Including custom dll and dylib in MonoMac app

2019-04-27 03:51发布

问题:

Background: My MonoMac app uses a custom build of sqlite3.0.8.6.dylib.

I need the exact steps to have MyApp.app use this dylib.

Here are some steps I took:

  1. Copied the dylib to MyApp.app/Contents/SharedSupport. (Related question: is this the preferred location for 3rd party dylibs or is MyApp.app/Contents/Frameworks preferred?)

  2. Changed the installed name for the library so that it matches its new location.

    MyApp.app/Contents/SharedSupport> otool -L libsqlite3.0.8.6.dylib 
    libsqlite3.0.8.6.dylib:
        @executable_path/../SharedSupport/libsqlite3.0.8.6.dylib (compatibility version 9.0.0, current version 9.6.0)
        /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 159.1.0)
    

Problems

  1. MyApp.app/Contents/MacOS/MyApp does not reference the dylib directly, so I can't use install_name_tool to point to the new library location. I believe that the library is referenced by System.Data.Sqlite.dll.
  2. I thought of overriding DYLD_FALLBACK_LIBRARY_PATH in the launcher script but MonoMac now uses a binary launcher (MyApp.app/Contents/MacOS/MyApp), not a script, so I'm out of luck there.

Would the MonoMac gods please help with what must be a simple solution? I've spent a couple of months, on and off, trying to get this to work.

And please provide exact steps - this problem is all about the details.

回答1:

Have a look at my answer to this question: Setting path of the Native Library for DllImport on Mono for Mac

The binary launcher comes from monodevelop/main/build/MacOSX/monostub.m.

You can use either MyApp.app/Contents/Frameworks or some other path, the important part is not to use any path names in your [DllImport] but instead add the <dllmap> using @executable_path to your app.config like I explained in that other answer.

There's a also a link to a test app on github in there.

Detailed Instructions

  1. Pick a path inside the MyApp.app to install your native dll, for instance Contents/SharedSupport/sqlite3.0.8.6.dylib.

  2. Compute the relative path from the directory where the managed assembly is located to the native .dll and prepend @executable_path to it.

    For instance, if your managed assembly is in Contents/MonoBundle/MyApp.exe and the native dll in Contents/SharedSupport/sqlite3.0.8.6.dylib, then it's @executable_path/../SharedSupport/sqlite3.0.8.6.dylib.

  3. Change the installed name of the library to this relative path using install_name_tool.

  4. Add a new MyApp.exe.config file to your project, containing

    <configuration>
      <dllmap dll="sqlite" target="@executable_path/../SharedSupport/sqlite3.0.8.6.dylib" />
    </configuration>
    

    Use the path that you computed in step 2. for the target field. Right-click the file in MonoDevelop, select "Quick Properties" from the context menu and enable "Copy to Output directory". This will copy the file into the Contents/MonoBundle directory, so it sits right next to your MyApp.exe.

  5. Use [DllImport ("sqlite")] to reference this in your code.

When another library references it

When another library, for instance Mono.Data.Sqlite.dll references it, it get a little bit more complicated.

Use the same steps as above, but you need to figure out which name that other library is using in its [DllImport] to reference the native library and put that into the <dllimport dll="..." />. You can either look for the [DllImport] statements in the source code or run monodis on the assembly and search for pinvokeimpl, for instance:

// method line 679
.method assembly static hidebysig pinvokeimpl ("sqlite3" as "sqlite3_create_function" cdecl )
       default int32 sqlite3_create_function (native int db, unsigned int8[] strName, int32 nArgs, int32 nType, native int pvUser, class Mono.Data.Sqlite.SQLiteCallback func, class Mono.Data.Sqlite.SQLiteCallback fstep, class Mono.Data.Sqlite.SQLiteFinalCallback ffinal)  cil managed preservesig 
{
    // Method begins at RVA 0x0
} // end of method UnsafeNativeMethods::sqlite3_create_function

So Mono.Data.Sqlite.dll is using "sqlite3" to reference the native dll, so your MyApp.exe.config file will look like this:

    <configuration>
      <dllmap dll="sqlite3" target="@executable_path/../SharedSupport/sqlite3.0.8.6.dylib" />
    </configuration>


回答2:

Be careful if you're using mono. @executable_path will return the path to the mono binary, instead of the actual .EXE executable. This should work correctly with Xamarin though.



回答3:

You can try manually loading the sqlite dylib.

[DllImport ("/usr/lib/libSystem.dylib")]
public static extern IntPtr dlopen (string path, int mode);
IntPtr p = dlopen("libsqlite3.0.8.6.dylib", 0);

Of course correctly resolving the path to your dylib.