[Mono-list] How to create C++ library and call it's functions from C# program?

Jonathan Pryor jonpryor@vt.edu
Thu, 11 Mar 2004 19:14:43 -0500


Below...

On Thu, 2004-03-11 at 15:25, Vadim B. Guzev wrote:
> Hello, mono-list@lists.ximian.com!
> 
> 
> I'm trying to call some simple C++ functions from C# program.
> Here's the sources I used:

First off, what platform are you trying to do this on?  When I did this
on Linux, I got slightly different output than you did; I didn't see the
"Failed to load library ./libmylib.so" messages.  I still saw the
System.MissingMethodException, though, which I expected (as I'll point
out below).

> 8<------------------------------------------------------------
> [vadim@server SCSI tests]$ cat FromCSharpToC.cs
> using System;
> using System.Runtime.InteropServices;
> 
> public class FromCSharpToC {
>  [DllImport("libmylib.so", EntryPoint="print")]

Strictly speaking, the "EntryPoint" isn't needed, as P/Invoke defaults
to using the name of the method.

>  static extern void print (string src);
> 
>  public static void Main( string[] args ) {
>   print("Hello world!");
>  }
> }
> 
> 8<------------------------------------------------------------
> 
> 8<------------------------------------------------------------
> [vadim@server SCSI tests]$ cat mylib.cpp
> #include <iostream.h>
> 
> static void print( const char* line ) {
>  cout << line << endl;
> }
> 
> 8<------------------------------------------------------------

And this is your actual problem: the declaration of the "print" C++
function.  There are two factors at work here:

1.  The "static" keyword means "don't export this symbol."  Which in
turn means "this symbol doesn't exist to any linker."  Which in turn
means "you can't call this function from C#, as the entire linker
infrastructure is working against you."

Find a good C language reference for more on what "static" does.  C++,
of course, adds some additional meanings for the keyword "static", then
deprecated the original C meaning, in favor of using anonymous
namespaces instead.  (Personally, I'd rather keep "static", so that the
symbols don't actually enter the linker's namespace...)

2.  C++ compilers use a technique called "name mangling", so that you
can have function overloads.  For example, your "print (const char*)"
function is *actually* the linker symbol:

	_Z5printPKc

On my machine, anyway, which is running Red Hat Linux 9 and GCC 3.2.2.

There are two solutions to this: the wrong way and the right way. :-)

The wrong way is to use the mangled name in your C# DllImport statement:

	[DllImport ("libmylib.so", EntryPoint="_Z5printPKc")]
	static extern void print (string src);

While this works, this is wrong because every C++ compiler uses a
different name mangling strategy, and the name mangling strategy can
even change between compiler versions.  By doing this, you're making the
C# program closely tied to your C++ compiler, which will only bring you
headaches later.

The right way is to instead tell the C++ compiler to disable name
mangling.  This is done by using an ``extern "C"'' modifier in your C++
code.  The downside to this is that you can not have two C++ function
overloads that are both marked as ``extern "C"'', as this would cause
confusion.

Putting this all together, your C++ code should look like:

	#include <iostream>

	using namespace std;

	extern "C" void
	print (const char *line)
	{
		cout << line << endl;
	}

> And here is a Makefile I used:
> 8<------------------------------------------------------------
> [vadim@server SCSI tests]$ cat Makefile
> all: o so exe
> 
> 
> o:
>         gcc -fpic -c mylib.cpp
> 
> so:
>         gcc -shared -o libmylib.so mylib.o
> 
> exe:
>         mcs FromCSharpToC.cs -o fromcsharptoc.exe
> 
> 8<------------------------------------------------------------
> 
> However, when I try to launch fromcsharptoc.exe it gives me the following:
> 
> 8<------------------------------------------------------------
> [vadim@server SCSI tests]$ make
> gcc -fpic -c mylib.cpp
> gcc -shared -o libmylib.so mylib.o
> mcs FromCSharpToC.cs -o fromcsharptoc.exe
> Compilation succeeded
> [vadim@server SCSI tests]$ mono fromcsharptoc.exe
> 
> ** (fromcsharptoc.exe:26607): WARNING **: Failed to load library
> ./libmylib.so (libmylib.so): ./libmylib.so: undefined symbol:
> __gxx_personality_v0

Make sure that "libmylib.so" is in your LD_LIBRARY_PATH environment
variable, so that the dynamic linker can find the library.

Though the "undefined symbol: __gxx_personality_v0" probably means that
one of the C++ libraries can't be found for some reason.  I would
suggest making sure that a "hello, world" C++ program correctly executes
from the same directory and environment you're trying to run this
program from.

> ** (fromcsharptoc.exe:26607): WARNING **: Failed to load library
> ./libmylib.so (libmylib.so): ./libmylib.so: undefined symbol:
> __gxx_personality_v0
> 
> Unhandled Exception: System.MissingMethodException: A missing method
> exception has occurred.
> in <0x00042> (wrapper managed-to-native) .FromCSharpToC:print (string)
> in <0x0000c> .FromCSharpToC:Main (string[])

This is what I expected.  Due to the C++ name mangling, Mono wasn't able
to find the function "print", as it didn't exist in libmylib.so. 
Re-compiling libmylib.so using my corrected print function should fix
this.

> 8<------------------------------------------------------------
> 
> Where's a mistake? Maybe I compile C++ source in a wrong way? (I'm a newbie
> in C++-programming).
> Is there some good tutorials on this theme? (I couldn't find anything :( )

MSDN is a good resource for this, though finding where to start reading
at MSDN can be daunting.

So I'll have to recommend my guide "Everything you (n)ever wanted to
know about Marshalling (and were afraid to ask!)", available at:

	http://www.jprl.com/~jon/interop.html

It's also part of the Mono handbook, and available through the Mono
Documentation (which appears to be down right now), at:

	http://www.go-mono.com/docs/

 - Jon