you're reading...
Interop Marshaling

Example Custom Marshaler – The Array Marshaler

1. Introduction.

1.1 A very useful .NET feature is the marshaling of managed arrays to unmanaged functions by using the MarshalAsAttribute with the MarshalAsAttribute.Value set to UnmanagedType.LPArray in conjunction with using the SizeParamIndex field.

1.2 The array may even be marshaled in both directions, i.e. to unmanaged code and then back to managed with data possibly modified.

1.3 The advantage is that from the point of view of the unmanaged function, the array is accessed via a pointer. This coding construct is most familiar to C/C++ programmers. The other advantage is that the length of the array (i.e. the count of elements contained in the array) need not be fixed at compile time. It can be dynamically passed to the unmanaged function at runtime.

1.4 Listed below is a declaration of an unmanaged function with such a signature :

void __stdcall ModifyArray_WithLengthFixed(/*[in] or [in, out]*/ int* pIntArray, /*[in]*/ int iLength);
  • The function above takes a pointer to an array of integers (“pIntArray”).
  • The length of the array is set via “iLength”.

We shall study an example of calling such an unmanaged function later on.

1.5 This arrangement, however, does not address the issue of arrays the length of which may need to be changed via external APIs. Listed below is a declaration of an unmanaged function with such a requirement :

void __stdcall ModifyArray_WithLengthChanged(/*[in, out]*/ int** ppIntArray, /*[in, out]*/ int* piLength);
  • The function above takes a double pointer to an integer array (ppIntArray).
  • The idea is that array may be structurally modified in terms of element count and then returned to the caller.
  • The “piLength” parameter would contain the original length of the array on entry to the function.
  • On exit from the function, “piLength” may be set to a new length value and returned to the caller.

1.6 Currently, the only standard way to achieve this is through setting MarshalAsAttribute.Value to UnmanagedType.SafeArray. This way, the managed array is transformed into a SAFEARRAY which is a self-describing array that self-contains the count of elements and the number of dimensions.

1.7 I always encourage developers to use the SAFEARRAY option when requiring to marshal arrays that may need to change in length.

1.8 However, for developers who are not familiar with managing SAFEARRAYs and are much more accustomed to using C-style array pointers, I shall later offer an alternative technique which involves a custom marshaler.

2. Example Array Marshaling Using Standard Constructs.

2.1 In this section, we study how we can call a function like ModifyArray_WithLengthFixed() (point 1.4).

2.2 The standard way would require a DllImport() declaration in C# such as the following :

[DllImport("TestDLL.dll", EntryPoint = "ModifyArray_WithLengthFixed", CallingConvention = CallingConvention.StdCall)]
private static extern void ModifyArray_WithLengthFixed
    [In][Out][MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] Int32[] Int32Array,
    Int32 iLength
  • We specify that the first parameter be an array of Int32 data.
  • The first parameter is also decorated with the InAttribute and OutAttribute and the MarshalAsAttribute.
  • The MarshalAsAttribute is further specified with value UnmanagedType.LPArray which indicates that the Int32 array will be marshaled as a C-style pointer to an unmanaged array.
  • The SizeParamIndex field being set to 1 indicates that at runtime, the second parameter “iLength” will indicate the total count of elements in the array.

2.3 Let’s provide a concrete implementation for ModifyArray_WithLengthFixed() :

void __stdcall ModifyArray_WithLengthFixed(int* pIntArray, int iLength)
	for (int i = 0; i < iLength; i++)
		pIntArray[i] += 1;
  • The implementation simply iterates through each element of the array and increments it by one.

2.4 A sample C# call to ModifyArray_WithLengthFixed() is listed below :

static void DoTest_ModifyArray_WithLengthFixed()
    Int32[] Int32Array = new Int32[5] { 0, 1, 2, 3, 4 };

    ModifyArray_WithLengthFixed(Int32Array, Int32Array.Length);

    for (int i = 0; i < Int32Array.Length; i++)
        Console.WriteLine("{0:D}", Int32Array[i]);
  • An array of 5 Int32 values is declared and defined to be of values from 0 through 4.
  • The ModifyArray_WithLengthFixed() API is then called.
  • The new values of the array is then displayed.

2.5 The following is the output on the console as expected :

  • True enough, each value in the array has been permanently incremented by 1.

3. Example Array Marshaling Using A Custom Marshaler.

3.1 The standard constructs as demonstrated in section 2 (using standard MarshalAsAttribute value and field), would be sufficient when dealing with a fixed length array.

3.2 However, if an array had to be structurally modified such that the count of elements before an API call may differ from that after the API call, the standard constructs will not be useful.

3.3 In this section, we explore how we can successfully call a function like ModifyArray_WithLengthChanged() in C#.

3.4 Before that, let’s provide an implementation for ModifyArray_WithLengthChanged() :

void __stdcall ModifyArray_WithLengthChanged(int** ppIntArray, int* piLength)
	// Get hold of the original array length.
	int iOriginalLength = *piLength;
	// Allocate buffer for an array of integers with 10 elements more than the original.
	int* pIntArrayNew = (int*)::CoTaskMemAlloc(sizeof(int) * (iOriginalLength + 10));

	// Copy over the integers from the original array into the new array.
	memcpy(pIntArrayNew, *ppIntArray, sizeof(int) * iOriginalLength);

	// For each new element, assign increasing numbers from 100
	// through 109.
	for (int i = iOriginalLength; i < (iOriginalLength + 10); i++)
		pIntArrayNew[i] = 100 + (i - iOriginalLength);

	// Free the original array.

	// Assign ppIntArray to the new array.
	*ppIntArray = pIntArrayNew;
	// Assign a new value to *ppiLength.
	*piLength = iOriginalLength + 10;
  • This function allocates a memory buffer for a new array of integers with 10 elements more than the original.
  • The allocation is done using CoTaskMemAlloc().
  • It then copies over the integers from the original array into the new array.
  • Then for each new element, it assigns increasing numbers from 100 through 109.
  • The original array is then freed using CoTaskMemFree().
  • The first parameter “ppIntArray” which is a double pointer to an integer (i.e. a pointer to a pointer to an integer array) is then made to point to the new array (with the extra 10 elements).
  • The actual integer value stored in the memory location pointed to by the second parameter “piLength” is then set to a value which equals : iOriginalLength + 10.

3.5 By using custom marshaling and a little bit of clever trickery, an API like ModifyArray_WithLengthChanged() can be successfully invoked.

3.6 Listed below is the custom marshaler that we shall use :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;

namespace ArrayMarshalerClassLib
    public class ArrayMarshaler : ICustomMarshaler
        public object MarshalNativeToManaged(IntPtr pNativeData)
            // By now the Int32 that was passed as the holder 
            // of the length of the array has been updated
            // by the unmanaged function.
            // We now obtain its current value.
            Int32 iNewLength = (Int32)(gch.Target);
            // Now that we have the latest length data value,
            // we can release the GCHandle that has held onto it.
            pLengthPointer = IntPtr.Zero;

            // We create a new array to be returned.
            Int32[] Int32ArrayNew = new Int32[iNewLength];
            // Copy all new data from the new unmanaged array
            // to the managed array.
            Marshal.Copy(pNativeData, Int32ArrayNew, 0, iNewLength);

            // Return the managed array which has value copied
            // from the unmanaged array.
            return Int32ArrayNew;

        public IntPtr MarshalManagedToNative(object ManagedObj)
            if (ManagedObj is Int32)
                // When the managed object is an Int32,
                // it means that the data to be marshaled
                // is the array length holder.

                // We fix the Int32 data in memory.
                gch = GCHandle.Alloc(ManagedObj, GCHandleType.Pinned);
                // This is done in order to obtain
                // a pointer to it.
                pLengthPointer = gch.AddrOfPinnedObject();
                // We then return this pointer.
                return pLengthPointer;

            if (ManagedObj is Int32[])
                // When the managed object is the array of Int32s,
                // we duplicate the array in unmanaged form.

                // We allocate an unmanaged memory buffer.
                IntPtr pInt32Array = Marshal.AllocCoTaskMem(sizeof(Int32) * ((Int32[])ManagedObj).Length);
                // We then copy the data from the managed array
                // to the unmanaged one.
                Marshal.Copy((Int32[])ManagedObj, 0, pInt32Array, ((Int32[])ManagedObj).Length);
                // We then return a pointer to the unmanaged buffer.
                return pInt32Array;

            // If the managed object is neither an Int32 or Int32 array,
            // we throw an ArgumentException.
            throw new ArgumentException("The input argument is not an Int32 nor an Int32 array.");

        public void CleanUpNativeData(IntPtr pNativeData)
            if (pNativeData != pLengthPointer)

        public void CleanUpManagedData(object ManagedObj)
            // Nothing to do

        public int GetNativeDataSize()
            return -1;

        public static ICustomMarshaler GetInstance(string cookie)
            // Always return the same instance
            if (marshaler == null)
                marshaler = new ArrayMarshaler();

            return marshaler;

        static private ArrayMarshaler marshaler;
        private GCHandle gch;
        private IntPtr pLengthPointer;
  • The custom marshaler is named ArrayMarshaler.
  • It is unique in that it is intended to be used for 2 different data types.
  • It will be used to marshal an Int32 array as well as an Int32 value.
  • Hence the checks and separate implementation code for each of the expected types in the MarshalManagedToNative() method.
  • It has 2 private member data of type GCHandle and IntPtr.

We shall study in greater depth how ArrayMarshaler works later on.

3.7 With reference to ArrayMarshaler, listed below is how we would DllImport declare the ModifyArray_WithLengthChanged() function in C# code :

[DllImport("TestDLL.dll", EntryPoint = "ModifyArray_WithLengthChanged", CallingConvention = CallingConvention.StdCall)]
private static extern void ModifyArray_WithLengthChanged
    [In][Out][MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ArrayMarshaler))] ref Int32[] Int32Array,
    [In][MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ArrayMarshaler))] object iLength
  • Besides the fact that we have declared that the array parameter “Int32Array” will be marshaled by a custom marshaler named ArrayMarshaler, notice also that we have also declared it as being passed by reference (the “ref” keyword).
  • This is important because it means that from the perspective of the CLR, in whatever form the managed array will be presented to the unmanaged function, a pointer to this presentation will be passed to the unmanaged function.
  • In our specific case, the ArrayMarshaler, through the return value from its MarshalManagedToNative() method, will ensure that the managed array will be presented as a pointer to a C-style contiguous series of integers.
  • This practically means that the CLR will pass a pointer to a pointer to the contiguous series of integers to the unmanaged code.
  • This is, of course, exactly what the unmanaged ModifyArray_WithLengthChanged() function expects as first parameter (see point 3.4).
  • Now, the second parameter, “iLength”, is declared as also being marshaled by an ArrayMarshaler.
  • Furthermore, it is declared as being of object type.
  • The significance of this is that while processing the “iLength” parameter, the CLR will pass whatever is returned from the MarshalManagedToNative() method to the unmanaged API as is.
  • Since MarshalManagedToNative() will always return an IntPtr, the ModifyArray_WithLengthChanged() function can expect a pointer to something. And in this case, the something is the integer that directly derives from “iLength”.
  • This is exactly what the ModifyArray_WithLengthChanged() function expects as second parameter.
  • More comments on this second parameter will be discussed later.

3.8 A sample C# code to call ModifyArray_WithLengthChanged() is listed below :

static void DoTest_ModifyArray_WithLengthChanged()
    Int32[] Int32Array = new Int32[5] { 0, 1, 2, 3, 4 };
    Int32 iLength = Int32Array.Length;
    object objLength = (object)iLength;

    ModifyArray_WithLengthChanged(ref Int32Array, objLength);

    iLength = (Int32)objLength;

    for (int i = 0; i < iLength; i++)
        Console.WriteLine("{0:D}", Int32Array[i]);
  • A C# array of 5 integers is declared and defined to be the numbers 0 through 4.
  • The length of this array (i.e. 5) is initially stored in “iLength”.
  • “iLength” is then boxed as an object “objLength” in order to later be passed as a parameter to the ModifyArray_WithLengthChanged() function.
  • This boxing is necessary because the .NET Framework still does not support the custom marshaling of value types (e.g. Int32). All value types must be boxed into an object in order to be custom marshaled.
  • The ModifyArray_WithLengthChanged() function is then called.
  • Once it returns, a new array length will be set and “iLength” will be updated.
  • The elements of the new updated array will be displayed on the console.

3.9 Let us now study in depth how ArrayMarshaler performs its task of marshaling the array and the array length holder.

  • As the ModifyArray_WithLengthChanged() function is called, ArrayMarshaler::GetInstance() will be called by the CLR in order to obtain an instance of ArrayMarshaler.
  • Next the ArrayMarshaler::MarshalManagedToNative() will be called to transform a managed parameter into an unmanaged data that will be passed to the unmanaged function.
  • Now, because the ModifyArray_WithLengthChanged() function has been declared to use __stdcall, the parameters will be passed from the right to the left.
  • This means that “objLength” will be passed first. Followed by “Int32Array”.
  • Inside MarshalManagedToNative(), a check is made to see if “ManagedObj” is of type Int32.
  • If so, it indicates that the current “ManagedObj” derived from “objLength”, the array length holder.
  • We first assign a new GCHandle instance to fix this Int32 data in memory in order to obtain a pointer to it (through the use of GCHandle::AddrOfPinnedObject()).
  • This effectively means that a pointer to “objLength” is passed as (second) parameter which is perfect for our cause.
  • The GCHandle is stored in an ArrayMarshaler member variable “gch”. This member will be helpful later when we need to get the updated value of “objLength” at the end of the unmanaged function call.
  • Note also that the address of “objLength” must be stored in an ArrayMarshaler member variable “pLengthPointer”. This member will be helpful later when CleanUpNativeData() is called.
  • When MarshalManagedToNative() completes its processing of the “objLength” parameter, MarshalManagedToNative() is called again, this time for processing the “Int32Array” parameter.
  • Notice that this time, ArrayMarshaler::GetInstance() will not be called again. That is, the same ArrayMarshaler is re-used to process the first parameter. This is exactly what we want.
  • An unmanaged memory buffer is created with size that will fit the current count of Int32 elements from the “Int32Array” array.
  • The memory buffer is allocated using Marshal.AllocCoTaskMem().
  • Then all Int32 elements from the “Int32Array” array is copied to the unmanaged memory buffer.
  • A pointer to this unmanaged buffer is then returned.
  • This is where the CLR comes in to add an additional level of indirection. It takes this pointer and copies it into another buffer. Then a pointer to this buffer (which contains the address of the unmanaged array) will be passed to the ModifyArray_WithLengthChanged() API.
  • Eventually, what ModifyArray_WithLengthChanged() receives is a double pointer to the unmanaged array.
  • The ModifyArray_WithLengthChanged() function allocates a new array buffer, copies the data from the original array into it, adds new elements to the array and then frees the original array.
  • Then, when it assigns a new value (the count of elements of the new array) to the length holder, it does so using the pointer to this holder.
  • This effectively updates the “objLength” managed integer.
  • When the unmanaged API returns, ArrayMarshaler::CleanUpNativeData() will be called to perform any required resource clearance associated with each parameter.
  • We need to be concerned over which native data is currently being cleaned up.
  • If the native data is associated with the length holder “objLength”, we must not do anything. We must specifically not call GCHandle::Free() on the “gch” member. This is because we will require “gch” later when MarshalNativeToManaged() is called.
  • If the native data is associated with the unmanaged array returned from the ModifyArray_WithLengthChanged() API, we must free it using Marshal.FreeCoTaskMem().
  • However, before this specific cleanup is activated, the CLR will first call MarshalNativeToManaged() in order to transform the returned array into a managed array.
  • This sequence is certainly fortuitous but it is also an inevitably logical one since only after the “Int32Array” parameter has been properly marshaled “back” to managed code will its cleanup be necessary.
  • When MarshalNativeToManaged() is called in association with the returned “out” unmanaged array parameter, the important task of reconstructing a managed array from the unmanaged one will need to be done.
  • This is achieved by creating a new managed array with a new length.
  • Here is where the “gch” member comes in very handy. It points to the address of the original length holder “objLength” which has now been updated with a new value.
  • We access “objLength” by using GCHandle::Target.
  • We can now free the GCHandle associated with “objLength”. The “pLengthPointer” member is also now deemed obsolete and we set it to a zero value.
  • The entire elements of the new unmanaged array is copied to the new managed array using Marshal::Copy(). The new array is then returned.
  • CleanUpNativeData() is then called in sequence to free the returned unmanaged array.
  • Control is then returned to the DoTest_ModifyArray_WithLengthChanged() C# function and the contents of the new array is displayed :
  • True enough, 10 additional elements are added to the array and they are values ranging from 100 through 109.
  • It should also be noted that “objLength” now has a different value from before the ModifyArray_WithLengthChanged() call.
  • This despite the fact that it was passed as an “in” parameter.

3.10 In summary, the ArrayMarshaler uses several tricks to achieve its end :

  • The use of one single custom marshaler for 2 parameters of different types.
  • The combined use of the custom marshaler and the GCHandle to treat an “in” only parameter as “in” and “out”.

These tricks allowed the ArrayMarshaler to :

  • Access the updated array length information when it has to create a new array to be returned to the caller (see the code for MarshalNativeToManaged()).
  • Update the array length object at the end of the unmanaged function call.

4. In Conclusion.

4.1 With some additional testing and research, we can even enhance the ArrayMarshaler with templates to make it generic across various types.

4.2 With this latest example code, I hope the reader will have an even greater appreciation for custom marshaling.

4.3 I hope to publish more examples in the future.

About Lim Bio Liong

I've been in software development for nearly 20 years specializing in C , COM and C#. It's truly an exicting time we live in, with so much resources at our disposal to gain and share knowledge. I hope my blog will serve a small part in this global knowledge sharing network. For many years now I've been deeply involved with C development work. However since circa 2010, my current work has required me to use more and more on C# with a particular focus on COM interop. I've also written several articles for CodeProject. However, in recent years I've concentrated my time more on helping others in the MSDN forums. Please feel free to leave a comment whenever you have any constructive criticism over any of my blog posts.


8 thoughts on “Example Custom Marshaler – The Array Marshaler

  1. Suppose you wanted to pass in 2 Int32Arrays, one array has data in it and you process that data ( maybe add 5 to each element in the array ) and then copy that to the second array and return that. Something like:
    [DllImport(“TestDLL.dll”, EntryPoint = “ModifyArray_WithLengthChanged2”, CallingConvention = CallingConvention.StdCall)]
    private static extern void ModifyArray_WithLengthChanged2
    [In][MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ArrayMarshaler))] Int32[] Int32Array,
    [In][Out] [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ArrayMarshaler))] ref Int32[] Int32ArrayOut,
    [In][MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(ArrayMarshaler))] object iLength);

    Would both arrays be specified as [In][Out] or just the array you plan on returning?

    Thanks for the great example.

    Posted by Kathy Lori | December 4, 2013, 8:22 pm
    • Hello Kathy,

      1. In your particular case, I would recommend that you use the standard MarshalAsAttribute constructs to achieve your aim. This is because both the referral array (the one that does not change in element values) and the receiving array are of fixed length.

      2. I assume for the moment that your C++ API is something like the following :

      void __stdcall TransformArrayValues
      /*[in]*/ int* pReferralIntArray,
      /*[in]*/ int iReferralArrayLength,
      /*[out]*/ int* pReceivingIntArray
      // Copy over the integers from the original array into the new array.
      memcpy(pReceivingIntArray, pReferralIntArray, sizeof(int) * iReferralArrayLength);

      // Iterate through each element in the receiving array
      // and add 5 to it.
      for (int i = 0; i < iReferralArrayLength; i++)
      pReceivingIntArray[i] += 5;

      3. You can DllImport declare such an API in C# as follows :

      [DllImport("TestDLL.dll", EntryPoint = "TransformArrayValues", CallingConvention = CallingConvention.StdCall)]
      private static extern void TransformArrayValues
      [In][MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] Int32[] Int32ArrayReferral,
      [In] Int32 iLength,
      [Out][MarshalAs(UnmanagedType.LPArray, SizeParamIndex=1)] Int32[] Int32ArrayReceiver

      4. The following is a C# function that you can use for testing :

      static void DoTest_TransformArrayValues()
      Int32[] Int32ArrayReferral = new Int32[5] { 0, 1, 2, 3, 4 };
      Int32[] Int32ArrayReceiver = new Int32[5];
      Int32 iLength = Int32ArrayReferral.Length;

      TransformArrayValues(Int32ArrayReferral, iLength, Int32ArrayReceiver);

      for (int i = 0; i < iLength; i++)
      Console.WriteLine("{0:D}", Int32ArrayReceiver[i]);

      5. You would only need to use the ArrayMarshaler if the length of the receiving array is only known after the API returned.

      6. Please let me know if I have understood you correctly.

      – Bio.

      Posted by Lim Bio Liong | December 5, 2013, 4:09 am
      • The c++ signature for my function is :
        DoCalcs(double** input, double** output);
        On the C# side, I’m allocating the arrays ( which are pretty big, about 1900 values ). The DoCalcs takes in the input array, does some processing, and returns the processed data in the output array. Right now, I’m cheating by having the array as a filestream that the c++ opens, reads, does the calculations, and writes the answers to another file. Since this will only be used internally, it’s not as big a deal, but I do want to learn how to do this correctly.
        I do so appreciate your help. I’m a c# person, and I haven’t had to deal with C++ a lot since 2003.

        Posted by Kathy Lori | December 5, 2013, 4:11 pm
  2. Bio, in today’s world why do you need to know this stuff ( although I do find it very interesting ) ?

    Posted by Pete Kane | December 4, 2013, 9:14 pm
    • Hello Pete,

      1. In short : compatibility and re-usability.

      2. There are many C++ functions that work only with array pointers and that returns variable length arrays the length of which will only be known at runtime.

      3. It is probable that C++ developers will continue to use array pointers (for “in” and “out” parameters) in the future.

      – Bio.

      Posted by Lim Bio Liong | December 5, 2013, 4:14 am
  3. Hello Kathy,

    1. Write your C++ DoCalcs() function as follows :

    DoCalcs(double* input, double* output);

    That is, “input” and “output” need not be double pointers. This is because both arrays originate from the caller (your C# program) and the DoCalcs() function does not allocate the memory for “output”.

    2. Then, since the size of the arrays is fixed and must be known by the DoCalcs() function, declare DoCalcs() in C# as follows :

    [DllImport(“MyDLL.dll”, EntryPoint = “DoCalcs”)]
    private static extern void DoCalcs
    [In][MarshalAs(UnmanagedType.LPArray, SizeConst = 1900 )] Double[] InputArray,
    [Out][MarshalAs(UnmanagedType.LPArray, SizeConst = 1900 )] Double[] OutputArray

    – Bio.

    Posted by Lim Bio Liong | December 5, 2013, 4:35 pm
    • Unfortunately, I can’t change the signature of that particular C++ function, it was written by someone else.

      Posted by Kathy Lori | December 5, 2013, 4:48 pm
      • Hello Kathy,

        1. It is unfortunate that DoCalcs() is defined that way.

        2. In this case, it would not be possible to use the MarshalAsAttribute with UnmanagedType.LPArray and SizeConst.

        3. I suggest that you experiment with the ArrayMarshaler (leaving out the code for marshaling the Int32 type). Code it such that the length of the input and output arrays is fixed (you can also try to use the MarshalCookie to specify this).

        – Bio.

        Posted by Lim Bio Liong | December 5, 2013, 5:25 pm

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: