Calling a dynamic library from MonoMac - Part 1

I ran into some difficulty today calling a native C library from MonoMac today and would have killed to have a working example to play with and build off of.  Since I finally was able to get everything working, I thought I'd share the sample here so that if someone else finds themselves in the same position I was they'll hopefully find this post.  (Besides, I'll probably need this as a refresher the next time I try to do this.)

This post will simply cover having a C# application call a native library with Mono from the command line.  I'll follow up with a second post later that explains how to integrate the library into a packaged MonoMac application.

First, in order to call a native library, we'll need a native library to call.  For the purposes of this sample, we'll create a simple function that echo's an integer value back to us.  Here's the C source code for that simple function:

//
// echo.c
//

#include <stdio.h>

int EchoInteger(int value)
{
    return value;
}

Second, we'll need to compile the source code:

gcc -arch i386 -c echo.c -o bin/echo.o

Third, we'll need to create dynamic library:

gcc -arch i386 \
 -dynamiclib \
 -o bin/libEchoTest.dylib \
 bin/echo.o \
 -install_name $CURRENT_DIR/libEchoTest.dylib

Next, one of the problems that I ran into originally was that my library wasn't built properly and the  function I was trying to call wasn't actually exposed externally in the library.  If you want to verify which symbols are exposed externally, you can use the "nm" command.  Here's how it would be invoked for this library, along with the results of execution.

/Applications/Xcode.app/Contents/Developer/usr/bin/nm -g bin/libEchoTest.dylib

00000f90 T _EchoInteger
         U dyld_stub_binder

The key thing to notice is the "T _EchoInteger" entry, which tells us the function we wrote is exposed externally.

Now that we know that our library is built correctly, we can move onto the C# call to invoke it.:

//
// Main.cs
//

using System;
using System.Runtime.InteropServices;

namespace MonoMacLoadLib
{
	class MainClass
	{
		[DllImport ("EchoTest")]
 		public static extern int EchoInteger (int arg);
 			
		static void Main (string[] args)
		{
			int echoResults = EchoInteger(11);
			System.Console.WriteLine("Echo Results:{0}",echoResults);
		}
	}
}

The key things is the code are the [DllImport("EchoTest")] public static extern int EchoInteger(int arg); lines.  These will bind the C# method to the C function via P/Invoke.  By default (as shown above), it will bind the C# method to a function of the same name (which in our case is "EchoInteger" in both the C and the C# code).  If you want to C# name to be different than in the C library, you can add an EntryPoint to the annotation: [DllImport ("EchoTest", EntryPoint="SomeOtherName")]

Now that we have the C# code written, we can move onto compiling and trying to run this.  Here is the command necessary to compile that class with Mono:

dmcs /noconfig "/out:bin/Test.exe" \
 "/r:/Library/Frameworks/Mono.framework/Versions/2.10.9/lib/mono/4.0/System.dll" \
 "/r:/Applications/MonoDevelop.app/Contents/MacOS/lib/monodevelop/AddIns/MonoDevelop.MonoMac/MonoMac.dll" \
 /nologo /warn:4 /debug:full /optimize- /codepage:utf8 /platform:x86 /define:DEBUG  /t:exe "Main.cs"

And here is the command necessary to run this:

mono bin/Test.exe

If everything went successfully you should see the following output:

Echo Results:11

Troubleshooting

If you run into problems (as I did originally) you may see a couple different errors/exceptions.  Here are a few things to look for and possible solutions to them:

The first error that you may see is the System.EntryPointNotFoundException exception.  If this happens, then you know that Mono was able to find your library; however it wasn't able to find the function that you referenced with your [DllImport] annotation.  You can use the "nm" command referenced above to make sure the expected function is externally available in the library, and you can also verify that your EntryPoint entry or C# method name are spelled correctly to match the C function.

The second error that you may see a System.DllNotFoundException exception.  This will occur if Mono can't find and load the dynamic library.  To resolve this, make sure that the name of the library is correct in your [DllImport] annotation, and make sure that it's in a location that it can be found by Mono.

Lastly, if you want to verify that Mono can even load your dynamic library, you can manually load the library using "MonoMac.ObjCRuntime.Dlfcn.dlopen()" method.  If that method is able to load the library, then it will return an IntPtr referencing the loaded library, otherwise it will return a 0 (IntPtr.Zero).  This was useful to me diagnose that I had errors in my library that prevented it from being loaded even though it was named and placed in the correct location.  Here is the C# sample from above that has been modified to also call the "dlopen" method as a first step.

using System;
using System.Runtime.InteropServices;
using MonoMac.ObjCRuntime;

namespace MonoMacLoadLib
{
	class MainClass
	{
		[DllImport ("EchoTest", EntryPoint="EchoInteger")]
 		public static extern int EchoInteger (int arg);
 			
		static void Main (string[] args)
		{
			// The current directly is expected to be passed in as arg[0] for this sample to work
			string path = args[0] + "/bin/libEchoTest.dylib";
			System.Console.WriteLine("Dylib Path:{0}", path);	
					
			IntPtr libPtr = Dlfcn.dlopen(path, 1);
			System.Console.WriteLine("Pointer:{0}",libPtr);
			
			int echoResults = EchoInteger(11);
			System.Console.WriteLine("Echo Results:{0}",echoResults);
		}
	}
}	

The source code for this sample is available on GitHub, along with a shell script that will compile everything and run the sample.