[Mono-list] Custom Marshalling

Jonathan Pryor jonpryor@vt.edu
Thu, 28 Oct 2004 22:10:28 -0400


You're making this far too difficult on yourself.  See below.

On Thu, 2004-10-28 at 19:57, Neale.Ferguson@SoftwareAG-USA.com wrote:
> I'll attempt to think before I type this time :-)
> 
> I have a a C routine I wish to call that takes the following parameters:
> 
> typedef struct XXXCB {
>     short a;
>     char  b[2];
> };
> 
> void scumbag(XXXCB *cb, char *v);

<snip/>

> In C# I'd code XXXCB as:
> 
> public struct XXXCB {
> 	public short a;
> 	[MarshalAS(UnmanagedType.ByValArray, SizeConst=2)}
> 	public byte[] b;
> }

Good declaration.

> Now, because the variable "v" can be a different length of each call
> (up to 32K is size) I can't use MarshalAs(UnmanagedType.ByValArray) as
> mcs demands that SizeConst= be coded (using SizeParamIndex= doesn't
> work, in fact I don't think it's supported).

There's a bigger problem: ByValArray doesn't mean what you think it
does.  At least, not entirely.

ByValArray means that it's a contiguous blob of memory, which is why you
used it in your XXXCB structure declaration -- you needed to specify the
size of "b".  Perfectly natural.

However, it means the *same thing* no matter where it's applied.  So
applied to a method argument, it means "push a blob of data SizeConst
bytes onto the stack").  This is NOT what C does; C always pushes a
*pointer* to an array onto the stack.  Compare these two (C-like)
declarations:

	// ByValArray equivalent
	void scumbag (XXXCB *cb, struct {char data[SOME_LENGTH];} v);

	// LPArray equivalent:
	void scumbag (XXXCB *cb, char *v);

The first one probably isn't valid C, and certainly isn't what you want
when invoking existing C code (but may be useful when dealing with other
languages).

Obviously, you don't want ByValArray for method arguments.  You want
LPArray.  Fortunately, this is the default, and marshals the number of
elements inside the array:

	[DllImport (libname)]
	private static extern void scumbag (ref XXXCB cb, byte[] v);

Usage is straight-forward:

	XXXCB cb = new XXXCB();
	scumbag (ref cb, Encoding.ASCII.GetBytes ("this is my v arg1"));
	scumbag (ref cb, Encoding.ASCII.GetBytes ("this is another v"));

Consequently, you don't need the custom marshaler.

 - Jon