Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions src/bgen/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6720,6 +6720,13 @@ public void Generate (Type type)
} else
print ("public _{0} () {{ IsDirectBinding = false; }}\n", dtype.Name);

// Tell the trimmer to not remove any of our protocol implementations, they might be called from native
// code even if we don't have any event handlers listening for them.
print ($"[DynamicDependency (DynamicallyAccessedMemberTypes.PublicMethods, typeof (_{dtype.Name}))]");
print ($"static _{dtype.Name} ()");
print ("{");
print ("\tGC.KeepAlive (null);"); // need to do _something_ (doesn't seem to matter what), otherwise the static cctor (and the DynamicDependency attributes) are trimmed away.
print ("}");

string shouldOverrideDelegateString = isProtocolEventBacked ? "" : "override ";

Expand Down Expand Up @@ -6748,8 +6755,6 @@ public void Generate (Type type)
} else
previous_miname = miname;

// Tell the trimmer to not remove the delegate's method if someone is listening for the event
print ($"[DynamicDependency (nameof ({mi.Name}))]");
if (mi.ReturnType == TypeCache.System_Void) {
if (bta.Singleton || mi.GetParameters ().Length == 1)
print ("internal EventHandler? {0};", miname);
Expand Down
11 changes: 11 additions & 0 deletions tests/bindings-test/ApiDefinition.cs
Original file line number Diff line number Diff line change
Expand Up @@ -853,6 +853,14 @@ interface HitchhikerDelegate {
[EventArgs ("")]
[Export ("buildIntergalacticHighway:")]
void BuildIntergalacticHighway (Hitchhiker sender);

[EventArgs ("")]
[Export ("byeByeDolphins:")]
void ByeByeDolphins (Hitchhiker sender);
Comment on lines +857 to +859
Copy link

Copilot AI Jan 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ByeByeDolphins method is marked as @required in the Objective-C protocol (libtest.h line 370-371), but is missing the [Abstract] attribute in the C# binding. This inconsistency means the binding generator will treat it as optional instead of required.

For consistency with the native protocol definition and to properly test the fix, add the [Abstract] attribute before this method declaration.

Copilot uses AI. Check for mistakes.

[EventArgs ("")]
[Export ("hitchhikeWithVogons:")]
void HitchhikeWithVogons (Hitchhiker sender);
}

[BaseType (typeof (NSObject), Delegates = new string [] { "Delegate" }, Events = new Type [] { typeof (HitchhikerDelegate) })]
Expand All @@ -862,5 +870,8 @@ interface Hitchhiker {

[Export ("destroyEarth")]
void DestroyEarth ();

[Export ("buildHighway")]
void BuildHighway ();
}
}
23 changes: 20 additions & 3 deletions tests/monotouch-test/ObjCRuntime/RegistrarTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using PlatformException = ObjCRuntime.RuntimeException;
using NativeException = ObjCRuntime.ObjCException;
using CoreAnimation;
using CoreBluetooth;
using CoreGraphics;
using CoreLocation;
#if !__TVOS__
Expand Down Expand Up @@ -56,15 +57,31 @@ public static Registrars CurrentRegistrar {
[Test]
public void EventTestCustomType ()
{
var earthDestroyed = false;
var earthDestroyed = 0;
using var vogons = new Hitchhiker ();
vogons.BuildIntergalacticHighway += (object sender, EventArgs ea) => {
earthDestroyed = true;
earthDestroyed++;
};
vogons.DestroyEarth ();
Assert.That (earthDestroyed, Is.True, "Event raised");
Assert.That (earthDestroyed, Is.EqualTo (1), "Event raised");

vogons.BuildHighway ();
Assert.That (earthDestroyed, Is.EqualTo (2), "Event raised");
}

[Test]
public void EventTestOSType ()
{
using var mgr = new CBCentralManager (new DispatchQueue ("com.xamarin.tests.ios-plain"));
// The bug happens when there's no event listener for 'UpdatedState', which is a required protocol method.
// mgr.UpdatedState += (sender, e) => { };

// attach at least one event handler, to create an instance of the internal class that transforms protocol callbacks into events
mgr.DiscoveredPeripheral += (sender, e) => { };

// mimic what CBCentralManager does, instead of having to spin up the entire Bluetooth stack.
Messaging.void_objc_msgSend_IntPtr (mgr.Delegate.Handle, Selector.GetHandle ("centralManagerDidUpdateState:"), IntPtr.Zero);
}

[Test]
public void EventTestSdkType ()
Expand Down
4 changes: 4 additions & 0 deletions tests/test-libraries/libtest.h
Original file line number Diff line number Diff line change
Expand Up @@ -367,14 +367,18 @@ typedef void (^outerBlock) (innerBlock callback);

@class Hitchhiker;
@protocol HitchhikerDelegate <NSObject>
@required
-(void) byeByeDolphins: (Hitchhiker *) sender;
@optional
-(void) buildIntergalacticHighway: (Hitchhiker *) sender;
-(void) hitchhikeWithVogons: (Hitchhiker *) sender;
@end

@interface Hitchhiker : NSObject {
}
@property (retain) id<HitchhikerDelegate> delegate;
-(void) destroyEarth;
-(void) buildHighway;
@end

#pragma clang diagnostic pop
Expand Down
6 changes: 6 additions & 0 deletions tests/test-libraries/libtest.m
Original file line number Diff line number Diff line change
Expand Up @@ -1542,6 +1542,12 @@ @implementation ClassWithNoDefaultCtor : NSObject {
@implementation Hitchhiker : NSObject {
}
-(void) destroyEarth
{
[self.delegate byeByeDolphins: self];
[self.delegate buildIntergalacticHighway: self];
[self.delegate hitchhikeWithVogons: self];
}
-(void) buildHighway
{
[self.delegate buildIntergalacticHighway: self];
}
Expand Down
Loading