[Mono-list] PInvoke and Marshalling

Jonathan Pryor jonpryor@vt.edu
Tue, 10 Feb 2004 22:09:25 -0500


Below...

On Tue, 2004-02-10 at 21:19, James Lapalme wrote:
> Dear All,
> 
> I created a dll with the following:
> 
> extern "C" _declspec(dllexport) int myFunction(A*){return a->myInt();}
> 
> class A{
>     public:
>         int i;
>         int myInt(void){rerturn i;}
> }

Note that, as far as the compiler is concerned, the above class is
equivalent to:

	struct A {
		int i;
	};

	int __A_myInt (A* const this) {return this->i;}

	extern "C" int myFunction (A* a) {return __A_myInt (a);}

I'll revisit this later.

> I creates a C# client with the folllowing:
> 
> public class MyClient{
> 
>     [DllImport("MyDll.dll")]
>     public static extern in myFunction(A a);
> 
>     public static void Main(){
> 
>         A a = new A();
>         a.i=5;
>         Console.WriteLine(myFunction(a));
>     }
> }
> 
> [StructureLayout(LayputKind.Sequential)]
> public class A{
>         public int i;
>         public int myInt(void){rerturn i;}
> }
> 
> 
> Why does this code work because most of the example I have seen is
> always using classes to mimic struct with no methods only fields?

This works because of the C++ Object Model.  (I recommend reading
"Inside the C++ Object Model" by Stanley Lippman for more information.) 
In the C++ Object Model, the class is laid out as a "normal" C
structure, with each member of the class corresponding to a member in
the structure.  Normal (non-virtual) methods (member functions) do not
change the class layout.

This is why your example worked.  Your C# class declaration had the same
structural layout as the C++ class object.  Member functions do not
change anything in the class layout.  You could have written the
following, and been just as correct:

	// C# wrapper
	public struct A {
		public int i;
		public int myInt (void) {return i;}

		// Note: pass-by-ref to preserve C++ signature
		[DllImport ("MyDll.dll")]
		public static extern int myFunction (ref A a);
	}

Also note that marshaling the C++ object pointer only worked because it
was a simple class.  If the class had any virtual functions, all bets
are off, as virtual functions would require a virtual function table
pointer, which is placed in a compiler-dependent location, and could
cause the unmanaged code to read from random memory (as the managed and
unmanaged representations would then differ).

Furthermore, if the C++ class had a constructor/destructor that made any
assumptions about class members, you would have to somehow ensure that
you weren't violating the class's semantics, or you could have more
memory corruption occur.
 
> How is the object marshalled?

The standard marshaling rules apply.  So, if you're lucky, the class
object will be locked in memory, and a pointer to the locked object
passed to the native function.  If you're not lucky, the runtime will
create a marshaled copy of the object, and pass a copy of the object to
the native function, then copy the results back.

> I have readed .Net nad COM by Nathan/ COM and Net by Troelsen/MSND
> doc... there is no info on this matter. Where can I get more
> information on this subject?

"Inside the C++ Object Model" will tell you how C++ objects are laid out
in memory, which is essential to knowing how they're ultimately
marshaled (or even if it's *possible* to marshal them).

You might also find my helpful P/Invoke guide helpful:

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

This is also in mono CVS, part of the monkeyguide module, in Monodoc,
and occasionally available as part of the go-mono.com documentation
(depending on whether mono's ASP.NET likes my document on a given day).

> What are the limites(inheritence,....) of redefining C+ classes in C#
> and call C++ code with the redefined classes?

Avoid virtual functions, multiple inheritance, virtual inheritance,
templates...

In short, you'd be crippling your C++ code.

There isn't much reason to write C++ code if you want to make it easy to
invoke from managed code.  Any methods you use would need to be wrapped
with `extern "C"' versions, increasing code size.  To maintain the same
structural layout rules, you can't use virtual functions or anything
else that would change the layout rules to any extent, and while you
could use inheritance, it would look weird from managed code: you'd
either have lots of slicing going on, or you wouldn't be able to
represent the inheritance hierarchy used in C++.

If you want to use C++, there are only two good solutions: (1) Use
Managed C++, in whatever form that may be (Managed Extensions to C++, or
the new Managed C++ ECMA standard that's being worked on).  Downside:
only works on .NET for now, may never be supported on non-.NET
platforms.

(2) Write a C "firewall" between C++ and managed code.  DO NOT depend on
structural equivalence, but use pointers (System.IntPtr) everywhere. 
This allows you to take advantage of virtual functions and (multiple)
inheritance in C++, and still invoke the C++ code from managed code. 
This is what Qt# did.

Example:
	// C++ code
	class Base {
		std::string _name;
	public:
		Base (const std::string& name) : _name (name) {}
		virtual std::string name () const {return _name;}
	};

	class Derived : public Base {
	public:
		Derived () : Base ("") {}
		virtual std::string name () const {return "Derived!";}
	};

	// C interface
	extern "C" Base* Base_New (const char* name)
		{return new Base (name);}

	extern "C" char* Base_getName (Base* b)
		{std::string n = b->name ();
		// Note: malloc or CoTaskMemAlloc depends on platform.
		char* r = (char*) malloc (n.length()+1);
		strcpy (r, n.c_str());
		return r;}

	extern "C" Derived* Derived_New ()
		{return new Derived ();}

	// C# code
	public class MyLib {
		[DllImport]
		public static extern IntPtr Base_New (string s);
		[DllImport]
		public static extern string Base_getName (IntPtr b);
		[DllImport]
		public static extern IntPtr Derived_New ();

		public static void Main () {
			IntPtr derived = Derived_New ();
			string r = Base_getName (derived);
			Console.WriteLine (r);	// prints "Derived";
		}
	}


SWIG (http://www.swig.org) can be used to generate the C and C#
bindings.

 - Jon