//
you're reading...
Interop Marshaling

Passing Managed Structures With Arrays To Unmanaged Code Part 3

1. Introduction

1.1 In parts 1 and 2 we studied how to pass managed structures (which contain arrays) to unmanaged code. The managed structures were passed an “in” parameters as well as “out” parameters.

1.2 In this part 3, we shall examine how to pass such structures as “reference” (i.e. “in” and “out”) parameters. Hence, we expect that the structure be first initialized in managed code and then passed to a target API that may perform action based on the initialized member values of the struct. The API finally has the option to modify the member values which will be permanently effective for the structure in managed code.

2. The Structures.

2.1 We shall continue to use the 2 structures that we have developed since 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 unmanaged 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. Referencing a Structure with an Inline Fixed-Length Array.

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

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

The InAttribute and the OutAttribute together indicate to the interop marshaler that data is to be marshaled to and from the unmanaged API. The “ref” C# keyword indicates that the “test_struct” argument is to be passed by reference.

3.2 Being passed by reference entails that the argument first be initialized before the API call. An important effect of being passed by reference is that any changes to the parameter value in the API will be permanently reflected when control returns to the managed code.

3.3 An example C++ implementation of the ReferenceStruct01() API is listed below :

void __stdcall ReferenceStruct01(/*[in, out]*/ TestStruct01* ptest_struct)
{
  // ptest_struct points to a TestStruct01
  // struct passed by reference.
  // This API may modify the values and
  // the changes will be effected in the
  // caller's code.

  // Increment the m_int member.
  ptest_struct -> m_int++;
  // Increment each array element value by 1.
  for (int i = 0; i < 10; i++)
  {
    (ptest_struct -> m_int_array)[i]++;
  }
}

3.4 An example C# calling code is listed below :

static void ReferenceStruct01InUnmanagedCode()
{
  // Allocate a TestStruct01 structure.
  TestStruct01 test_struct = new TestStruct01();
  // Assign values to the members.
  test_struct.m_int = 0;
  test_struct.m_int_array = new int[10];
  // The values of the array are sequential
  // values from 0 through 9.
  for (int i = 0; i < 10; i++)
  {
    test_struct.m_int_array[i] = i;
  }
  // Display the "before" values of test_struct.
  DisplayStruct01(test_struct);
  // Pass test_struct to the unmanaged API.
  // The API will modify the members of the
  // struct.
  ReferenceStruct01(ref test_struct);
  // Display the "after" values of test_struct.
  DisplayStruct01(test_struct);
}

A TestStruct01 structure “test_struct” is first declared and then initialized with values. Thereafter, the DisplayStruct01() API (an API we have used since part 1) is called to display a “before” view of “test_struct”.

After that, ReferenceStruct01() is called which will modify the member values of “test_struct”. Finally DisplayStruct01() is called again which will display the “after” view of “test_struct”.

When this C# method is called, we shall notice that the member values of “test_struct” were indeed modified permanently. How did this take place ? Section 4 below will provide an explanation.

Another point to note is that although the elements of the “m_int_array” array may be permanently modified, its size cannot be changed. This size is fixed by the SizeConst argument of the MarshalAsAttribute declared for TestStruct01 and cannot be modified by the unmanaged code.

4. Under The Hood.

4.1 Just as we have seen in part 2, when a structure was passed as an “out” parameter, when the ReferenceStruct01() API is called, the interop marshaler uses an allocated 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 (see part 2 section 4 for details).

4.2 This buffer is then treated like the unmanaged version of TestStruct01 and will be filled with values exactly matching those of the members of the managed TestStruct01 structure. In particular, the data of the “m_int_array” managed array will be copied into the structure as a serialized, flat C-style array.

4.3 After that, a pointer to this buffer will be passed to the unmanaged API. When the API modifies the values of the structure, the changes are made to this buffer. Then, when the API returns, this buffer is used to set the values of the counterpart managed TestStruct01 instance “test_struct”.

4.4 Note that the buffer that was used to store the temporary unmanaged TestStruct01 structure (a pointer to which 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 as a buffer for storing temporary values like the one we have just seen).

5. Referencing a Structure with a SAFEARRAY.

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

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

An advantage of using a SAFEARRAY (viz an inline array) is that its size can be changed. That is, the total number of elements can be increased or decreased by the unmanaged code. An example of this will follow shortly.

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

void __stdcall ReferenceStruct02(/*[in, out]*/ TestStruct02* ptest_struct)
{
  (ptest_struct -> m_int)++;

  long lLbound = 0;
  long lUbound = 0;
  // Obtain bounds information of the SAFEARRAY.
  SafeArrayGetLBound(ptest_struct -> pSafeArrayOfInt, 1, &lLbound);
  SafeArrayGetUBound(ptest_struct -> pSafeArrayOfInt, 1, &lUbound);
  long lDimSize = lUbound - lLbound + 1;  

  SAFEARRAYBOUND rgsabound[1];
  rgsabound[0].lLbound = 0;
  // Double the size of the array.
  rgsabound[0].cElements = lDimSize * 2;
  // Re-dimension the array.
  SafeArrayRedim
  (
    ptest_struct -> pSafeArrayOfInt,
    rgsabound
  );
  // Assign values to the new elements of the
  // array. Again using sequential values.
  for (int i = lDimSize; i < (lDimSize * 2); i++)
  {
    long rgIndices[1];
    int value = i;          

    rgIndices[0] = i;
    ::SafeArrayPutElement
    (
      ptest_struct -> pSafeArrayOfInt,
      rgIndices,
      (void*)&value
    );
  }
}

This API will increment the value of the “m_int” field by 1. Thereafter, as an interesting show of modification, it will actually increase the size of the array (doubling it) and assign values into the new elements of the array.

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

static void ReferenceStruct02InUnmanagedCode()
{
  // Allocate a TestStruct02 structure.
  TestStruct02 test_struct = new TestStruct02();
  // Assign values to the members.
  test_struct.m_int = 0;
  test_struct.m_int_array = new int[10];
  // The values of the array are sequential
  // values from 0 through 9.
  for (int i = 0; i < 10; i++)
  {
    test_struct.m_int_array[i] = i;
  }
  // Display the "before" values of test_struct.
  DisplayStruct02(test_struct);
  // Pass test_struct to the unmanaged API.
  // The API will modify the members of the
  // struct.
  ReferenceStruct02(ref test_struct);
  // Display the "after" values of test_struct.
  DisplayStruct02(test_struct);
}

Before and after values are displayed by this method. If you run the above C# code using the APIs, you will see that the data have indeed been modified. The “after” array has indeed been expanded in size.

5.4 Now what happens under the covers is very simple. A SAFEARRAY is a self-describing array object which intrinsically holds type, dimension and bounds (count of elements per dimension) information. When the unmanaged API returned, the interop marshaler referred to the SAFEARRAYBOUND field of the “pSafeArrayOfInt” SAFEARRAY to determine any changes in the count of elements in the array.

5.5 The interop marshaler then re-dimensioned the counterpart managed array member of “test_struct” and copied all the SAFEARRAY array data to the buffer of “test_struct”.

5.6 Then when this is done, the unmanaged SAFEARRAY “pSafeArrayOfInt” is destroyed using SafeArrayDestroy().

6. In Conclusion.

6.1 And there we have it : as complete a discussion as possible on passing an array containing structure to and from unmanaged code.

6.2 Throughout this series of blogs, I hope I have highlighted the importance of the StructLayoutAttribute and the MarshalAsAttribute. These are powerful tools to help in marshaling data to and from 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: