Add the dynamic library plus any non-system dynamic library dependencies as "Native References" in Xamarin Studio.
Fix up the shared library install names
for all of the libraries. Specifically, use install_name_tool -change
(see the man
page) to set all of the non-system dependencies to match one of the following two options:
@loader_path/libdependency.dylib
@executable_path/../MonoBundle/libdepdency.dylib
Import methods from your library via [DllImport("libmylibrary")]
, and then P/Invoke them.
Here's a complete working example: MonoMacNativeLib.zip. You can follow the steps below to recreate this project.
Copy and paste these example files, or download the zip.
liba.h
int foo();
liba.c
#include "liba.h" int foo() { return 5; }
libb.h
int bar();
libb.c
#include "libb.h" #include "liba.h" int bar() { return foo(); }
Makefile
OBJDIR := obj LIBDIR := lib all: $(LIBDIR)/libb.dylib $(OBJDIR)/%.o : %.c %.h | $(OBJDIR) clang -arch i386 -fpic -c $< -o $@ $(LIBDIR)/liba.dylib: $(OBJDIR)/liba.o | $(LIBDIR) clang -arch i386 -dynamiclib $(OBJDIR)/liba.o -o $(LIBDIR)/liba.dylib $(LIBDIR)/libb.dylib: $(OBJDIR)/libb.o $(LIBDIR)/liba.dylib clang -arch i386 -dynamiclib $(OBJDIR)/libb.o $(LIBDIR)/liba.dylib -o $(LIBDIR)/libb.dylib $(OBJDIR): mkdir -p obj $(LIBDIR): mkdir -p lib clean: rm $(OBJDIR)/* $(LIBDIR)/*
Be sure to build for the i386
architecture since neither MonoMac nor Xamarin.Mac yet supports 64-bit applications.
Create a new MonoMac Project
, or a new Xamarin.Mac Project
.
Add the compiled liba.dylib
and libb.dylib
libraries as Native References
.
Add a text field (for example a Wrapping Label
) to the MainWindow.xib
layout as a place to display the return value of the native library method. Connect the text field to an outlet.
DllImport
the method from the native library:
[DllImport("libb", EntryPoint = "bar")] public static extern int Bar();
Create a MainWindowController.WindowDidLoad()
override method that P/Invokes the native method and outputs the results:
public override void WindowDidLoad() { base.WindowDidLoad(); OutputTextField.StringValue = Bar().ToString(); }
Try building and running the app.
System.DllNotFoundException: libbcaused by
Library not loaded: lib/liba.dylib
Running the app at this step causes a DllNotFoundException
.
Unhandled Exception: System.DllNotFoundException: libb at (wrapper managed-to-native) TestApp.MainWindowController.Bar () at TestApp.MainWindowController.WindowDidLoad ()
In this case the DllNotFoundException
is misleading. The problem is not actually that libb.dylib
cannot be found. We can get more information about what's really happening if we run the app from the command line:
MONO_LOG_LEVEL=debug MONO_ENV_OPTIONS=--trace=E:all TestApp/bin/Debug/TestApp.app/Contents/MacOS/TestApp > TestApp.o.txt 2> TestApp.e.txt
TestApp.o.txt
file: Library not loaded: lib/liba.dylib … image not found
Mono: DllImport attempting to load: 'libb'.
Mono: DllImport error loading library '~/Projects/TestApp/TestApp/bin/Debug/TestApp.app/Contents/MonoBundle/libb': 'dlopen(~/Projects/TestApp/TestApp/bin/Debug/TestApp.app/Contents/MonoBundle/libb, 9): image not found'.
Mono: DllImport error loading library '~/Projects/TestApp/TestApp/bin/Debug/TestApp.app/Contents/MonoBundle/libb.dylib': 'dlopen(~/Projects/TestApp/TestApp/bin/Debug/TestApp.app/Contents/MonoBundle/libb.dylib, 9): Library not loaded: lib/liba.dylib
Referenced from: ~/Projects/TestApp/TestApp/bin/Debug/TestApp.app/Contents/MonoBundle/libb.dylib
Reason: image not found'.
Thanks to the MONO_LOG_LEVEL=debug
environment variable, the standard output now tells us that the real reason we're getting the DllNotFoundException
for libb
is that the program can't find lib/liba.dylib
.
Side note: if you prefer, you can set the MONO_LOG_LEVEL
environment variable to debug
by creating an Environment variables
(aka LSEnvironment
) property in the Info.plist
. Then the verbose output will appear in Xamarin Studio's Application Output pad when you debug the app.
lib/liba.dylib
is not foundbecause there is no file at that path
Let's check the contents of the app bundle:
$ cd TestApp/TestApp/bin/Debug/; find TestApp.app/Contents
TestApp.app/Contents
TestApp.app/Contents/Info.plist
TestApp.app/Contents/MacOS
TestApp.app/Contents/MacOS/TestApp
TestApp.app/Contents/MonoBundle
TestApp.app/Contents/MonoBundle/liba.dylib
TestApp.app/Contents/MonoBundle/libb.dylib
TestApp.app/Contents/MonoBundle/MonoMac.dll
TestApp.app/Contents/MonoBundle/TestApp.exe
TestApp.app/Contents/MonoBundle/TestApp.exe.mdb
TestApp.app/Contents/Resources
TestApp.app/Contents/Resources/MainMenu.nib
TestApp.app/Contents/Resources/MainWindow.nib
We can see that both liba.dylib
and libb.dylib
are in the MonoBundle/
folder. And more importantly, there is no lib/
folder. This is the correct behavior.
lib/liba.dylib
is within libb.dylib
itself. It is not related to MonoMac.We can immediately see that libb.dylib
contains an incorrect reference if we check the dynamic library dependencies using otool
:
$ otool -L libb.dylib libb.dylib: lib/libb.dylib (compatibility version 0.0.0, current version 0.0.0) lib/liba.dylib (compatibility version 0.0.0, current version 0.0.0) /usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 169.3.0)
We can also confirm that attempting to load libb
from a C program hits the same problem as the MonoMac app:
prog.c
#include <stdio.h> #include <dlfcn.h> int main(int argc, char **argv) { void *libb; libb = dlopen("libb.dylib", 0); if (!libb) { fprintf(stderr, "%s\n", dlerror()); return 1; } return 0; }
$ cd TestApp/TestApp/bin/Debug/TestApp.app/Contents/MonoBundle
$ clang -arch i386 prog.c -o prog
$ ./prog
dlopen(libb.dylib, 0): Library not loaded: lib/liba.dylib Referenced from: ~/Projects/TestApp/TestApp/bin/Debug/TestApp.app/Contents/MonoBundle/libb.dylib Reason: image not found
liba.dylib
to @loader_path/liba.dylib
Fortunately, this incorrect reference is easy to fix. We can just use install_name_tool
to fix it up:
$ cd TestApp/TestApp/lib
$ install_name_tool -change lib/liba.dylib @loader_path/liba.dylib libb.dylib
The special @loader_path
value tells dlopen()
to search for liba
in a path relative to libb
.
If we now re-build the MonoMac app and debug it, the app calls the native library successfully and outputs the result!
-install_name
at compile time instead of using install_name_tool
Download the updated version of the native libraries folder.
If we pass the -install_name
argument to clang
, we can set the install name for liba.dylib
to @loader_path/liba.dylib
right in the Makefile
. That way we won't have to worry about fixing it again if we later rebuild the native libraries.
Makefile
lines$(LIBDIR)/liba.dylib: $(OBJDIR)/liba.o | $(LIBDIR) clang -arch i386 -dynamiclib -install_name "@loader_path/liba.dylib" $(OBJDIR)/liba.o -o $(LIBDIR)/liba.dylib $(LIBDIR)/libb.dylib: $(OBJDIR)/libb.o $(LIBDIR)/liba.dylib clang -arch i386 -dynamiclib -install_name "@loader_path/libb.dylib" $(OBJDIR)/libb.o $(LIBDIR)/liba.dylib -o $(LIBDIR)/libb.dylib
Native References
It is not strictly necessary to include dynamic libraries as Native References
when using them in a MonoMac app. It is the easiest option when using single-file libraries. But for frameworks a better option is to mimic the layout of Objective-C apps and copy each framework folder directly into the Content
directory of the app bundle. In most cases, this approach will require a few additional steps to tell the MonoMac app where to find the libraries.
Another update with more details soon.
Submit a comment or correction
Copyright (c) 2014 Xamarin Inc http://www.xamarin.com
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
12 May 2014 | Posted |