//
you're reading...
Interop Marshaling, Managed Structures

Passing a Pointer to a Structure from C# to C++ Part 3.

1. Introduction.

1.1 In part 2 of this series of blogs, I have demonstrated how to marshal to unmanaged code a pointer to a structure which contains non-blittable field types.

1.2 We have examined how such a structure is to be allocated and deallocated in memory. We have seen in particular, the importance of using the Marshal.DestroyStructure() method.

1.3 Here in part 3, I shall present 2 complex examples which hopefully will cement the reader’s understaning of this subject.

2. Example 1 – A Structure Containing Arrays.

2.1 For this example, we shall use the following managed structure :

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct TestStructWithArrays
{
  [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType=VarEnum.VT_I4)]
  public Int32[] m_intArray01;
  [MarshalAs(UnmanagedType.ByValArray, SizeConst=3)]
  public Int32[] m_intArray02;
}

This structure TestStructWithArrays contains 2 managed arrays each of which is marshaled using a different technique. The first m_intArray01 is marshaled as a SAFEARRAY of integers. The second is marshaled as an inline array embedded within the structure itself.

2.2 This structure will have the following unmanaged representation :

#pragma pack(1)

struct TestStructWithArrays
{
  SAFEARRAY* m_pSafeArrayOfInt;
  int m_pArrayOfInt[3];
};

2.3 To display the field values inside such a structure, we have the following C++ API :

void __stdcall DisplayTestStructWithArrays_ByPointer(/*[in]*/ TestStructWithArrays* ptest_struct_with_arrays)
{
  long lowerBound;
  long upperBound;
  long l;

  SafeArrayGetLBound(ptest_struct_with_arrays -> m_pSafeArrayOfInt, 1, &lowerBound);
  SafeArrayGetUBound(ptest_struct_with_arrays -> m_pSafeArrayOfInt, 1, &upperBound);

  for (l = lowerBound; l <= upperBound; l++)
  {
    int iValue = 0;

    SafeArrayGetElement(ptest_struct_with_arrays -> m_pSafeArrayOfInt, &l, (void*)&iValue);
    printf ("ptest_struct_with_arrays -> m_pSafeArrayOfInt[%d] : [%d].\r\n", l, iValue);
  }

  for (l = 0; l < 3; l++)
  {
    printf ("ptest_struct_with_arrays -> m_pArrayOfInt[%d] : [%d].\r\n", l, ptest_struct_with_arrays -> m_pArrayOfInt[l]);
  }
}

2.4 The above API is imported into C# code using the following declaration :

[DllImport("TestDLL01.dll", CallingConvention = CallingConvention.StdCall)]
private static extern void DisplayTestStructWithArrays_ByPointer([In] IntPtr ptest_struct_with_arrays);

2.5 The following is a sample C# function that demonstrates how to prepare an unmanaged version of a TestStructWithArrays structure, set its field values, and then invoke the DisplayTestStructWithArrays_ByPointer() API :

static void DisplayTestStructWithArrays_ByPointer()
{
  TestStructWithArrays test_struct_with_arrays = new TestStructWithArrays();
  int i = 0;

  test_struct_with_arrays.m_intArray01 = new Int32[5];
  for (i = 0; i < 5; i++)
  {
    test_struct_with_arrays.m_intArray01[i] = i;
  }

  test_struct_with_arrays.m_intArray02 = new Int32[3];
  for (i = 0; i < 3; i++)
  {
    test_struct_with_arrays.m_intArray02[i] = i * 10;
  }

  int iSizeOfTestStructWithArrays = Marshal.SizeOf(typeof(TestStructWithArrays));
  IntPtr ptest_struct_with_arrays = Marshal.AllocHGlobal(iSizeOfTestStructWithArrays);
  Marshal.StructureToPtr(test_struct_with_arrays, ptest_struct_with_arrays, false);

  DisplayTestStructWithArrays_ByPointer(ptest_struct_with_arrays);

  Marshal.DestroyStructure(ptest_struct_with_arrays, typeof(TestStructWithArrays));
  Marshal.FreeHGlobal(ptest_struct_with_arrays);
  ptest_struct_with_arrays = IntPtr.Zero;
}

2.6 In part 2, we have examined in detail how a structure like TestStructWithArrays may be prepared to be passed as an IntPtr. Hence we will not go through this again in this part. Instead, we will see for ourselves how the m_pSafeArrayOfInt field is destroyed by the Marshal.DestroyStructure() method.

2.7 When the DisplayTestStructWithArrays_ByPointer() API is called, the contents of the TestStructWithArrays structure is as follows :

The TestStructWithArrays.m_pArrayOfInt is an inline integer array which is embedded inside the TestStructWithArrays structure itself and its values are clearly seen : 0 (0x00000000), 10 (0x0000000a) and 20 (0x00000014).

The TestStructWithArrays.m_pSafeArrayOfInt field is a pointer to a SAFEARRAY. This pointer points to the address 0x0081dad8. The following is the memory layout at address 0x0081dad8 :

The bytes boxed in red are the bytes of a SAFEARRAY. There are altogether 24 bytes in a SAFEARRAY structure. A SAFEARRAY structure internally holds a member field which points to the actual array data. This is at address 12 bytes offset from the beginning of the SAFEARRAY structure. From the diagram above, we can see that the address is 0x0081D610.

Let’s look at the contents of this address :

Each array element is a 4-byte(32-bit) value and the values 0, 1, 2, 3, 4 can be clearly seen.

2.8 When the DisplayTestStructWithArrays_ByPointer() runs through, the following will be displayed on the console output :

ptest_struct_with_arrays -> m_pSafeArrayOfInt[0] : [0].
ptest_struct_with_arrays -> m_pSafeArrayOfInt[1] : [1].
ptest_struct_with_arrays -> m_pSafeArrayOfInt[2] : [2].
ptest_struct_with_arrays -> m_pSafeArrayOfInt[3] : [3].
ptest_struct_with_arrays -> m_pSafeArrayOfInt[4] : [4].
ptest_struct_with_arrays -> m_pArrayOfInt[0] : [0].
ptest_struct_with_arrays -> m_pArrayOfInt[1] : [10].
ptest_struct_with_arrays -> m_pArrayOfInt[2] : [20].

2.9 The data contents of the unmanaged TestStructWithArrays structure together with the data contents of the SAFEARRAY member and the array data of the SAFEARRAY will all remain in memory even when DisplayTestStructWithArrays_ByPointer() completes.

2.10 Only when Marshal.DestroyStructure() is called on ptest_struct_with_arrays will the contents of the SAFEARRAY member and the array data be deleted. The following diagrams show this clearly :

  • The TestStructWithArrays.m_pSafeArrayOfInt field itself is all set to zeroes :

  • The memory for the contents of the TestStructWithArrays.m_pSafeArrayOfInt SAFEARRAY (24 bytes) is recovered :

  • The memory for the array data of the TestStructWithArrays.m_pSafeArrayOfInt SAFEARRAY (5 elements which is 20 bytes) is recovered :

2.11 And then when Marshal.FreeHGlobal() is called on ptest_struct_with_arrays, the memory contents of ptest_struct_with_arrays itself is recovered :

3. Example 2 – A Structure which Embeds Another Structure.

3.1 For this second example I shall use 2 structures one of which embeds the other. The first structure is listed below :

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct TestStructOuter
{
  [MarshalAs(UnmanagedType.LPStr)]
  public string m_str01;
  public TestStructEmbedded m_test_struct_embedded;
  public Int32 m_int01;
}

3.2 The second structure, the one which is embedded, is listed below :

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct TestStructEmbedded
{
  [MarshalAs(UnmanagedType.LPStr)]
  public string m_str01;
  [MarshalAs(UnmanagedType.ByValArray, SizeConst=5)]
  public short [] m_shortArray01;
  public Int32 m_int01;
}

3.3 These structures will have the following C++ counterparts :

#pragma pack(1)

struct TestStructEmbedded
{
  LPSTR m_lpszStr01;
  short m_shortArray01[5];
  int m_int01;
};

struct TestStructOuter
{
  LPSTR	m_lpszStr01;
  TestStructEmbedded m_test_struct_embedded;
  int m_int01;
};

3.4 To display the field values inside a TestStructOuter structure, we have the following C++ API :

void __stdcall DisplayTestStructOuter_ByPointer
(
  /*[in]*/ TestStructOuter* ptest_struct_outer
)
{
  printf
  (
    "ptest_struct_outer -> m_lpszStr01 : [%s].\r\n",
    ptest_struct_outer -> m_lpszStr01
  );

  printf
  (
    "ptest_struct_outer -> m_test_struct_embedded.m_lpszStr01 : [%s].\r\n",
    ptest_struct_outer -> m_test_struct_embedded.m_lpszStr01
  );

  for (int i = 0; i < 5; i++)
  {
    printf
    (
      "ptest_struct_outer -> m_test_struct_embedded.m_shortArray01[%d] : [%d].\r\n",
      i,
      ptest_struct_outer -> m_test_struct_embedded.m_shortArray01[i]
    );
  }

  printf
  (
    "ptest_struct_outer -> m_test_struct_embedded.m_int01 : [%d].\r\n",
    ptest_struct_outer -> m_test_struct_embedded.m_int01
  );

  printf
  (
    "ptest_struct_outer -> m_int01 : [%d].\r\n",
    ptest_struct_outer -> m_int01
  );
}

3.5 The above API is imported into C# code using the following declaration :

[DllImport("TestDLL01.dll", CallingConvention = CallingConvention.StdCall)]
private static extern void DisplayTestStructOuter_ByPointer([In] IntPtr ptest_struct_outer);

3.6 The following is a sample C# function that demonstrates how to prepare an unmanaged version of a TestStructOuter structure, set its field values, and then invoke the DisplayTestStructOuter_ByPointer() API :

static void DisplayTestStructOuter_ByPointer()
{
  TestStructOuter test_struct_outer = new TestStructOuter();

  test_struct_outer.m_str01 = "123456789";
  test_struct_outer.m_test_struct_embedded = new TestStructEmbedded();
  test_struct_outer.m_test_struct_embedded.m_str01 = "ABCDEFGHIJ";
  test_struct_outer.m_test_struct_embedded.m_shortArray01 = new short[5];
  for (int i = 0; i < 5; i++)
  {
    test_struct_outer.m_test_struct_embedded.m_shortArray01[i] = (short)i;
  }
  test_struct_outer.m_test_struct_embedded.m_int01 = 100;
  test_struct_outer.m_int01 = 100;

  Int32 iSizeOfTestStructOuter = Marshal.SizeOf(typeof(TestStructOuter));
  IntPtr ptest_struct_outer = Marshal.AllocHGlobal(iSizeOfTestStructOuter);
  Marshal.StructureToPtr(test_struct_outer, ptest_struct_outer, false);

  DisplayTestStructOuter_ByPointer(ptest_struct_outer);

  Marshal.DestroyStructure(ptest_struct_outer, typeof(TestStructOuter));
  Marshal.FreeHGlobal(ptest_struct_outer);
  ptest_struct_outer = IntPtr.Zero;
}

3.7 Once again, we will not need to go through examining in detail how a structure like TestStructOuter may be prepared to be passed as an IntPtr. We have covered this in-depth in part 2. We will go directly to the process of destruction of the embedded sub-structure of TestStructOuter.

3.8 When the DisplayTestStructOuter_ByPointer() API is being executed, the following is the contents of the TestStructOuter structure pointed to by the ptest_struct_outer parameter :

The m_test_struct_embedded field is completely embedded inside the TestStructOuter structure.

3.9 The ptest_struct_outer -> m_lpszStr01 field is a pointer to a C-style string with address 0x006fe8e0. The memory layout at this address is :

3.10 Let’s examine the ptest_struct_outer -> m_test_struct_embedded field. The first field of this embedded structure (m_lpszStr01) is also a pointer to a C-style string with value 0x006f4600. Let’s look at the memory at this address :

3.11 The other fields of the ptest_struct_outer -> m_test_struct_embedded field, i.e. m_shortArray01 and m_int01 are embedded inside the m_test_struct_embedded structure itself and these can be clearly seen in the diagram of point 3.8 (in the blue box).

3.12 When DisplayTestStructOuter_ByPointer() runs through, the following will be displayed on the console output :

ptest_struct_outer -> m_lpszStr01 : [123456789].
ptest_struct_outer -> m_test_struct_embedded.m_lpszStr01 : [ABCDEFGHIJ].
ptest_struct_outer -> m_test_struct_embedded.m_shortArray01[0] : [0].
ptest_struct_outer -> m_test_struct_embedded.m_shortArray01[1] : [1].
ptest_struct_outer -> m_test_struct_embedded.m_shortArray01[2] : [2].
ptest_struct_outer -> m_test_struct_embedded.m_shortArray01[3] : [3].
ptest_struct_outer -> m_test_struct_embedded.m_shortArray01[4] : [4].
ptest_struct_outer -> m_test_struct_embedded.m_int01 : [100].
ptest_struct_outer -> m_int01 : [100].

When code control returns to the C# code, all field values of ptest_struct_outer remain in memory. This includes all field values of ptest_struct_outer -> m_test_struct_embedded (TestStructEmbedded).

3.13 When Marshal.DestroyStructure() is called on ptest_struct_outer, all the sub-structures and non-blittable fields of the structure pointed to by ptest_struct_outer will be destroyed. This includes the sub-structures and non-blittable fields of the m_test_struct_embedded structure.

3.14 In particular, the memory occuppied by the following fields are destroyed : TestStructOuter.m_lpszStr01 and TestStructOuter.m_test_struct_embedded.m_lpszStr01. These are the string types. But it also goes if the fields are SAFEARRAYs or BSTRs.

  • The following is the memory layout at location 0x006fe8e0 which used to hold the string pointed to by ptest_struct_outer -> m_lpszStr01 :

The following is the memory layout at location 0x006f4600 which used to hold the string pointed to by ptest_struct_outer -> m_test_struct_embedded.m_lpszStr01 :

3.15 Now, when Marshal.FreeHGlobal() is called on ptest_struct_outer, the entire memory block occuppied by the TestStructOuter structure pointed to by ptest_struct_outer will be destroyed :

4. In Conclusion.

4.1 And there you have it. Two rigorous examples of pointers to structures which contain non-blittable types and how they can safely destroyed completely with the help of Marshal.DestroyStructure() and Marshal.FreeHGlobal() (or Marshal.FreeCoTaskMem() if Marshal.AllocCoTaskMem() was used to allocated the memory).

4.2 The interop marshaler is a truly useful tool which can help us tremendously in memory allocation and decallocation. It may take a little more time to learn and master. But it certainly is standard and consistent.

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.

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: