[转]Passing Managed Structures With Strings To Unmanaged Code Part 2

时间:2022-03-20 19:09:01

1. Introduction.

1.1 In part
1
of this series of blogs we studied how to pass a managed structure
(which contains strings) to unmanaged code. The structure was passed as an “in”
(by-value) parameter, i.e. the structure was passed to the unmanaged code as a
read-only parameter.

1.2 Here in part 2, we shall explore the techniques for
receiving such a structure from unmanaged code as an “out” parameter. I have
previously written about marshaling
managed structures to and from unmanaged code
. This series of blogs aim to
study how to marshal structures which specifically contain strings.

2. Sample Structure, Unmanaged String Representations
and Marshaling Direction.

2.1 We shall continue to use the test structures
that we have bult up from part 1.

2.2 Although the direction of parameter passing is
now reversed (i.e. from unmanaged to managed code), the ways that a string
structure member is represented in unmanaged code remain the same :

  • As a fixed length inline NULL-terminated character array
    (aka C-style string).
  • As a pointer to a NULL-terminated character array
    (better known as LPCSTR in C/C++ lingo).
  • As a BSTR.

2.3 Throughout the various sections below, attention
will be given to the effects of marshaling an “out” parameter : that any
initially set string values will not be received by the called API. The
marshaling direction is from the unmanaged side to the managed side. Hence only
the string allocated on the unmanaged side and returned “outwardly” to the
managed side will be marshaled.

3. Receiving a Structure with a Fixed-Length Inline
String.

3.1 For the demonstrative codes presented in this
section, we shall use the C# structure TestStruct01 :

[StructLayout(LayoutKind.Sequential, Pack = 1, CharSet = CharSet.Ansi)]
public struct TestStruct01
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 21)]
public string m_strString;
};

3.2 The C++ equivalent is as usual :

#pragma pack (1)

struct TestStruct01
{
char m_szString[21];
};

3.3 An example C++-based unmanaged API that takes such a
structure as an “out” parameter is listed below :

void __stdcall SetValueTestStruct01(/*[out]*/ TestStruct01* ptest_struct_01)
{
strcpy (ptest_struct_01 -> m_szString, "From unmanaged code.");
}

3.4 The C# declaration for such an API is listed below
:

[DllImport("TestDLL01.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void SetValueTestStruct01([Out] out TestStruct01 test_struct_01);

3.5 And a sample C# code for invoking the API
:

static void SetValueTestStruct01()
{
TestStruct01 test_struct_01; SetValueTestStruct01(out test_struct_01); DisplayTestStruct01(test_struct_01);
}

Notice that the “test_struct_01” TestStruct01 structure
was instantiated but not initialized before the call to SetValueTestStruct01().
This is acceptable to the compiler because “test_struct_01” is used as an “out”
parameter which means that its value is to be set by the SetValueTestStruct01()
API.

We then used DisplayTestStruct01() (an API we have
already met in part 1) to display the value set for
“test_struct_01.m_strString”.

3.6 Under the covers, the following is how the interop
marshaler performs the marshaling process :

  • When the SetValueTestStruct01() API was called, the
    interop marshaler will allocate a buffer with space large enough to
    hold the structure which is listed in point 3.2.
  • Note that this temporary buffer need not always be
    freshly allocated using Marshal.AllocHGlobal() or Marshal.AllocCoTaskMem().
    This buffer may be from a cached and re-usable memory area managed by the
    CLR.
  • Wherever it originates, this memory buffer serves
    to store a temporary unmanaged version of the “TestStruct01”
    structure.
  • The interop marshaler will then pass a pointer to
    this structure to the SetValueTestStruct01() API.
  • The API will assign value to the “m_szString” field of
    the “TestStruct01” struct pointed to by the input “ptest_struct_01”
    parameter.
  • When the API returns, the interop marshaler will assign
    the string value in the unmanaged “m_szString” field to
    the counterpart “m_strString” field of the managed “test_struct_01”
    structure.
  • The managed “test_struct_01” structure will now be
    completely set.
  • At this time, the interop marshaler is free to
    re-use (for other purposes) the original buffer space which was used
    to hold the temporary structure.

3.7 The DisplayTestStruct01() API will display the
following expected output on the console :

test_struct_01.m_szString : [From unmanaged code.].

3.8 This is hence how we receive a structure (with
a string field typed as an inline embedded character array) from unmanaged
code. It is quite simple since most of the marshaling work is done by the
interop marshaler.

3.9 It is the interop marshaler that allocates memory
space for the unmanaged structure. This space includes the inline embedded
character array field. The unmanaged API assigns character values into this
character array field and the interop marshaler transcribes this value
into the managed structure. The interop marshaler then either destroys the
unmanaged structure or re-uses the memory occuppied by it.

3.10 Now, what if inside SetValueTestStruct01(), we had
set an initialized value to test_struct_01.m_strString before passing
test_struct_01 to SetValueTestStruct01() ? Listed below is a function which
performs this :

// SetValueTestStruct01_WithInitialization() will
// call SetValueTestStruct01() but with parameter
// initialized.
static void SetValueTestStruct01_WithInitialization()
{
TestStruct01 test_struct_01 = new TestStruct01(); test_struct_01.m_strString = "Initial string."; // When SetValueTestStruct01() is called, the interop
// marshaler will not copy the current value of
// test_struct_01.m_strString to the unmanaged
// counterpart field.
SetValueTestStruct01(out test_struct_01);
// When SetValueTestStruct01() completes, the
// string value in the unmanaged m_szString
// field will be used to overwrite the
// value in the managed m_strString field. DisplayTestStruct01(test_struct_01);
}

3.11 What will happen is that when the
SetValueTestStruct01() API is called, the interop
marshaler will
not
 copy the current value
of test_struct_01.m_strString to the m_szString field of the unmanaged
TestStruct01 structure. Hence inside SetValueTestStruct01(),
ptest_struct_01 -> m_szString will contain all NULL characters. This is
due to the fact that the parameter is an “out” parameter and so no marshaling of
the fields of the managed struct will be done towards the direction of
the API call.

3.12 Then when SetValueTestStruct01() returns, the
string value in the unmanaged m_szString field will be used to overwrite
the value in the managed m_strString field.

4. Receiving the Structure with a Pointer to a
NULL-terminated Character Array.

4.1 In this section, we use the TestStruct02 structure
that we first defined in part 1 :

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct TestStruct02
{
[MarshalAs(UnmanagedType.LPStr)]
public string m_strString;
};

4.2 The corresponding C++ equivalent of such a structure
is :

#pragma pack(1)

struct TestStruct02
{
LPCSTR m_pszString;
};

4.3 An example C++-based unmanaged API that takes such a
structure as an “out” parameter is listed below :

void __stdcall SetValueTestStruct02(/*[out]*/ TestStruct02* ptest_struct_02)
{
char szTemp[] = "From unmanaged code.";
ptest_struct_02 -> m_pszString = (LPSTR)::CoTaskMemAlloc(sizeof(szTemp));
strcpy ((LPSTR)(ptest_struct_02 -> m_pszString), szTemp);
}

4.4 The C# declaration for such an API is listed below
:

[DllImport("TestDLL01.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void SetValueTestStruct02([Out] out TestStruct02 test_struct_02);

4.5 And a sample C# code for invoking the API
:

static void SetValueTestStruct02()
{
TestStruct02 test_struct_02; SetValueTestStruct02(out test_struct_02); DisplayTestStruct02(test_struct_02);
}

Just like the example we met in section 3, the
“test_struct_02” TestStruct02 structure was instantiated but not initialized
before the call to SetValueTestStruct02(). This is acceptable to the compiler
because “test_struct_02” is used as an “out” parameter which means that its
value is to be set by the SetValueTestStruct02() API.

We then used DisplayTestStruct02() (an API we have
already met in part 1) to display the value set for
“test_struct_02.m_strString”.

4.6 Under the covers, the following are the processes
that take place throughout the SetValueTestStruct02() API call :

  • When the SetValueTestStruct02() API was called, the
    interop marshaler will allocate a buffer with space large enough to hold the
    unmanaged “TestStruct02” structure which is listed in point
    4.2.
  • Just like the example we studied in section 3, this
    temporary buffer need not always be freshly allocated using
    Marshal.AllocHGlobal() or Marshal.AllocCoTaskMem(). This buffer may be from a
    cached and re-usable memory area managed by the CLR.
  • Wherever it originates, this memory buffer serves to
    store a temporary unmanaged version of the “TestStruct02”
    structure.
  • At this time, the “m_pszString” member of this
    “TestStruct02” structure will be set to all zeroes.
  • As SetValueTestStruct02() executes, it will use the
    ::CoTaskMemAlloc() API to allocate an ANSI string buffer which can hold the
    string “From unmanaged code.”. The structure’s m_pszString member is then
    made to point to this buffer.
  • Then, when SetValueTestStruct02() returns, the string
    buffer pointed to by m_pszString is used to create a managed string that holds
    the same characters in Unicode. The string buffer pointed to by m_pszString is
    then freed using Marshal.FreeCoTaskMem().

4.7 The DisplayTestStruct02() API will display the
following expected output on the console screen :

test_struct_02.m_pszString : [From unmanaged code.].

4.8 This is hence how we receive a structure (with
a string field typed as a pointer to a NULL-terminated character array)
from unmanaged code. This time, the unmanaged code shares some responsibility in
allocating memory for the unmanaged string field.

4.9 Note that the memory allocation must be performed
using ::CoTaskMemAlloc(). This is the protocol. The interop marshaler will then
use Marshal.FreeCoTaskMem() to free this memory after using the contained
character data to create the corresponding managed string field.

4.10 Now, what happens if before being
passed to the SetValueTestStruct02() API, test_struct_02.m_strString is set
to some string value (like the code shown below) ?

// SetValueTestStruct02_WithInitialization() will
// call SetValueTestStruct01() but with parameter
// initialized.
static void SetValueTestStruct02_WithInitialization()
{
TestStruct02 test_struct_02 = new TestStruct02(); test_struct_02.m_strString = "Initial string."; // When SetValueTestStruct02() is called, the interop
// marshaler will not allocate any character buffer
// in memory. The m_pszString field of the unmanaged
// representation of the TestStruct02 struct will
// be NULL.
SetValueTestStruct02(out test_struct_02);
// When SetValueTestStruct02() returns, the
// string pointed to by the unmanaged m_pszString
// field will be used to overwrite the
// value in the managed m_strString field. DisplayTestStruct02(test_struct_02);
}

4.11 What will happen is that when
SetValueTestStruct02() is called, the interop marshaler will
not
 allocate any character buffer in
memory to contain the ANSI string “Initial
string.”
. The m_pszString field of the
unmanaged representation of the TestStruct02 struct
will be NULL.This is
what SetValueTestStruct02() will see when it executes. This is because the
parameter is an “out” parameter and so no marshaling of the fields of the
managed struct will be done towards the direction of the API
call.

4.12 Then when SetValueTestStruct02() returns, the
string pointed to by the unmanaged m_pszString field will be used to overwrite
the value in the managed m_strString field.

5. Receiving a Structure with a
BSTR.

5.1 For this section, we use the TestStruct03 structure
that we first defined in part 1 :

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct TestStruct03
{
[MarshalAs(UnmanagedType.BStr)]
public string m_strString;
};

5.2 The corresponding C++ equivalent of such a structure
is :

#pragma pack(1)

struct TestStruct03
{
BSTR m_bstr;
};

5.3 An example C++-based unmanaged API that takes such a
structure as an “out” parameter is listed below :

void __stdcall SetValueTestStruct03(/*[out]*/ TestStruct03* ptest_struct_03)
{
ptest_struct_03 -> m_bstr = ::SysAllocString(L"BSTR from unmanaged code.");
}

5.4 The C# declaration for such an API is listed below
:

[DllImport("TestDLL01.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void SetValueTestStruct03([Out] out TestStruct03 test_struct_03);

5.5 And a sample C# code for invoking the API
:

static void SetValueTestStruct03()
{
TestStruct03 test_struct_03; SetValueTestStruct03(out test_struct_03); DisplayTestStruct03(test_struct_03);
}

Similar to the prevous examples that we have seen
earlier, the TestStruct03 structure “test_struct_03” was instantiated but
not initialized before the call to SetValueTestStruct03(). This is acceptable to
the compiler because “test_struct_03” is used as an “out” parameter which means
that its value is to be set by the SetValueTestStruct03() API.

We then used DisplayTestStruct03() (an API we have
already met in part 1) to display the value set for
“test_struct_03.m_strString”.

5.6 Under the covers, the following are the processes
that take place throughout the SetValueTestStruct02() API call :

  • When the SetValueTestStruct03() API was called, the
    interop marshaler will allocate a buffer with space large enough to hold the
    unmanaged “TestStruct03” structure which is listed in point
    5.2.
  • Just like the previous example we have studied, this
    temporary buffer need not always be freshly allocated using
    Marshal.AllocHGlobal() or Marshal.AllocCoTaskMem(). This buffer may be from a
    cached and re-usable memory area managed by the CLR.
  • Wherever it originates, this memory buffer serves to
    store a temporary unmanaged version of the “TestStruct03”
    structure.
  • At this time, the “m_bstr” member of this “TestStruct03”
    structure will be set to all zeroes.
  • As SetValueTestStruct03() executes, it will use the
    ::SysAllocString() API to allocate a BSTR which can hold the Unicode string
    “BSTR from unmanaged code.”. The structure’s “m_bstr” member is then made to
    point to this buffer.
  • Then, when SetValueTestStruct03() returns, the
    BSTR pointed to by “m_bstr” is used to create a managed string that holds
    the same characters. The BSTR pointed to by “m_bstr” is then freed using
    Marshal.FreeBSTR() (which eventually calls
    ::SysFreeString()).

5.7 The DisplayTestStruct03() API will display the
following expected output on the console screen :

test_struct_03.m_bstr : [BSTR from unmanaged code.].

5.8 We have thus examined how we receive from
unmanaged code a structure with a string field typed as a pointer to a
BSTR. Just like the example we saw in section 4, the unmanaged code shares
some responsibility in allocating memory for the BSTR.

5.9 The protocol for this is very important : the
BSTR allocation must be performed using ::SysAllocString(). The interop
marshaler will then use Marshal.FreeBSTR() to free the BSTR after
using it to create the corresponding managed string field.

5.10 Again we ask the same question : what if, prior to
being passed to the SetValueTestStruct03() API, test_struct_03.m_strString was
set to some initialization string (like the code below) ?

// SetValueTestStruct03_WithInitialization() will
// call SetValueTestStruct03() but with parameter
// initialized.
static void SetValueTestStruct03_WithInitialization()
{
TestStruct03 test_struct_03 = new TestStruct03(); test_struct_03.m_strString = "Initial string."; // When SetValueTestStruct03() is called, the interop
// marshaler will not allocate any BSTR for "Initial string.".
// The m_bstr field of the unmanaged
// representation of the TestStruct03 struct will
// be NULL.
SetValueTestStruct03(out test_struct_03);
// When SetValueTestStruct03() returns, the
// BSTR pointed to by the unmanaged m_bstr
// field will be used to overwrite the
// value in the managed m_strString field. DisplayTestStruct03(test_struct_03);
}

5.11 What will happen is that when
SetValueTestStruct03() is called, the interop marshaler will
not
 allocate any BSTR for “Initial string.”.
The m_bstr field of the unmanaged representation of the TestStruct03 struct will
be set to NULL. Once again, this is all because the parameter is an “out”
parameter and so no marshaling of the fields of the managed struct will be
done towards the direction of the API call.

5.12 Then, when SetValueTestStruct03() returns, the BSTR
pointed to by the unmanaged m_bstr field will be used to overwrite
the value in the managed m_strString field.

6. Special Handling for a Structure Passed by
Pointer.

6.1 If the arrangement is such that a managed
structure (which contains a string field) is to be passed as a pointer to
an unmanaged API, and then get its string field assigned by the unmanaged API,
special processing will need to be done.

6.2 As usual an unmanaged representation of the
structure must be constructed somewhere in memory. And then a pointer to this
unmanaged structure is to be passed to the API.

6.3 Furthermore, because the spirit and intent of what
we are accomplishing now is to have the structure returned as an “out”
parameter, the structure must be initialized (to null values) properly before
being passed to the unmanaged API. This point will be emphasized in the example
codes which will be presented later on.

6.4 Let’s revisit the SetValueTestStruct01()
API :

void __stdcall SetValueTestStruct01(/*[out]*/ TestStruct01* ptest_struct_01)
{
strcpy (ptest_struct_01 -> m_szString, "From unmanaged code.");
}

6.5 The original C# declaration for
SetValueTestStruct01() is as follows (as per point 3.4) :

[DllImport("TestDLL01.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void SetValueTestStruct01([Out] out TestStruct01 test_struct_01);

6.6 Now, because the unmanaged API really does take a
pointer to an unmanaged TestStruct01 structure, we can import the same API
using a different declaration :

[DllImport("TestDLL01.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "SetValueTestStruct01")]
public static extern void SetValueTestStruct01_ByPointer(IntPtr ptest_struct_01);

6.7 This time, instead of passing in a managed
TestStruct01 structure to the API, we need to pass an IntPtr which points to an
unmanaged representation of a TestStruct01 structure. The following is a sample
code :

static void SetValueTestStruct01_ByPointer()
{
TestStruct01 test_struct_01 = new TestStruct01(); // Determine the size of the test_struct_01 for marshaling.
int iSizeOfTestStruct01 = Marshal.SizeOf(typeof(TestStruct01));
// Allocate in unmanaged memory a block of memory the size
// of an unmanaged TestStruct01 structure.
IntPtr ptest_struct_01 = Marshal.AllocHGlobal(iSizeOfTestStruct01); // Define an array of bytes with iSizeOfTestStruct01
// number of elements.
byte[] byZeroBytes = new byte[iSizeOfTestStruct01];
// Set all elements of the array to zero.
for (int i = 0; i < byZeroBytes.Length; i++)
{
byZeroBytes[i] = 0;
}
// Transfer the zero bytes from the byte array to
// the unmanaged memory which now serves as the
// unmanaged counterpart to test_struct_01.
// We need to do this because we do not want to
// pass a structure that contains garbage data.
Marshal.Copy(byZeroBytes, 0, ptest_struct_01, iSizeOfTestStruct01); // Call the API using a pointer to the unmanaged test_struct_01.
SetValueTestStruct01_ByPointer(ptest_struct_01);
// We now transfer the contents of the unmanaged TestStruct01
// (pointed to by ptest_struct_01) to the managed memory
// of test_struct_01.
test_struct_01 = (TestStruct01)Marshal.PtrToStructure(ptest_struct_01, typeof(TestStruct01));
// We must remember to destroy the test_struct_01 structure.
// Doing this will free any fields which are references
// to memory (e.g. m_strString).
Marshal.DestroyStructure(ptest_struct_01, typeof(TestStruct01));
// Finally, the block of memory allocated for the
// unmanaged test_struct_02 must itself be freed.
Marshal.FreeHGlobal(ptest_struct_01);
ptest_struct_01 = IntPtr.Zero; DisplayTestStruct01(test_struct_01);
}

6.8 Basically, the marshaling must be done completely
manually. The following are pertinent points concerning the code above
:

  • A managed TestStruct01 structure is to be allocated.
    Note that we will be calling an API that will set the “m_strString” field value
    of this structure and so we need not assign any value to this
    field.
  • After the structure has been allocated, an
    unmanaged representation of this structure will need to be allocated and
    initialized properly.
  • To do this, we need to calculate the size of the
    unmanaged representation. This is done by using Marshal.SizeOf() which uses the
    StructLayoutAttribute of the structure and various MarshalAsAttributes assigned
    to the fields to perform the calculation.
  • With this size calculated, we can proceed to create the
    unmanaged representation by allocating in memory such a structure. This is
    done by using Marshal.AllocHGlobal(). Note that Marshal.AllocCoTaskMem() may be
    used as well. An IntPtr will be returned. This structure will have a memory
    format as depicted in the structure of point 3.2.
  • Next, we need to ensure that the fields of this
    unmanaged TestStruct01 are completely zeroed. This is to ensure that when
    we pass this structure to the unmanaged API, the fields do not contain
    random data.
  • We do this by preparing a byte array the length of which
    equals the number of bytes taken up by the unmanaged TestStruct01. We then zero
    all bytes of this array. Thereafter, we copy all the bytes of this array to the
    bytes of the unmanaged TestStruct01 using the Marshal.Copy()
    method.
  • After the field zeroing out has been done, the unmanaged
    representation of the TestStruct01 structure is complete. The IntPtr may be
    passed to the SetValueTestStruct01_ByPointer() API.
  • The SetValueTestStruct01_ByPointer() API will assign
    character values into the m_szString field of the structure.
  • Now, remember that the spirit and intent of calling
    SetValueTestStruct01_ByPointer() is to have this API set the field values for
    the structure. Hence when the API returns, we need to transfer the contents of
    the unmanaged structure (which may have been modified) to that of the managed
    version. This is done by using Marshal.PtrToStructure().
  • When Marshal.PtrToStructure() is done, the managed
    TestStruct01 structure will contain the same string value as that of the
    unmanaged representation.
  • Next comes the important task of memory deallocation :
    that of the fields of the unmanaged structure (which are not inline and
    embedded as part of the structure itself), and that of the unmanaged structure
    itself.
  • Fields of a structure which are not embedded as
    part of the structure are usually references to memory elsewhere. These
    must be destroyed with the help of the Marshal.DestroyStructure() method.
    This method clears the non-embedded fields with help
    from the MarshalAsAttributes assigned to the fields.
  • Now, with a field like TestStruct01.m_szString which is
    an inline embedded array of 21 ANSI characters, nothing needs to be done. The
    memory occuppied by this field is automatically recovered when the structure
    itself is recovered.
  • If we were dealing with TestStruct02, then with a field
    like TestStruct02.m_pszString (LPCSTR), the memory pointed to by this field,
    which will be allocated when Marshal.StructureToPtr() is called,
    Marshal.DestroyStructure() will recover the memory occuppied by the buffer
    pointed to by TestStruct02.m_pszString.
  • If we were dealing with TestStruct03, then with a field
    like TestStruct03.m_bstr (BSTR), the BSTR pointed to by this field, which will
    be allocated when Marshal.StructureToPtr() is called, Marshal.DestroyStructure()
    will free the BSTR pointed to by TestStruct03.m_bstr.

6.9 To help to test cases where we pass TestStruct02 and
TestStruct03 by pointers, I have prepared the following code constructs
for testing purposes :

[DllImport("TestDLL01.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "SetValueTestStruct01")]
public static extern void SetValueTestStruct01_ByPointer(IntPtr ptest_struct_01); [DllImport("TestDLL01.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "SetValueTestStruct02")]
public static extern void SetValueTestStruct02_ByPointer(IntPtr ptest_struct_02); [DllImport("TestDLL01.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "SetValueTestStruct03")]
public static extern void SetValueTestStruct03_ByPointer(IntPtr ptest_struct_03); delegate void Delegate_SetValueTestStruct0x_ByPointer(IntPtr ptest_struct_0x); delegate void Delegate_DisplayTestStruct0x<T>(T t); static void SetValueTestStruct0x_ByPointer<T>
(
Delegate_SetValueTestStruct0x_ByPointer pSetFunction,
Delegate_DisplayTestStruct0x<T> pDisplayFunction
) where T : new()
{
T test_struct_0x = new T(); // Determine the size of T for marshaling.
int iSizeOfTestStruct0x = Marshal.SizeOf(typeof(T));
// Allocate in unmanaged memory a block of memory the size
// of an unmanaged T structure.
IntPtr ptest_struct_0x = Marshal.AllocHGlobal(iSizeOfTestStruct0x); // Define an array of bytes with iSizeOfTestStruct0x
// number of elements.
byte[] byZeroBytes = new byte[iSizeOfTestStruct0x];
// Set all elements of the array to zero.
for (int i = 0; i < byZeroBytes.Length; i++)
{
byZeroBytes[i] = 0;
}
// Transfer the zero bytes from the byte array to
// the unmanaged memory which now serves as the
// unmanaged counterpart to T.
// We need to do this because we do not want to
// pass a structure that contains garbage data.
Marshal.Copy(byZeroBytes, 0, ptest_struct_0x, iSizeOfTestStruct0x); // Call the API using a pointer to the unmanaged test_struct_0x.
pSetFunction(ptest_struct_0x);
// We now transfer the contents of the unmanaged T
// (pointed to by ptest_struct_0x) to the managed memory
// of test_struct_0x.
test_struct_0x = (T)Marshal.PtrToStructure(ptest_struct_0x, typeof(T));
// We must remember to destroy the test_struct_0x structure.
// Doing this will free any fields which are references
// to memory (e.g. m_strString).
Marshal.DestroyStructure(ptest_struct_0x, typeof(T));
// Finally, the block of memory allocated for the
// unmanaged test_struct_0x must itself be freed.
Marshal.FreeHGlobal(ptest_struct_0x);
ptest_struct_0x = IntPtr.Zero; pDisplayFunction(test_struct_0x);
}

I have provided declarations for the versions of
the SetValueTestStruct02() and the SetValueTestStruct03() APIs which take IntPtr
as parameter (i.e. SetValueTestStruct02_ByPointer() and
SetValueTestStruct03_ByPointer()).

I have also provided a delegate declaration for invoking
these APIs () (Delegate_SetValueTestStruct0x_ByPointer).

I have also provided a generic delegate declaration for
the APIs which display TestStruct01, TestStruct02 and TestStruct03 (i.e.
Delegate_DisplayTestStruct0x<T>).

Finally, I have defined a generic function for
generically performing the exact equivalent of the code in point 6.5.

6.10 For more details on passing a
pointer to a structure from managed to unmanaged code, please refer to
:

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

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

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

7. In Conclusion.

7.1 Here in part 2, we have examined how to pass a
structure (which contains a string field) from managed to unmanaged code as an
“out” (return) parameter.

7.2 We have learnt the 3 possible ways to express a
string in unmanage code : as an inline character array, as a pointer to a
character array, as a BSTR. We have also seen how each of these 3 types of
strings may be assigned in unmanaged code.

7.3 The importance of directionality of marshaling has
been duely emphasized.

7.4 We also touched a little on the special cases where
the structures are passed by pointer.

7.5 In part 3, we shall study the same structures but
with the structures passed as both “in” and “out” (by reference)
parameters.