Forum Xamarin Xamarin.iOS

Weird unrecognized selector error in one project, not in the other

laarmenlaarmen FRMember

Hi,

I'm currently in the process of making a Xamarin Forms PDF viewing component for internal use in my company, based on RadaeePDF SDK.

The iOS SDK gives us a .a library, probably written in C, and a few Obj-C classes (with source) to wrap around it to have it in nice, iOS views. So, I've compiled the project into a .a, made a new binding library, and a new Xamarin.iOS class library to host my Forms renderer.

Naturally, I've made a sample project that uses the component. So far, so good, it all works.

However, the first time we integrated the component into one of our existing Forms app, I've hit a "unrecognized selector" error in the existing project, even though the code is strictly similar.

The error is the following:

2017-01-31 12:08:03.549 BatiScriptLiteiOS[736:535362] -[PDFView vOpen::]: unrecognized selector sent to instance 0x1802e7b0

but it seems any PDFView method results in a similar crash. The PDFView class is used internally by the component and is not part of the exposed API.

The API definition is the following (I trimmed out a few methods):

    [BaseType (typeof(UIScrollView))]
    interface PDFView : PDFVInnerDel, IUIScrollViewDelegate, IUIPickerViewDelegate, IUIPickerViewDataSource
    {
        [Export("initWithFrame:")]
        IntPtr Constructor(CGRect frame);

        [Export ("vOpen::")]
        void vOpen(PDFDoc doc, [NullAllowed] PDFViewDelegate deleg);

        [Export ("vGetDocWidth")]
        int DocWidth { get; }
        [Export ("vGetDocHeight")]
        int DocHeight { get; }
    }
[BaseType (typeof(NSObject))]
interface PDFDoc
{

    // -(int)openStream:(id<PDFStream>)stream :(NSString *)password;
    [Export ("openStream::")]
    int OpenStream (PDFStream stream, [NullAllowed] string password);
}

And the code in the renderer:

var page = e.NewElement;
_pdfView = new PDFView(new CoreGraphics.CGRect(0, 0, NativeView.Bounds.Width, NativeView.Bounds.Height));
SetNativeControl(_pdfView);
_pdfView.AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
var src = await page.DocumentAsyncSrc();
PDFDoc doc = new PDFDoc();
doc.OpenStream(new StreamWrapper() { Src = src }, null);
_pdfView.vOpen(doc, new PdfViewEventListener() { ToUpdate = page }); // This call crashes in the non-sample app
// The PDFViewEventListener is a C# impl of an objC protocol, as is the StreamWrapper

I've tried quite a few things, to no avail. The selector error occurs on iPhone, iPad, and simulator. I've changed the linking all through None, SDK only and full, as it seems it is the likeliest culprit.

Anyone has any idea ?

Bonus points to those who tell me why there is no exception thrown AFAICT.

Thanks in advance

Tagged:

Best Answer

  • laarmenlaarmen FRMember
    Accepted Answer

    For posterity, here is how I solved the bug. I simply renamed the PDFView class into RDPDFView, both in ObjC and C#. I'm guessing there was a name conflict with one of the libraries used, although i'll be damned if I can find which one. Honestly, I found it on pure luck, having exhausted any rational lead.

    Also, I renamed my ApiDefinition.cs into RadaeeApiDefinition, since a deep dive into Xamarion iOS sources told me the file name defined the namespace for some of the generated code, and there was also a conflict in there with the WebP.Touch bindings used in our project. I'm guessing setting the Namespace property in a PropertyGroup in the csproj would have had the same effect, I'll try to post a patch to the documentation to mention this, as it changes the behaviour of the generated constructors:

                [Export ("initWithFrame:")]
                [CompilerGenerated]
                public RDPDFView (CGRect frame)
                        : base (NSObjectFlag.Empty)
                {
                        IsDirectBinding = GetType ().Assembly == global::RadaeeApiDefinition.Messaging.this_assembly;
                        if (IsDirectBinding) {
                                InitializeHandle (global::RadaeeApiDefinition.Messaging.IntPtr_objc_msgSend_CGRect (this.Handle, Selector.GetHandle ("initWithFrame:"), frame), "initWithFrame:");
                        } else {
                                InitializeHandle (global::RadaeeApiDefinition.Messaging.IntPtr_objc_msgSendSuper_CGRect (this.SuperHandle, Selector.GetHandle ("initWithFrame:"), frame), "initWithFrame:");
                        }
                }
    

    In this correct sample, RadaeeApiDefinition.Messaging.this_assembly does point to the correct assembly. If I hadn't renamed, it would have been on the WebP assembly as they used the same file name (which is the one advised in the documentation), thus calling msgSendSuper. I'm not entirely sure it wouldn't have worked, but logically it would have called the superclass constructor and not the proper one, resulting in an undefined state in the best case, and a badly-typed object in the worst, which would have explained the symptoms encountered.

    All in all, it was a hair-pulling case, I tell you!

Answers

  • laarmenlaarmen FRMember

    Allright, a bit more info.

    It turns out the PDFView initWithFrame objc method isn't called at all. I still dunno exactly what is called instead, stay tuned :)

  • laarmenlaarmen FRMember
    Accepted Answer

    For posterity, here is how I solved the bug. I simply renamed the PDFView class into RDPDFView, both in ObjC and C#. I'm guessing there was a name conflict with one of the libraries used, although i'll be damned if I can find which one. Honestly, I found it on pure luck, having exhausted any rational lead.

    Also, I renamed my ApiDefinition.cs into RadaeeApiDefinition, since a deep dive into Xamarion iOS sources told me the file name defined the namespace for some of the generated code, and there was also a conflict in there with the WebP.Touch bindings used in our project. I'm guessing setting the Namespace property in a PropertyGroup in the csproj would have had the same effect, I'll try to post a patch to the documentation to mention this, as it changes the behaviour of the generated constructors:

                [Export ("initWithFrame:")]
                [CompilerGenerated]
                public RDPDFView (CGRect frame)
                        : base (NSObjectFlag.Empty)
                {
                        IsDirectBinding = GetType ().Assembly == global::RadaeeApiDefinition.Messaging.this_assembly;
                        if (IsDirectBinding) {
                                InitializeHandle (global::RadaeeApiDefinition.Messaging.IntPtr_objc_msgSend_CGRect (this.Handle, Selector.GetHandle ("initWithFrame:"), frame), "initWithFrame:");
                        } else {
                                InitializeHandle (global::RadaeeApiDefinition.Messaging.IntPtr_objc_msgSendSuper_CGRect (this.SuperHandle, Selector.GetHandle ("initWithFrame:"), frame), "initWithFrame:");
                        }
                }
    

    In this correct sample, RadaeeApiDefinition.Messaging.this_assembly does point to the correct assembly. If I hadn't renamed, it would have been on the WebP assembly as they used the same file name (which is the one advised in the documentation), thus calling msgSendSuper. I'm not entirely sure it wouldn't have worked, but logically it would have called the superclass constructor and not the proper one, resulting in an undefined state in the best case, and a badly-typed object in the worst, which would have explained the symptoms encountered.

    All in all, it was a hair-pulling case, I tell you!

Sign In or Register to comment.