Home > .NET and the CLR, COM > How to create and use a Custom COM Marshaler in .NET

How to create and use a Custom COM Marshaler in .NET

Microsoft has done a nice job when it designed Component Object Model, also known as COM, even if  the system’s complexity was painful for some developers. To fix that, they first created Vb6 (well…no comment) to soften the burden of consuming COM objects, while C++ was still a little bit rough to use (but anything concerning COM was possible !). After a few years, they finally created the .NET framework. As a fair part of the Windows architecture was based on COM and COM+, they had to make the most popular language (C# and VB.NET) of the platform compatible with it. I’m currently working on a system where the .NET code use a lot of COM to communicate with other parts of the system, and I can tell you that even most of the features of COM can be used, sometimes you have to dig deeper to make .NET component compatible with other languages. I saw a few custom marshaler described on the net, but no one done to marshal an array of “anything” (called VARIANT in COM), so I thought it could be interesting to share it with others.

What is a COM marshaler ?

As COM was designed to communicate between codes coming from different language, something has to do the job of converting the bits of every parameter of every function from one language standard to the other. By instance, if a C++ code, where a string is a null-terminated array of char, wants to call a Vb6 function, where a string is a BSTR (also called Pascal string, with the number of char written in the first four bytes), there must be an allocation and an intelligent copy to create one from the other. This is basically what a COM marshaler do, and the default one provided by .NET is enough for most of the usage.

Well, if the default marshaler is ok, why do I have to create another one by myself?

Sometime you want very special processing of your functions arguments. It happened to me with a function passing an array of VARIANT, used to store the arguments of a stored procedure. The core of the issue is that the default marshaler will translate an array of VARIANT into an array of object, and you have more type information with a VARIANT. Why? Because in a VARIANT, you can make the difference between a null string and a null object (the type and the value is stored in different places), and you cannot do this distinction with an object (if an object is null, you lose all type information). Sadly, I had to process in a different ways the null objects and the null string arguments of my stored procedure, so I was stuck: did the ‘null’ that I received was formerly a COM object or a string?

I also had to change the default date (use the Vb6 default date instead of the .NET default one, because every language in the world use different ‘zero’ dates…).

Now show me this custom marshaler !

Here is the code: I commented it fully, so I think further explanation is unnecessary. Sorry for this looooooooong piece of code, but at least all is here and you just have to copy/paste it into some C# file.

class VariantArrayCustomMarshaller : ICustomMarshaler
{
    // so nobody can create a new instance
    private VariantArrayCustomMarshaller()
    {
    }

    // unique instance that will be used ; multi-threading use may require proper protection of multiple instanciation
    static readonly VariantArrayCustomMarshaller _marshaler = new VariantArrayCustomMarshaller();

    // a SAFEARRAY bound layout
    [StructLayout(LayoutKind.Sequential, Pack = 4)]
    public struct SafeArrayBound
    {
        [ComAliasName("Microsoft.VisualStudio.OLE.Interop.ULONG")]
        public uint cElements;
        [ComAliasName("Microsoft.VisualStudio.OLE.Interop.LONG")]
        public int lLbound;
    }

    #region ICustomMarshaler members

    public object MarshalNativeToManaged(IntPtr pNativeData)
    {
        if (pNativeData == IntPtr.Zero)
            return null;

        // check that dimension number is one (this marshaller does not manage multi-dimensionnal SAFEARRAYs)
        if (SafeArrayGetDim(pNativeData) != 1)
            throw new ArgumentException("pNativeData must point to a SAFEARRAY with exactly one dimension", "pNativeData");

        // get lower bound
        long lBound = 0;
        int hr = SafeArrayGetLBound(pNativeData, 1, ref lBound);
        if (hr != 0)
            throw Marshal.GetExceptionForHR(hr);

        // check that lower bound is 0 (this marshaller does not manage non 0 lower bound)
        if(lBound != 0)
            throw new ArgumentException("pNativeData must point to a SAFEARRAY with one dimension starting at 0", "pNativeData");

        // get upper bound
        long uBound = 0;
        hr = SafeArrayGetUBound(pNativeData, 1, ref uBound);
        if (hr != 0)
            throw Marshal.GetExceptionForHR(hr);

        // get the memory size of a single element
        uint elementSize = SafeArrayGetElemsize(pNativeData);
        if (elementSize == 0)
            throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());

        // access (and lock) data
        IntPtr data = IntPtr.Zero;
        hr = SafeArrayAccessData(pNativeData, ref data);
        if (hr != 0)
            throw Marshal.GetExceptionForHR(hr);

        try
        {
            // use the handy System.Runtime.InteropServices.Marshal class
            object[] returned = Marshal.GetObjectsForNativeVariants(data, (int)(uBound + 1));

            // From now the returned object is good
            // I add my personnal need : I want to translate NULL strings to empty strings, and NULL dates to default
            // value of OLE dates

            for (int i = 0; i <= uBound; ++i)
            {
                // get the address of the element
                IntPtr ptr = new IntPtr(data.ToInt32() + elementSize * i);

                // read the first 16 bits, as the VAR_TYPE is on two bytes
                VarEnum ve = (VarEnum)Marshal.ReadInt16(ptr);

                // process
                if (ve == VarEnum.VT_BSTR && returned[i] == null)
                {
                    returned[i] = String.Empty;
                }
                else if (ve == VarEnum.VT_DATE && returned[i] == null)
                {
                    returned[i] = DateTime.FromOADate(0);
                }
            }

            return returned;
        }
        finally
        {
            // SafeArrayAccessData locks the array
            SafeArrayUnaccessData(pNativeData);
        }
    }

    // data returned by MarshalManagedToNative, so I will be able to clean it in CleanUpNativeData
    private IntPtr _safeArrayPtr;
    public IntPtr MarshalManagedToNative(object managedObj)
    {
        if( managedObj == null)
            return IntPtr.Zero;

        if (!(managedObj is object[]))
            throw new ArgumentException("Input must be of object[] type", "managedObj");

        object[] realObject = (object[]) managedObj;

        try
        {
            // Create the SAFEARRAY

            // create my unique dimension
            SafeArrayBound sab;
            sab.lLbound = 0;
            sab.cElements = (uint)realObject.Length;

            IntPtr psab = IntPtr.Zero;
            try
            {
                // Performances would be better with a stackalloc, but then I would have to declare the method unsafe
                psab = Marshal.AllocHGlobal(Marshal.SizeOf(sab));
                Marshal.StructureToPtr(sab, psab, false);

                // the real SAFEARRAY creation
                _safeArrayPtr = SafeArrayCreate((ushort) VarEnum.VT_VARIANT, 1, psab);
                if (_safeArrayPtr == IntPtr.Zero)
                    throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());
            }
            finally
            {
                // SafeArrayCreate create a copy of the SafeArrayBound memory, so it is safe and recommended to free it here
                if (psab != IntPtr.Zero)
                    Marshal.FreeHGlobal(psab);
            }

            IntPtr data = IntPtr.Zero;

            // I use SafeArrayAccessData instead of SafeArrayPutElement to avoid one allocation for each Variant
            int hr = SafeArrayAccessData(_safeArrayPtr, ref data);
            if (hr != 0)
                throw Marshal.GetExceptionForHR(hr);

            try
            {
                // get the memory size of a single element
                uint elementSize = SafeArrayGetElemsize(_safeArrayPtr);
                if(elementSize == 0)
                    throw Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error());

                // use the handy System.Runtime.InteropServices.Marshal class for each element
                for (int i = 0; i < realObject.Length; ++i)
                {
                    // get the address of the element
                    IntPtr ptr = new IntPtr(data.ToInt32() + elementSize*i);

                    Marshal.GetNativeVariantForObject(realObject[i], ptr);
                }
            }
            finally
            {
                // SafeArrayAccessData locks the array
                SafeArrayUnaccessData(_safeArrayPtr);
            }

            return _safeArrayPtr;

        }
        catch(Exception)
        {
            CleanUpNativeData(_safeArrayPtr);
            _safeArrayPtr = IntPtr.Zero;
            throw;
        }
    }

    // Clean the data returned by MarshalManagedToNative
    public void CleanUpNativeData(IntPtr pNativeData)
    {
        if (pNativeData != IntPtr.Zero)
        {
            int hr = SafeArrayDestroy(pNativeData);
            if (hr != 0)
                throw Marshal.GetExceptionForHR(hr);
        }
    }

    // Managed datas will be cleaned by the garbage collector
    public void CleanUpManagedData(object managedObj)
    {
    }

    // As my marshaller manage only a reference value
    public int GetNativeDataSize()
    {
        return -1;
    }

    // return the static instance
    public static ICustomMarshaler GetInstance(string cookie)
    {
        return _marshaler;
    }

    #endregion

    #region pInvoke stuff. For your personnal project, it is better to put it on a separate class

    [DllImport("oleaut32.dll", EntryPoint = "SafeArrayCreate", SetLastError = true, CharSet = CharSet.Auto,
        ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
    static extern IntPtr SafeArrayCreate(ushort vt, uint cDims, IntPtr rgsabound);

    [DllImport("oleaut32.dll", EntryPoint = "SafeArrayDestroy", SetLastError = true, CharSet = CharSet.Auto,
        ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
    static extern int SafeArrayDestroy(IntPtr psa);

    [DllImport("oleaut32.dll", EntryPoint = "SafeArrayAccessData", SetLastError = true, CharSet = CharSet.Auto,
        ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
    static extern int SafeArrayAccessData(IntPtr psa, ref IntPtr ppvData);

    [DllImport("oleaut32.dll", EntryPoint = "SafeArrayUnaccessData", SetLastError = true, CharSet = CharSet.Auto,
        ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
    static extern int SafeArrayUnaccessData(IntPtr psa);

    [DllImport("oleaut32.dll", EntryPoint = "SafeArrayGetElemsize", SetLastError = true, CharSet = CharSet.Auto,
        ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
    static extern uint SafeArrayGetElemsize(IntPtr psa);

    [DllImport("oleaut32.dll", EntryPoint = "SafeArrayGetDim", SetLastError = true, CharSet = CharSet.Auto,
        ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
    static extern uint SafeArrayGetDim(IntPtr psa);

    [DllImport("oleaut32.dll", EntryPoint = "SafeArrayGetLBound", SetLastError = true, CharSet = CharSet.Auto,
        ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
    static extern int SafeArrayGetLBound(IntPtr psa, uint nDim, ref long bound);

    [DllImport("oleaut32.dll", EntryPoint = "SafeArrayGetUBound", SetLastError = true, CharSet = CharSet.Auto,
        ExactSpelling = true, CallingConvention = CallingConvention.StdCall)]
    static extern int SafeArrayGetUBound(IntPtr psa, uint nDim, ref long bound);

    #endregion
}

Of course you have to tell the system to use this marshaler in the parameter of your specific function, here it is:

[ComVisible(true)]
public void MyFunction([In, Out, MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef=typeof(VariantArrayCustomMarshaller))] ref object[] arr)       {
    // process
}

Here it is, happy marshaling !

Categories: .NET and the CLR, COM
  1. April 10, 2011 at 9:55 pm

    jl7i8u Very true! Makes a change to see someone spell it out like that. :)

  2. April 11, 2011 at 12:58 am

    LC9ekX BION I’m impressed! Cool post!

  3. Mustafa Muhammad
    January 20, 2012 at 8:05 pm

    hI Julien,
    I am very much impressed by this article. I am stuck in the same problem. I have Integrated your Custom Marshaller in my code but still I am unable to get the Variant Array. I am getting an exception at SafeArrayGetDim(), and it is basically Access Voilation ” Attempted to read or write protected memory. This is often an indication that other memory is corrupt.”.Can you please help me in this Issue.
    Best Regards
    Mustafa Muhammad

  4. January 23, 2012 at 8:48 am

    Hi Mustafa
    The argument you’re passing to the marshaller might not be a COM array. Do you have a snippet of the code that calls the function having the [CustomMarshaller] attribute ? Maybe I can find something.

  5. Mustafa Muhammad
    January 23, 2012 at 9:50 am

    Julien,
    Can you please add me on skype “mustafa23831”. I want to solve this issue as soon as possible.

    best regards

  6. January 23, 2012 at 10:54 am

    I Mustafa
    I’m currently working now so it’s a little bit difficult for me to start a skype session. I’m gonna try to be very reactive on my blog ok ? Right now I need the code source that call the DoSomething function, could you give it to me ?
    I would like to ask this on stackoverflow but sadly I don’t have enough point to ask questions.

  7. Mustafa Muhammad
    January 23, 2012 at 11:24 am

    Hi Julien,
    I thing that i want to tell you that this Custom Interface is according to “OPC Standard” . “OPC” Foundation has defined this interface. All the Clients have to make call on standard interface. Can you please send me your email address where i can send you the C# COM server code. And C++ interface file that I have implemented…

  8. Mustafa Muhammad
    January 23, 2012 at 11:26 am

    As this is custom Interface any one can make its client in c++ and call this method..

  9. jeffery
    August 3, 2013 at 2:06 am

    I have a project here that would interest you but drive you crazy at the same time:
    http://kinectmultipoint.codplex.com. When I get to queueinputreport on the vb.net code I have tried everything to marshal it but I need to marshal a safe array of variants (content is of VT_UI1 on c++ side) to c++ side. I tried object but it does not pass the c++ FADF_FIXEDSIZED and FADF_STATIC bits to c++ when passing the object variable. If you think you are good at making a variant in c# try to see if you call it. I have lync here so you can ask me questions anytime at school too: jeffery.carlson657@topper.wku.edu. that’s my school account but later I will have one for a job.

  1. No trackbacks yet.

Leave a comment