Custom Marshaling – Part 2

In Part 1 I posted a sort of “set up” for today’s post. I presented a data structure (WAVEFORMATEX) that doesn’t fit into the “automatic” marshaling paradigm due to it’s dynamic size. The solution is to create a custom marshaling class that implements ICustomMarshaler and perform the marshaling from managed to native (and vice versa) yourself. There is a bit of good news, in this particular instance. Last time I showed the example of the ADPCMWAVEFORMAT struct which extends WAVEFORMATEX, but the good news is that any extension to the WAVEFORMATEX struct can simply be represented in memory by the WAVEFORMATEX struct plus cbSize number of bytes of data. This typically applies when reading the struct out of a file (and you don’t want to test the codec Id against every possibility), since when writing to a file you will typically know what format you’re writing and can deal with it in whatever way you see fit.

The first thing to know about creating a custom marshaler is that your class must declare a static method called GetInstance that returns an instance of the class. I find it officially “not-cool” that the only way to know you need to do this is by reading the docs (maybe that tells you more about me than the API), but it basically implements the singleton pattern allowing you full control over the number of objects created. I’m not clear yet what the cookie parameter specifies–so far it’s always been an empty string in my testing. At any rate, here is a stubbed out class that implements ICustomMarshaler, and one at a time I’ll go through the required methods:

public sealed class WAVEFORMATEXMarshaler : ICustomMarshaler
{
   private static WAVEFORMATEXMarshaler marshaler = null;
   public static ICustomMarshaler GetInstance(string cookie)
   {
       if (marshaler == null)
       {
              marshaler = new WAVEFORMATEXMarshaler();
       }
       return marshaler;
   }
   #region ICustomMarshaler Members

   public object MarshalNativeToManaged(System.IntPtr pNativeData)
   {
   }

   public System.IntPtr MarshalManagedToNative(object ManagedObj)
   {
   }

   public void CleanUpManagedData(object ManagedObj)
   {
   }

   public int GetNativeDataSize()
   {
   }

   public void CleanUpNativeData(System.IntPtr pNativeData)
   {
   }

   #endregion
}

I’ll start with the easy ones. Note that in the code below, I have a WAVEFORMATEX_tag struct that is wrapped by a WAVEFORMATEX class that provides friendly properties and helper methods, etc.
In my case, an implementation for CleanUpManagedData is not required since there isn’t any managed data that requires cleaning up. GetNativeDataSize() isn’t actually called by the runtime (that I can determine), but just in case it is, I return the size of a non-extended (PCM) WAVEFORMATEX structure. GetNativeDataSize(WAVEFORMATEX fmt) is a helper method that I added to calculate the amount of native memory required to allocate. Finally, CleanUpNativeData needs to free any memory allocated during marshaling.

public void CleanUpManagedData(object ManagedObj)
{
   // No action required
}

public int GetNativeDataSize(WAVEFORMATEX fmt)
{
   int size = Marshal.SizeOf(typeof(WAVEFORMATEX_tag));

   // Calculate the unmanaged size of the struct
   size += fmt.Size;
   // Add the size of the extra data
   return size;
}

public int GetNativeDataSize()
{
   // Assume a standard WAVEFORMATEX (no extra bytes)
   return Marshal.SizeOf(typeof(WAVEFORMATEX_tag));
   // Calculate the unmanaged size of the struct
}

public void CleanUpNativeData(System.IntPtr pNativeData)
{
   Marshal.FreeHGlobal(pNativeData);
}

Getting to the real meat of custom marshaling, we have the two methods which do the actual work: MarshalNativeToManaged and MarshalManagedToNative. If you have trouble figuring out what each one is intended to do, uh, you might want to reconsider a few things. Anyway (ahem), MarshalNativeToManaged supplies you with an IntPtr which points to the native data structure. Whenever we’re marshaling, we always want to take advantage of any built-in helper methods we can (in this case, Marshal.PtrToStructure). After pulling out the WAVEFORMATEX structure, we need to pull out any extra data, such as the coefficient information in ADPCMWAVEFORMAT. The implementation of this method isn’t rocket science, so I’ll leave it to you to interpret it. MarshalManagedToNative involves a little more complexity-only because we have to allocate memory on the native heap before we can write to it. Once we’ve allocated the memory, we can use the helper methods to marshal the struct, then manually add the additional data at the end. Again, this ain’t rocket science.

public object MarshalNativeToManaged(System.IntPtr pNativeData)
{
   WAVEFORMATEX wfx = new WAVEFORMATEX();
   wfx.waveFormat = (WAVEFORMATEX_tag)Marshal.PtrToStructure(pNativeData, typeof(WAVEFORMATEX_tag));
   // If there is extra data, marshal it
   if (wfx.Size > 0)
   {
       // Move pointer forward
       pNativeData = new IntPtr(pNativeData.ToInt32() + Marshal.SizeOf(typeof(WAVEFORMATEX_tag)));
       // Read extra data
       wfx.data = new byte[wfx.Size];
       Marshal.Copy(pNativeData, wfx.data, 0, wfx.Size);
   }
   return wfx;
}

public System.IntPtr MarshalManagedToNative(object ManagedObj)
{
   WAVEFORMATEX wfx = null;
   if (!(ManagedObj is WAVEFORMATEX))
   {
       throw new ArgumentException("Specified object is not a WAVEFORMATEX object.", "ManagedObj");
   }
   else
   {
       wfx = (WAVEFORMATEX)ManagedObj;
   }
   IntPtr ptr = Marshal.AllocHGlobal(this.GetNativeDataSize(wfx));
   if (ptr == IntPtr.Zero)
   {
       throw new Exception("Unable to allocate memory to marshal ADPCMWAVEFORMAT.");
   }
   // Write out WAVEFORMATEX structure
   Marshal.StructureToPtr(wfx.waveFormat, ptr, false);
   // Write extra data (move the ptr "up")
   if (wfx.Size > 0)
   {
       IntPtr dataPtr = new IntPtr(ptr.ToInt32() + Marshal.SizeOf(typeof(WAVEFORMATEX_tag)));
       Marshal.Copy(wfx.data, 0, dataPtr, Math.Min(wfx.Size, wfx.data.Length));
   }
   return ptr;
}

Finally, we come to the method prototype which uses the MarshalAs attribute to associate our custom marshaling class with specific method parameters (if we don’t do this, then why the heck did we write a custom marshaler??). First we specify that we are using a custom marshaler (UnmanagedType.CustomMarshaler), and then we provide the class name of our marshaling class. Easy, no?

[DllImport("Msacm32.dll")]
public static extern uint acmStreamOpen(ref IntPtr phas,
        int had,
        [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(WAVEFORMATEXMarshaler))] WAVEFORMATEX pwfxSrc,
        [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(WAVEFORMATEXMarshaler))] WAVEFORMATEX pwfxDst,
        IntPtr pwfltr,
        IntPtr dwCallback,
        IntPtr dwInstance,
        uint fdwOpen);

That’s all she wrote, folks! It’s really not that difficult at all, but it took a while to find the information to guide me on this. That’s why I’m posting this here-hopefully it can help someone else!

As always, if anybody has any comments, please leave them-I love feedback!

This entry was posted on Thursday, July 6th, 2006 at 12:49 am and is filed under programming. You can follow any responses to this entry through the RSS 2.0 feed. Both comments and pings are currently closed.

4 Responses to “Custom Marshaling – Part 2”

  1. Sallac Says:

    Hi Aaaron,

    thanks a lot for your blog. It is really hard to find information about the ICustionMarshaler and i really appreciate your help!

    Thanks!

  2. SJR Says:

    Hi Aaron

    Thanks for the post. I have a question and a comment.

    Question: How do you make this work for a returned struct?

    Comment: The link to part 1 of this post seems to be broken. I would love to see it.

  3. Diogo Pinheiro Says:

    Hello,

    Can you help me with a CustomMarshaler?
    I would be apreciated.

    I need to create a CustomMarshaler for the type MPR_SERVER_HANDLE.

    The function I’m using:

    public static extern HandledEventArgs MprAdminServerConnect
    (
    [In][MarshalAs(UnmanagedType.LPWStr)] string lpwsServerName,
    [Out][MarshalAs(UnmanagedType.CustomMarshaler)] string phMprServer
    );

    The CustomMarshaler is for the parameter phMprServer, and the type I need is MPR_SERVER_HANDLE.
    I’m quite new to c#, thats why even with your examples i’m not able do it. Can you help me with this ?

    Many thanks

  4. James Says:

    Summary
    MarshalCookie
    A String that is used with System.Runtime.InteropServices.UnmanagedType.CustomMarshaler to hold a cookie that is passed to the custom marshaler. The value of the cookie is defined by the custom marshaler implementation.