//
you're reading...
Interop Marshaling

Passing Managed Structures With Arrays To Unmanaged Code Part 2

1. Introduction.

1.1 In part 1 of this series of blogs, we discussed the techniques for passing by value a structure that contains an array.

1.2 In this part 2, we explore the various techniques for passing such a structure as a return (or “out”) parameter. In other words, how to receive an initialized structure from an unmanaged API.

1.3 As was the case in part 1, all unmanaged APIs are written in C++.

2. The Structures.

2.1 We shall continue to use the 2 structures that we have developed in part 1 :

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct TestStruct01
{
  public int m_int;
  [MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
  public int[] m_int_array;
};

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct TestStruct02
{
  public int m_int;
  [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_I4)]
  public int[] m_int_array;
};

2.2 We shall also use the same corresponding nmanaged C++ structures that we defined previously :

#pragma pack(1)
struct TestStruct01
{
  int m_int;
  int m_int_array[10];
};

struct TestStruct02
{
  int m_int;
  SAFEARRAY* pSafeArrayOfInt;
};

2.3 In section 3, we shall look into passing TestStruct01 and we in section 5, we will discuss TestStruct02.

3. Receiving a Structure with an Inline Fixed-Length Array.

3.1 To pass a structure like TestStruct01 as a return (“out”) parameter, we need to define it as an “out” parameter. The following is an example :

[DllImport("TestDLL.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void GetStruct01([Out] out TestStruct01 test_struct);

The OutAttribute indicates to the interop marshaler that data is to be marshaled from the unmanaged API to the managed side. The “out” C# keyword indicates that the “test_struct” argument is to be passed by reference but the argument need not be first initialized. In fact, it is the GetStruct01() API that will “initialize” it with values.

3.2 An example C++ implementation of the GetStruct01() API is listed below :

void __stdcall GetStruct01(/*[out]*/ TestStruct01* ptest_struct)
{
  ptest_struct -> m_int = 0;

  for (int i = 0; i < 10; i++)
  {
    ptest_struct -> m_int_array[i] = i;
  }
}

Notice that the “ptest_struct” parameter is a pointer to a TestStruct01 structure. The API treats the pointer as if it already points to an allocated TestStruct01 structure. This is an interesting point and we shall return to this again later in section 4.

3.3 An example C# calling code is listed below :

static void GetStruct01FromUnmanagedCode()
{
  // Declare an uninitialized TestStruct01 structure
  TestStruct01 test_struct;
  // Call GetStruct01() which will fill the struct
  // with values including values for the "m_int_array"
  // member.
  GetStruct01(out test_struct);
  // Display newly set values.
  DisplayStruct01(test_struct);
}

Notice that the “test_struct” structure is declared but not initialized. The GetStruct01() API is then called with “test_struct” being passed as an “out” parameter. When GetStruct01() returns, “test_struct” is indeed filled with values. How did this happen ? See section 4 below.

4. Under The Hood.

4.1 When the GetStruct01() API is called, the interop marshaler first allocates a memory area large enough to store the unmanaged version of the TestStruct01 structure. The size of this buffer is determined by the StructLayoutAttribute of TestStruct01 together with the various MarshalAsAttributes applied to the members of the structure.

4.2 This size works out to be 44 bytes (4 bytes for “m_int” plus 40 bytes for the 10 element integer array “m_int_array”). Since the structure member packing is 1 byte, there will be no spaces in between the 2 struct members and so no extra “padding” bytes need to be added to the overall size.

4.3 Then a pointer to this buffer will be passed to the unmanaged API. This is why the API is able to assign values to the structure via the pointer as if the structure already exists (see point 3.2).

4.4 Then, when the API returns, this buffer is used to initialize the actual managed TestStruct01 instance “test_struct”. This is likely done using Marshal.PtrToStructure().

4.5 Note that the buffer that was used to store the temporary unmanaged TestStruct01 structure (a pointer to it was passed to the API) may not be de-allocated. This buffer could have been pre-allocated by the CLR for temporary usage (e.g for making API calls).

5. Receiving a Structure with a SAFEARRAY.

5.1 To pass a structure like TestStruct02 as a return (“out”) parameter, we need to define it as an “out” parameter just like we did for TestStruct01. The following is an example :

[DllImport("TestDLL.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void GetStruct02([Out] out TestStruct02 test_struct);

5.2 An example C++ implementation of the GetStruct02() API is listed below :

void __stdcall GetStruct02(/*[out]*/ TestStruct02* ptest_struct)
{
  ptest_struct -> m_int = 0;

  SAFEARRAYBOUND rgsabound[1];
  rgsabound[0].lLbound = 0;
  rgsabound[0].cElements = 5;

  ptest_struct -> pSafeArrayOfInt = SafeArrayCreate(VT_I4, 1, rgsabound);

  for (int i = 0; i < 5; i++)
  {
    long rgIndices[1];
    int value = i;
    rgIndices[0] = i;
    SafeArrayPutElement(ptest_struct -> pSafeArrayOfInt, rgIndices, (void*)&value);
  }
}

As usual, to the GetStruct02() API, the TestStruct02 structure pointed to by “ptest_struct” already exists and is laid out just as it expects (point 2.2). The GetStruct02() API creates the SAFEARRAY for the “pSafeArrayOfInt” member and assigns values to it.

5.3 An example C# call to GetStruct02() is listed below :

static void GetStruct02FromUnmanagedCode()
{
  // Declare a TestStruct02 structure but do not
  // set its array member.
  TestStruct02 test_struct;
  // Call GetStruct01 which will fill the struct
  // with values including values for the
  // "m_int_array" member.
  GetStruct02(out test_struct);
  // Display newly set values.
  DisplayStruct02(test_struct);
}

Just as it was for the TestStruct01 case, we simply declare a TestStruct02 structure without initializing it and then hand it over to the GetStruct02() API which will “initialize” it for us.

5.4 The temporary memory allocation mechanism for TestStruct02 is exactly the same as for TestStruct01 (see section 4). However, this time, another significant series of actions will be performed by the interop marshaler :

  • the SAFEARRAY that was created inside GetStruct02() will be used to create the managed array member “m_int_array”.
  • After the managed array member has been constructed, the SAFEARRAY will be destroyed using the SafeArrayDestroy() API.

5.5 Note another significant matter : when DisplayStruct02() is called, yet another (temporary) SAFEARRAY is created from the managed array member “m_int_array” of “test_struct”. This SAFEARRAY is then passed to DisplayStruct02() and when the API returns, the (temporary) SAFEARRAY is again destroyed.

6. In Conclusion.

6.1 Here in part 2, we have examined how a structure that contains an array is passed to an unmanaged API as a return (“out”) parameter.

6.2 We have also examined what the interop marshaler does under the hood to provide seamless integration between a managed array and an unmanaged SAFEARRAY.

6.3 In the next part of this series, we will examine how an array containing structure may be passed to unmanaged code by reference. That is, with initialized data which is then modified by unmanaged code.

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: