Search code examples
xamarinxamarin.iosdelegatesxamarin-binding

Is it possible to pass a delegate as a parameter of another delegate in bindings project


I'm attempting to get a bindings project working. But I have encountered an error where it does not like me passing a delegate as a parameter to another delegate.

The error I am recieving is this;

"Attempting to JIT compile method '(wrapper runtime-invoke) 
:BackgroundFetchResultHandler(object,UIBackgroundFetchResult)' while running 
with --aot-only.

See http://docs.xamarin.com/ios/about/limitations for more information.\n"

So what's happening is I'm using PushySDK and created a bindings library project. Everything in the bindings project works except for the BackgroundFetchResultHandler delegate. If I try to use an action or Func instead of a delegate for the BackgroundFetchResultHandler the bindings lib wont compile.

My apiDefinitions.cs file is this;

using System;
using Foundation;
using UIKit;

namespace PushySDK
{
    public delegate void BackgroundFetchResultHandler(UIBackgroundFetchResult result);
    public delegate void NotificationHandler(NSDictionary info, BackgroundFetchResultHandler action);

    // @interface Pushy : NSObject
    [BaseType(typeof(NSObject), Name = "_TtC8PushySDK5Pushy")]
    [DisableDefaultCtor]
    interface Pushy
    {
        // -(instancetype _Nonnull)init:(UIResponder * _Nonnull)appDelegate application:(UIApplication * _Nonnull)application __attribute__((objc_designated_initializer));
        [Export("init:application:")]
        [DesignatedInitializer]
        IntPtr Constructor(UIResponder appDelegate, UIApplication application);

        // -(void)setNotificationHandler:(void (^ _Nonnull)(NSDictionary * _Nonnull, void (^ _Nonnull)(UIBackgroundFetchResult)))notificationHandler;
        [Export("setNotificationHandler:")] 
        void SetNotificationHandler(NotificationHandler notificationHandler);

        // -(void)register:(void (^ _Nonnull)(NSError * _Nullable, NSString * _Nonnull))registrationHandler;
        [Export("register:")]
        void Register(Action<NSError, NSString> registrationHandler);
    }
}

I've attempted to use action instead of delegate but I get the same error. If I change the the NotificationHandler Delegate to;

public delegate void NotificationHandler(NSDictionary info, int action);

Everythign works fine except for the fact that I cannot call the callback because it is an int. But my push notifications work properly and all is fine.

So Im just trying to find a way to call this callback without getting a crash. It seems like when Xamarin is generating the binding code it has a lot of trouble deciding on what type the second delegate is supposed to be.

If I use NotificatioNHandler as this;

public delegate void NotificationHandler(NSDictionary info, Action<UIBackgroundFetchResult> action);

The bindings library wont compile and I get an error about an invalid character "Action`1"

Is there anyway anyone can think of to get this working. Thanks in advance!


Solution

  • Okay so I ended up looking at the compiled code in the obj folder to see how they did the first delegate. So I just copied it for the delegate type changing it to an IntPtr. My api definition file ended up like this.

    using System;
    using Foundation;
    using UIKit;
    using ObjCRuntime;
    
    namespace PushySDK
    {
    internal delegate void NotificationHandler(NSDictionary info, IntPtr action);
    
    // @interface Pushy : NSObject
    [BaseType(typeof(NSObject), Name = "_TtC8PushySDK5Pushy")]
    [DisableDefaultCtor]
    interface Pushy
    {
        // -(instancetype _Nonnull)init:(UIResponder * _Nonnull)appDelegate application:(UIApplication * _Nonnull)application __attribute__((objc_designated_initializer));
        [Export("init:application:")]
        [DesignatedInitializer]
        IntPtr Constructor(UIResponder appDelegate, UIApplication application);
    
        // -(void)setNotificationHandler:(void (^ _Nonnull)(NSDictionary * _Nonnull, void (^ _Nonnull)(UIBackgroundFetchResult)))notificationHandler;
        [Export("setNotificationHandler:")]
        void SetNotificationHandler(NotificationHandler notificationHandler);
    
        // -(void)register:(void (^ _Nonnull)(NSError * _Nullable, NSString * _Nonnull))registrationHandler;
        [Export("register:")]
        void Register(Action<NSError, NSString> registrationHandler);
    }
    }
    

    I declare the delegate in my APPDelegate class where the callback will be assigned.

    [UnmanagedFunctionPointerAttribute(CallingConvention.Cdecl)]
    internal delegate void DBackgroundFetchResultHandler(IntPtr blockPtr, nuint result); 
    

    declare this internal class inside appDelegate as well

    internal class BackgroundFetchResultHandlerProxy
        {
            IntPtr blockPtr;
            DBackgroundFetchResultHandler invoker;
    
            [Preserve(Conditional = true)]
            public unsafe BackgroundFetchResultHandlerProxy(BlockLiteral* block)
            {
                blockPtr = _Block_copy((IntPtr)block);
                invoker = block->GetDelegateForBlock<DBackgroundFetchResultHandler>();
            }
    
            [Preserve(Conditional = true)]
            ~BackgroundFetchResultHandlerProxy()
            {
                _Block_release(blockPtr);
            }
    
            [Preserve(Conditional = true)]
            internal unsafe void Invoke(UIBackgroundFetchResult result)
            {
                invoker(blockPtr, (nuint) (UInt64) result);
            }
        }
    

    and then I changed my function to

    [Export("handleNotification:completionHandler:")]
    internal unsafe void HandleNotification(NSDictionary info, IntPtr actionPtr)
    {
        var proxy = new BackgroundFetchResultHandlerProxy((BlockLiteral *)actionPtr);
            proxy.Invoke(UIBackgroundFetchResult.NewData);
    }
    

    and then Im able to call

    pushy.SetNotificationHandler(HandleNotification);