Search code examples
c#xcodemonomacdlopenxamarin.mac

Xamarin.Mac: Error "the native class hasn't been loaded" when creating instance of native class, cannot dlopen


I am trying to get a very simple test framework to work in MonoMac / Xamarin.Mac. It's a 32-bit framework that has only one method, "run" that simply returns the NSString @"OK". When I import the dll and create an instance of the class it throws the following error at runtime:

Could not create an native instance of the type 'OPN200x.SimpleClass': the native class hasn't been loaded.
It is possible to ignore this condition by setting MonoMac.ObjCRuntime.Class.ThrowOnInitFailure to false.
  • I've double-checked with lipo and the binary is indeed 32-bit.
  • I've tried to import and run this framework into a regular Xcode project and it works fine.
  • I've imported a .dylib in my MonoMac project just to test and I can dlopen it.
  • The file in the .framework will not dlopen.

These are the contents of Main.cs:

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using OPN200x;
using System.IO;
using MonoMac.Foundation;
using MonoMac.AppKit;
using MonoMac.ObjCRuntime;

namespace MacTest
{
    class MainClass
    {

        static void Main (string[] args)
        {
            var absolutePath = "/Users/UserName/Code/Xamarin/MacTest/MacTest/Frameworks/Simple.framework/Versions/A/Simple";
            if (!File.Exists (absolutePath)) {
                Console.Error.WriteLine ("File not found");
            } else {
                Console.WriteLine ("File Found");
            }

            var absolutePath2 = "/Users/UserName/Code/Xamarin/MacTest/MacTest/bin/Debug/MacTest.app/Contents/MonoBundle/libopn_driver.dylib";
            if (!File.Exists (absolutePath2)) {
                Console.Error.WriteLine ("File not found");
            } else {
                Console.WriteLine ("File Found");
            }

            var absolutePath3 = "/Users/UserName/Code/Xamarin/MacTest/MacTest/bin/Debug/MacTest.app/Contents/MonoBundle/Simple.framework/Versions/A/Simple";
            if (!File.Exists (absolutePath3)) {
                Console.Error.WriteLine ("File not found");
            } else {
                Console.WriteLine ("File Found");
            }

            if (Dlfcn.dlopen ("/Users/UserName/Code/Xamarin/MacTest/MacTest/bin/Debug/MacTest.app/Contents/MonoBundle/libopn_driver.dylib", 0) == IntPtr.Zero) {
                Console.Error.WriteLine("Unable to load libopn_driver.dylib"); // Does not get logged
            }

            Console.Error.WriteLine (System.Reflection.Assembly.GetExecutingAssembly ().Location);
            if (Dlfcn.dlopen("Simple.framework/Versions/A/Simple", 0) == IntPtr.Zero) 
            {
                Console.Error.WriteLine("Unable to load the dynamic library."); // Gets logged
            }

            if (Dlfcn.dlopen("/Users/UserName/Code/Xamarin/MacTest/MacTest/bin/Debug/MacTest.app/Contents/MonoBundle/Simple.framework/Versions/A/Simple", 2) == IntPtr.Zero) 
            {
                Console.Error.WriteLine("Unable to load the dynamic library."); // Gets logged
            }



            if (Dlfcn.dlopen("/Users/UserName/Code/Xamarin/MacTest/MacTest/Frameworks/Simple.framework/Versions/A/Simple", 2) == IntPtr.Zero) 
            {
                Console.Error.WriteLine("Unable to load the dynamic library."); // Gets logged
            }

            if (Dlfcn.dlopen("/Users/UserName/Code/Xamarin/MacTest/MacTest/Frameworks/Simple.framework/Simple", 2) == IntPtr.Zero) 
            {
                Console.Error.WriteLine("Unable to load the dynamic library."); // Gets logged
            }

            var simple = new SimpleClass (); // This line crashes
            var result = simple.Run;
            Console.WriteLine (result);

            NSApplication.Init ();
            NSApplication.Main (args);
        }
    }
}

It always finds the files that I check for, but dlopen only and always fails with the Simple framework and not the libopn_driver.dylib

Simple.dll has been created with sharpie and bmac, I think it went OK in the end but here's the description of it: Converting custom native MacOS X library to dll using MonoMac


Further investigation

I've added the following log in every place where I couldn't load the assembly:

Console.Error.WriteLine (Dlfcn.dlerror ());

This gives me in every place roughly the same error:

    dlopen(/Users/UserName/Code/Xamarin/MacTest/MacTest/bin/Debug/MacTest.app/Contents/MonoBundle/Simple.framework/Versions/A/Simple, 2): no suitable image found.  Did find:
    /Users/UserName/Code/Xamarin/MacTest/MacTest/Frameworks/Simple.framework/Versions/A/Simple: unknown file type, first eight bytes: 0x21 0x3C 0x61 0x72 0x63 0x68 0x3E 0x0A
    /Users/UserName/Code/Xamarin/MacTest/MacTest/bin/Debug/MacTest.app/Contents/MonoBundle/Simple.framework/Versions/A/Simple: unknown file type, first eight bytes: 0x21 0x3C 0x61 0x72 0x63 0x68 0x3E 0x0A

I think this means that the image is not compatible in some sort of way, but I don't know exactly why. I've tested with file:

file Simple.Framework/Versions/A/Simple
Simple.Framework/Versions/A/Simple: current ar archive random library

And lipo:

lipo Simple.Framework/Versions/A/Simple -info
input file Simple.Framework/Versions/A/Simple is not a fat file
Non-fat file: Simple.Framework/Versions/A/Simple is architecture: i386

This seems OK to me at first glance, so does the resulting application:

file MacTest
MacTest: Mach-O executable i386

MacOS Lucas$ lipo MacTest -i
Non-fat file: MacTest is architecture: i386

Solution

  • Make sure that you set your Framework to Dynamic Library:

    enter image description here

    Now the output of file reads:

    file Simple.Framework/Versions/A/Simple
    Simple.Framework/Versions/A/Simple: Mach-O dynamically linked shared library i386