Inside C# 4 Data Structs
Written by Mike James   
Tuesday, 07 September 2010
Article Index
Inside C# 4 Data Structs
Being exact
The API
Serialising structs
Manual mashaling
Struct-to-pointer

Banner

Manual Marshaling

Marshalling works so well most of the time that there is a tendency to forget that it is doing anything at all. However, as soon as you hit something even slightly out of the ordinary you might be surprised at what happens when it stops working.

For example, some API calls need you to pass a pointer to a pointer to struct. You already know how to pass a pointer to a struct – it’s just pass by ref - and this might lead you to believe that a simple modification will allow you to pass a pointer to that pointer.

But things are more complicated than you might expect. Let’s look at this a step at a time.

In the AVIFileCreateStream API call the last two parameters are passed as pointers to an IntPtr and a struct respectively:

 

[DllImport("avifil32.dll")]
extern static int AVIFileCreateStream(
IntPtr pfile,
ref IntPtr pavi,
ref AVISTREAMINFO lParam);

To use this API call you would use:

result = AVIFileCreateStream(pFile,
ref pStream, ref Sinfo);

At this point, given our earlier examples, it would appear easy to take over the marshaling of the pointer to the struct and do it manually.

For example, what could be wrong with changing the declaration to:

[DllImport("avifil32.dll")]
extern static int AVIFileCreateStream(
IntPtr pfile,
ref IntPtr pavi,
IntPtr lParam);

However, if you try to use it by passing the address of the pinned structure:

GCHandle handle = GCHandle.Alloc(Sinfo,
GCHandleType.Pinned);
result = AVIFileCreateStream(pFile,
ref pStream,
handle.AddrOfPinnedObject());
handle.Free();

the result is a runtime error as shown in Figure 2.

struct2


Protected memory runtime error

 

The reason is that while you are indeed passing a pointer to the start of the stuct, that struct is in managed memory and unmanaged code cannot access it without generating a protection error.

What we are forgetting is that standard marshalling does much more for us than generate addresses to use as pointers. The default marshalling for all parameters passed by ref also makes a copy of the entire data in unmanaged memory before deriving a pointer. It then copies the unmanaged memory back to the managed type when the function ends.

It isn’t difficult, and is indeed quite useful, to write a function that does the same job as default marshalling:

private IntPtr MarshalToPointer(
object data)
{
IntPtr buf = Marshal.AllocHGlobal(
Marshal.SizeOf(data));
Marshal.StructureToPtr(data,
buf, false);
return buf;
}

This simply returns an IntPtr to an area of the global heap that contains a copy of the data. The only problem with this function is that you have to remember to release the allocated heap memory after use. For example:

IntPtr lpstruct = MarshalToPointer(Sinfo);
result = AVIFileCreateStream(pFile,
ref pStream,lpstruct);
Marshal.FreeHGlobal(lpstruct);

works exactly like default marshalling.

But don’t forget that lpstruct is itself still being marshalled as a pass-by-value integer. To copy the result back to the struct an additional function is required:

private object MarshalToStruct(
IntPtr buf,Type t)
{
return Marshal.PtrToStructure(buf, t);
}


Banner

<ASIN:1449380344>

<ASIN:0123745144>

<ASIN:0321658701>

<ASIN:0321741765>

<ASIN:0470495995>

<ASIN:0672330792>

<ASIN:0521114292>



Last Updated ( Tuesday, 28 September 2010 )