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

Passing a Structure which Emdeds another Structure from C# to C++.

1. Introduction.

1.1 It is not uncommon to see structures embedding other structures.

1.2 In managed code, such structures are also defineable.

1.3 This blog examines how such complex structures may be passed to an unmanaged API via standard interop marshaling.

2. Test Structures.

2.1 Let’s define 2 structures that we can use for demonstrative purposes :

[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;
}

[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;
}

Here, we have defined 2 structures : TestStructOuter and TestStructEmbedded with TestStructOuter embedding TestStructEmbedded. The various MarshalAsAttributes are very important in ensuring proper interop marshaling.

2.2 Given the layout of TestStructOuter as defined above, when it is marshaled to unmanaged memory, its embeded structure is also embedded inline inside TestStructOuter itself. That is, the whole of TestStructEmbedded is completely contained inside TestStructOuter.

2.3 Its memory layout will be just like the following C++ structure definitions :

#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;
};

2.4 The m_test_struct_embedded member need not be decorated with any MarshalAsAttributes.

2.5 Let us demonstrate this with some test codes.

3. Test Codes.

3.1 Let’s say we have the following C++ API that displays the contents of a TestStructOuter structure :

void __stdcall DisplayTestStructOuter
(
  /*[in]*/ TestStructOuter test_struct_outer
)
{
  printf
  (
    "test_struct_outer.m_lpszStr01 : [%s].\r\n",
    test_struct_outer.m_lpszStr01
  );

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

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

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

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

3.2 The above API will be declared as follows in C# :

[DllImport("TestDLL01.dll", CallingConvention=CallingConvention.StdCall)]
private static extern void DisplayTestStructOuter([In] TestStructOuter test_struct_outer);

3.3 The following is a test C# function :

static void DisplayTestStructOuter()
{
  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;

  DisplayTestStructOuter(test_struct_outer);
}

3.4 Now, at runtime, when DisplayTestStructOuter() is called, test_struct_outer will be passed by value and so it is created on the call stack. The following will be the memory layout of test_struct_outer  at runtime :

The various fields of test_struct_outer are highlighted in red boxes and their field names displayed. As can be seen, the entire TestStructOuter.m_test_struct_embedded field is embedded inside the memory space of test_struct_outer itself. The following sections will analyze the various field values of test_struct_outer in greater detail.

3.5 The TestStructOuter.m_lpszStr01 field of test_struct_outer (a pointer to a NULL-terminated C-style string) has value 0x001b4f18. Let’s have a look at this memory location :

It indeed points to a memory location where the NULL-terminated C-style string “123456789” is stored.

3.6 The first field of TestStructOuter.m_test_struct_embedded is also a pointer to a NULL-terminated C-style string. According to the diagram in point 3.4, this pointer value is : 0x001B4F48. Let’s have a look at this memory location :

It indeed points to a memory location where the NULL-terminated C-style string “ABCDEFGHIJ” is stored.

3.7 The rest of the fields of TestStructOuter.m_test_struct_embedded can be easily spotted from the diagram in point 3.4. The m_shortArray01 field can be easily identified after the hex byte sequence 48 4f 1b 00 :

00 00 01 00 02 00 03 00 04 00

which works out to :

test_struct_outer.m_test_struct_embedded.m_shortArray01[0] == 0
test_struct_outer.m_test_struct_embedded.m_shortArray01[1] == 1
test_struct_outer.m_test_struct_embedded.m_shortArray01[2] == 2
test_struct_outer.m_test_struct_embedded.m_shortArray01[3] == 3
test_struct_outer.m_test_struct_embedded.m_shortArray01[4] == 4

The TestStructEmbedded.m_shortArray01 field has been declared as being marshaled as an inline array of 5 short values :

[MarshalAs(UnmanagedType.ByValArray, SizeConst=5)]
public short [] m_shortArray01;

Hence its field values are all embedded inside the memory block for test_struct_outer.m_test_struct_embedded itself.

3.8 The last field of the TestStructEmbedded structure inside test_struct_outer (i.e. TestStructOuter.m_test_struct_embedded.m_int01) has hex byte sequence : 64 00 00 00. This works out to the decimal value 100.

3.9 The last field value of test_struct_outer also has hex byte sequence 64 00 00 00. This works out to the decimal value 100.

3.10 The DisplayTestStructOuter() API will display the following on the console output :

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

This output confirms the various field values that we examined in memory for the test_struct_outer structure and its emdedded m_test_struct_embedded structure.

4. Data Destruction.

4.1 When the DisplayTestStructOuter() API completes its run, the memory location occuppied for test_struct_outer is naturally recovered via stack memory recovery.

4.2 The 2 C-style strings that were allocated for the various string fields will be destroyed. They have been previously allocated via Mashal.AllocCoTaskMem() (which eventually called CoTaskMemAlloc()) and will be freed using Marshal.FreeCoTaskMem() (which eventually calls ::CoTaskMemFree().

5. In Conclusion.

5.1 I hope the reader will see the great beauty of the interop marshaler.

5.2 Many things can be done for the benefit of the C++ developer with judicious use of the MarshalAsAttributes.

5.3 In a future blog, I shall demonstrate how to pass a structure (which embeds another structure) from C# to C++ via a pointer.

 

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: