//
you're reading...
Interop Marshaling

Example Custom Marshaler – The Long Marshaler

1. Introduction.

1.1 I wish there is a survey on how many developers actually develop custom marshalers.

1.2 It would be interesting to know the type of custom marshalers that people develop.

1.3 In the course of my work, I have developed a few custom marshalers most of which deal with arrays that have to be exchanged between managed and unmanaged code.

1.4 When working with arrays to be used across managed and unmanaged code, custom marshalers can be very useful indeed. And so are complex structures.

1.5 In this article I would like to expound on a custom marshaler designed for learning and reference but may be useful in and of itself.

2. The LongMarshaler.

2.1 In C#, a long data type is a 64-bit integer. In C++, the long data type is a 32-bit integer.

2.2 The Microsoft C++ compilers of today (e.g. Visual Studio 2012), intrinsically supports the __int64 type. But if your C++ compiler does not support such a type, you can use the LARGE_INTEGER type (defined for Windows development).

2.3 For more information, please refer to : LARGE_INTEGER union on MSDN.

2.4 Some functions and COM interface methods incorporate LARGE_INTEGER type parameters, e.g. IStream::SetSize().

2.5 The custom marshaler that I provide here is called LongMarshaler. The basic mission of the LongMarshaler is to marshal a data of type long to an equivalent representation in C++ (type LARGE_INTEGER).

3. Source Codes of the LongMarshaler.

3.1 The following is a listing of the LongMarshaler class :

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

namespace LongMarshalerClassLib
{
    public class LongMarshaler : ICustomMarshaler
    {
        public object MarshalNativeToManaged(IntPtr pNativeData)
        {
            return null;
        }

        public IntPtr MarshalManagedToNative(object ManagedObj)
        {
            // Managed Object must be a long (i.e. 64bit integer) type.
            if (!(ManagedObj is long))
            {
                throw new ArgumentException("The input argument is not a long type.");
            }

            GCHandle gch = GCHandle.Alloc(ManagedObj, GCHandleType.Pinned);
            IntPtr pLong = gch.AddrOfPinnedObject();

            Int32[] intArray = new Int32[2];

            Marshal.Copy(pLong, intArray, 0, 2);

            gch.Free();

            int iSize = sizeof(Int32) * intArray.Length;
            IntPtr pIntArray = Marshal.AllocHGlobal(iSize);

            Marshal.Copy(intArray, 0, pIntArray, intArray.Length);

            return pIntArray;
        }

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

        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 LongMarshaler();
            }

            return marshaler;
        }

        static private LongMarshaler marshaler;
    }
}

3.2 The following is a summary of this class :

  • It is a one-way marshaler that transforms managed data to unmanaged hence the non-implementation of the MarshalNativeToManaged() method.
  • The main action is performed inside the MarshalManagedToNative() and the CleanUpNativeData() methods.
  • Let’s study the MarshalManagedToNative() function in greater detail.
  • MarshalManagedToNative() first ensures that the input parameter must be of type long. If not it throws the ArgumentException().
  • Recall that the goal of the LongMarshaler is to transform a C# long data to an unmanaged LARGE_INTEGER structure and the target C# long data is the input parameter to MarshalManagedToNative().
  • The LongMarshaler does this via MarshalManagedToNative() by creating an unmanaged LARGE_INTEGER structure in memory and then fills the structure with appropriate values that would make it represent a 64-bit long value and then returns a pointer to this LARGE_INTEGER structure.
  • This pointer to the LARGE_INTEGER is to be passed to the unmanaged function that is called.
  • Remember that the input parameter to the unmanaged function is passed as an “in” parameter and so it remains owned by the LongMarshaler even after the unmanaged function returns.
  • This LARGE_INTEGER, which was allocated in MarshalManagedToNative(), is de-allocated in CleanUpNativeData().
  • We shall examine the inner workings of the MarshalManagedToNative() method in greater detail later.

4. Client Code.

4.1 In this section, we present some client code that will invoke the LongMarshaler.

4.2 We need both a C# client as well as an exported DLL function that will be called from inside the C# client.

4.3 The following is a listing of a C++ exported function that will be called in C# :

bool __stdcall TestLong(/*[in]*/ PLARGE_INTEGER pLargeInteger)
{
	if (pLargeInteger == NULL)
	{
		return false;
	}

	__int64	i64 = 0x1111222233334444;

	if (i64 == pLargeInteger -> QuadPart)
	{
		return true;
	}
	else
	{
		return false;
	}
}
  • The purpose of TestLong() is to check whether an input 64-bit long number equals to a fixed constant 64-bit long number.
  • TestLong() returns a boolean result.
  • It takes an input parameter which is a pointer to the LARGE_INTEGER that represents the 64-bit long value.
  • It then checks to see if the input LARGE_INTEGER “pLargeInteger” is NULL. If so it simply returns false.
  • The input LARGE_INTEGER is then compared with the constant long value 0x1111222233334444. The result is returned as a boolean value.

4.4 The following is how the TestLong() function is declared in C# :

[DllImport("TestDLL.dll", EntryPoint = "TestLong", CallingConvention = CallingConvention.StdCall)]
[return: MarshalAs(UnmanagedType.I1)]
private static extern bool TestLong([In][MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(LongMarshaler))] object lValue);
  • Note that the parameter “lValue” must be declared as of type object.
  • It cannot be declared as long which will make it a value type (even though it is).
  • This has to do with the fact that custom marshaling of value types is currently still not supported.

4.5 The following is a sample C# function that we can use to call the TestLong() API :

static void DoTest()
{
    Boolean bTest;

    // This will return true.
    bTest = TestLong(0x1111222233334444);

    // This will return false.
    bTest = TestLong(0x1111222233334445);

    // This will throw an exception.
    bTest = TestLong(0x11112222);
}
  • The first call to TestLong() will return a value of true.
  • The second call will result in false.
  • The third call will result in the ArgumentException being thrown because the input value is not a long data type. It is a 32-bit integer.

5. How LongMarshaler Works at Low Level.

5.1 Central to the LongMarshaler is the MarshalManagedToNative() and the CleanUpNativeData() methods.

5.2 Let’s examine how these 2 functions work at runtime :

  • We shall make a call to TestLong() using the value 0x1111222233334444.
  • A new instance of LongMarshaler will be created and then MarshalManagedToNative() is called.
  • In MarshalManagedToNative(), the GCHandle class is used to fix in memory the input object “ManagedObj”.
  • We then obtain the memory address by using the GCHandle::AddrOfPinnedObject() function.
  • This is stored in “pLong” which is an IntPtr :

MarshalManagedToNative_Memory_View

  • The “ManagedObj”, a long value, can be seen as it is stored in memory in the Memory Viewer.
  • The purpose of fixing “ManagedObj” in memory is to be able to copy its memory contents into an array of 2 Int32 numbers (i.e. intArray).
  • A 64-bit long number is a blittable value. This means that whether used in managed or unmanaged code, it is represented the same way in memory.
  • In C++, the LARGE_INTEGER union expects the 64-bit long number to be stored in memory as a back-to-back combination of 2 32-bit numbers :
typedef union _LARGE_INTEGER {
    struct {
        DWORD LowPart;
        LONG HighPart;
    };
    struct {
        DWORD LowPart;
        LONG HighPart;
    } u;
    LONGLONG QuadPart;
} LARGE_INTEGER;
  • Hence the memory representation of “ManagedObj” fits directly into a LARGE_INTEGER structure.
  • After copying the memory contents from “pLong” to “intArray”, “ManagedObj” is released from being fixed in memory. The GCHandle::Free() function is called.
  • MarshalManagedToNative() then allocates an unmanaged memory buffer large enough to hold 2 Int32 values.
  • It does so using the Marshal.AllocHGlobal() function. A pointer to this unmanaged buffer is stored in “pIntArray”.
  • The contents of “intArray” is then copied to this buffer :

MarshalManagedToNative_Memory_View_2

  • As can be seen from the screen shot above, the “pIntArray” buffer contains the exact same byte values as “intArray”.
  • The pointer to the buffer “pIntArray” is then returned.
  • Control then passes to the TestLong() API itself :

TestLong_Memory_View

  • From the screen shot above, we see that the parameter to TestLong(), “pLargeInteger”, points to the buffer that was allocated in MarshalManagedToNative().
  • When TestLong() returns, control will reach CleanUpNativeData() :

CleanUpNativeData_Memory_View

  • The memory buffer that was allocated in MarshalManagedToNative(), then used in TestLong(), is now passed to CleanUpNativeData() which will de-allocate it using Marshal.FreeHGlobal().
  • The use of Marshal.FreeHGlobal() is important because the buffer was allocated using Marshal.AllocHGlobal().

6. In Summary.

6.1 I hope the reader appreciates the flexibility afforded to the custom marshaler in deciding the way managed data can be expressed in unmanaged code.

6.2 Memory buffers can be allocated and filled with data in any way that the developer sees fit.

6.3 The key is to understand the data structure that is expected on the unmanaged side.

Advertisements

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.

Discussion

No comments yet.

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 )

Twitter picture

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

Facebook photo

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

Google+ photo

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

Connecting to %s

%d bloggers like this: