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

时间:2022-03-20 19:08:49

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 Then in part
2
, we studied the techniques for receiving such a structure from
unmanaged code as an “out” parameter.

1.3 Here in part 3, I shall expound on passing a
structure (which contain strings) to and from unmanaged code. That is, as a
parameter passed in both “in” and “out” directions (or
reference parameter).

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 No matter the direction of parameter
passing, the ways that a string structure member can be 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 Note that this time, the marshaling direction
will be 2-ways. That is, from the managed side to the unmanaged side and
reverse. Hence the string allocated on the managed side will be visible on
the unmanaged side. Furthermore, the string allocated on the
unmanaged side and returned “outwardly” to the managed side will also be
marshaled.

2.4 Throughout the various examples below,
attention will be given to the effects of marshaling an “in” and “out”
parameter.

3. Passing 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 And 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 “in” and “out” parameter is listed below :

void __stdcall ReferenceTestStruct01
(
/*[in, out]*/ TestStruct01* ptest_struct_01
)
{
printf
(
"Value of ptest_struct_01 -> m_szString before change : [%s].\r\n",
ptest_struct_01 -> m_szString
);
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 ReferenceTestStruct01([In][Out] ref TestStruct01 test_struct_01);

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

static void ReferenceTestStruct01()
{
TestStruct01 test_struct_01 = new TestStruct01(); test_struct_01.m_strString = "From managed code."; ReferenceTestStruct01(ref test_struct_01); DisplayTestStruct01(test_struct_01);
}

This time, the test_struct_01 structure is
both instantiated and initialized before the call to
ReferenceTestStruct01(). This is a necessary action because
“test_struct_01″ is to be passed as a “ref” parameter which means that
its value is both passed to and set by the ReferenceTestStruct01()
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 ReferenceTestStruct01() 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.
  • But wherever it originated, 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 ReferenceTestStruct01() API.
  • This time, being passed as both “in” and “out”, the
    unmamaged test_struct_01 structure will contain its initialized field
    values. Hence the ptest_struct_01 -> m_szString will contain the string
    “From managed code.”
  • The API will then re-assign the character
    values of ptest_struct_01 -> m_szString.
  • 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. This is the
    effect of being an “out” parameter.
  • The managed “test_struct_01″ structure will now
    be completely re-set by the ReferenceTestStruct01() API.
  • 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 following expected output will be displayed on
the console :

Value of ptest_struct_01 -> m_szString before change : [From managed code.].
test_struct_01.m_szString : [From unmanaged code.].

3.8 Whenever we deal with a structure with a string
field typed as an inline embedded character array, things are pretty
simple. 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 and performs the marshaling of field values to
the unmanaged structure. When the unmanaged API returned, the interop
marshaler transcribes the new character values of the inline
character array field to the corresponding string field of the managed
structure. The interop marshaler then either destroys the unmanaged structure or
re-uses the memory occuppied by it.

4. Passing a 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 “in” and “out” parameter is listed below :

void __stdcall ReferenceTestStruct02(/*[in, out]*/ TestStruct02* ptest_struct_02)
{
printf ("Value of ptest_struct_02 -> m_pszString before change : [%s].\r\n", ptest_struct_02 -> m_pszString); if (ptest_struct_02 -> m_pszString)
{
::CoTaskMemFree((LPVOID)(ptest_struct_02 -> m_pszString));
ptest_struct_02 -> m_pszString = NULL;
}
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 ReferenceTestStruct02([In][Out] ref TestStruct02 test_struct_02);

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

static void ReferenceTestStruct02()
{
TestStruct02 test_struct_02 = new TestStruct02(); test_struct_02.m_strString = "From managed code."; ReferenceTestStruct02(ref test_struct_02); DisplayTestStruct02(test_struct_02);
}

Just like the example we met in section 3, the
test_struct_02 structure is both instantiated and initialized before the call to
ReferenceTestStruct02(). This is a necessary action because test_struct_02 is to
be passed as a “ref” parameter which means that its value is both passed to and
set by the ReferenceTestStruct02() 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 ReferenceTestStruct02() API call :

  • When the ReferenceTestStruct02() API was called, the
    interop marshaler will allocate a buffer with space large enough to hold the
    structure which is listed in point 4.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.
  • But wherever it originated, this memory buffer serves to
    store a temporary unmanaged version of the TestStruct02
    structure.
  • The interop marshaler will then pass a pointer to this
    structure to the ReferenceTestStruct02() API.
  • This time, being passed as both “in” and “out”, the
    unmamaged test_struct_02 structure must contain the initialized field
    values of the managed test_struct_02 structure.
  • Using the current value in test_struct_02.m_strString
    (i.e. “From managed code.”), the interop marshaler will allocate (using
    Marshal.StringToCoTaskMemAnsi()) a sufficiently-sized character buffer and then
    copy the managed string to it. A pointer to this buffer will be assigned to the
    m_pszString field of the unmanaged structure.
  • This is done before ReferenceTestStruct02() is
    invoked.
  • Hence when code control reaches ReferenceTestStruct02(),
    and ptest_struct_02 -> m_pszString is to be printed, “From managed
    code.” will be displayed on the console.
  • The API will then re-assign ptest_struct_02 ->
    m_pszString by first freeing the buffer that it points to (using
    ::CoTaskMemFree()).
  • Note that ::CoTaskMemFree() must be used because it
    matches Marshal.AllocCoTaskMem().
  • The API will then re-allocate a new buffer using
    ::CoTaskMemAlloc() and make ptest_struct_02 -> m_pszString point to
    it.
  • A new string will be assigned to the new
    buffer.
  • Then when the API returns, the interop marshaler will
    assign the string contained in the buffer pointed to by ptest_struct_02
    -> m_pszString to the counterpart m_strString field of the managed
    test_struct_02 structure. This is the effect of being an “out”
    parameter.
  • The managed test_struct_02 structure will now be
    completely re-set by the ReferenceTestStruct02() API.
  • 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.

4.7 The following expected output will be displayed on
the console screen :

Value of ptest_struct_02 -> m_pszString before change : [From managed code.].
test_struct_02.m_pszString : [From unmanaged code.].

4.8 When we pass by reference a structure with a
string field typed as a pointer to a NULL-terminated character array, the
unmanaged code shares some responsibility in the re-allocation of memory for the
unmanaged string field. The memory allocation and deallocation must be done
using ::CoTaskMemAlloc() and ::CoTaskMemFree(). This is the required
protocol.

4.9 Note well that the managed client code need not
always pre-assign value to test_struct_02.m_strString before calling
ReferenceTestStruct02(). If a NULL pointer was set for ptest_struct_02 ->
m_pszString, then ReferenceTestStruct02() need not call
::CoTaskMemFree().

4.10 In the same way, if, on return from
ReferenceTestStruct02(), ptest_struct_02 -> m_pszString is NULL, the interop
marshaler will not free it. However, this will effective set the managed
test_struct_02.m_strString field to an empty string.

4.11 If ReferenceTestStruct02() simply
leaves ptest_struct_02 -> m_pszString alone and does not free it nor
re-assign it, then on return, the interop marshaler will use whatever buffer
that ptest_struct_02 -> m_pszString points to (as if it was freshly
allocated by ReferenceTestStruct02()) to assign to test_struct_02.m_strString.
It will then free the buffer pointed to by ptest_struct_02 ->
m_pszString.

5. Passing 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 “in” and “out” parameter is listed below :

void __stdcall ReferenceTestStruct03(/*[in, out]*/ TestStruct03* ptest_struct_03)
{
printf ("Value of ptest_struct_03 -> m_bstr before change : [%S].\r\n", ptest_struct_03 -> m_bstr); if (ptest_struct_03 -> m_bstr)
{
::SysFreeString(ptest_struct_03 -> m_bstr);
ptest_struct_03 -> m_bstr = NULL;
} 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 ReferenceTestStruct03([In][Out] ref TestStruct03 test_struct_03);

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

static void ReferenceTestStruct03()
{
TestStruct03 test_struct_03 = new TestStruct03(); test_struct_03.m_strString = "From managed code."; ReferenceTestStruct03(ref test_struct_03); DisplayTestStruct03(test_struct_03);
}

Just like the example we met in section 3 and 4, the
test_struct_03 structure is both instantiated and initialized before the call to
ReferenceTestStruct03(). This is a necessary action because test_struct_03 is to
be passed as a “ref” parameter which means that its value is both passed to and
set by the ReferenceTestStruct03() 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 ReferenceTestStruct03() API call :

  • When the ReferenceTestStruct03() API was called, the
    interop marshaler will allocate a buffer with space large enough to hold the
    structure which is listed in point 5.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.
  • But wherever it originated, this memory buffer serves to
    store a temporary unmanaged version of the TestStruct03
    structure.
  • The interop marshaler will then pass a pointer to this
    structure to the ReferenceTestStruct03() API.
  • This time, being passed as both “in” and “out”, the
    unmamaged test_struct_03 structure must contain the initialized field values of
    the managed test_struct_03 structure.
  • Using the current value in test_struct_03.m_strString
    (i.e. “From managed code.”), the interop marshaler will allocate (using
    Marshal.StringToBSTR()) a BSTR with matching characters. A pointer to
    this BSTR will be assigned to the m_bstr field of the unmanaged
    structure.
  • This is done before ReferenceTestStruct03() is
    invoked.
  • Hence when code control reaches ReferenceTestStruct03(),
    and ptest_struct_03 -> m_bstr is to be printed, “From managed code.” will be
    displayed on the console.
  • The API will then re-assign ptest_struct_03 -> m_bstr
    by first freeing the BSTR that it points to (using
    ::SysFreeString()).
  • Note that ::SysFreeString() must be used because it
    matches Marshal.StringToBSTR().
  • The API will then re-allocate a new BSTR (“BSTR
    from unmanaged code.”) using ::SysAllocString() and make ptest_struct_03 ->
    m_bstr point to it.
  • Then when the API returns, the interop marshaler will
    assign the BSTR pointed to by ptest_struct_03 -> m_bstr to the
    counterpart m_strString field of the managed test_struct_03 structure. This is
    the effect of being an “out” parameter.
  • The managed test_struct_03 structure will now be
    completely re-set by the ReferenceTestStruct03() API.
  • 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.

5.7 The following expected output will be displayed on
the console screen :

Value of ptest_struct_03 -> m_bstr before change : [From managed code.].
test_struct_03.m_bstr : [BSTR from unmanaged code.].

5.8 Just like the case in section 4 where we used a
LPCSTR, when we pass by reference a structure with a string field typed as
a BSTR, the unmanaged code shares some responsibility in the re-allocation of
memory for the BSTR. The memory allocation and deallocation must be done using
::SysAllocString() and ::SysFreeString(). This is the required
protocol.

5.9 Again, just like the case in section
4, the managed client code need not always pre-assign value to
test_struct_03.m_strString before calling ReferenceTestStruct03(). If a NULL
pointer was set for ptest_struct_03 -> m_bstr, then ReferenceTestStruct03()
need not call ::SysFreeString().

5.10 In the same way, if, on return from
ReferenceTestStruct03(), ptest_struct_03 -> m_bstr is NULL, the interop
marshaler will not free it. However, this will effective set the managed
test_struct_03.m_strString field to an empty string.

5.11 If ReferenceTestStruct03() simply leaves
ptest_struct_03 -> m_bstr alone and does not free it nor re-assign it, then
on return, the interop marshaler will use whatever buffer that ptest_struct_03
-> m_bstr points to (as if it was freshly allocated by
ReferenceTestStruct03()) to assign to test_struct_03.m_strString. It will then
free the BSTR pointed to by ptest_struct_03 -> m_bstr.

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 passed to the API in 2
directions “in” and “out” (in other words, as a reference “ref”
parameter), the unmanaged structure must be initialized (to the exact
values of the managed version) before being passed to the API. This point will
be emphasized in the example codes which will be presented later on.

6.4 Let’s revisit the ReferenceTestStruct01()
API :

void __stdcall ReferenceTestStruct01(/*[in, out]*/ TestStruct01* ptest_struct_01)
{
printf ("Value of ptest_struct_01 -> m_szString before change : [%s].\r\n", ptest_struct_01 -> m_szString);
strcpy (ptest_struct_01 -> m_szString, "From unmanaged code.");
}

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

[DllImport("TestDLL01.dll", CallingConvention = CallingConvention.StdCall)]
public static extern void ReferenceTestStruct01([In][Out] ref 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 (i.e.
ReferenceTestStruct01()) using a different declaration :

[DllImport("TestDLL01.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "ReferenceTestStruct01")]
public static extern void ReferenceTestStruct01_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 ReferenceTestStruct01_ByPointer()
{
// Create TestStruct01 and assign value to its field as usual.
TestStruct01 test_struct_01 = new TestStruct01(); test_struct_01.m_strString = "From managed code."; // Determine the size of the TestStruct01 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);
// Transfer the contents of the managed TestStruct01
// (i.e. test_struct_01) to the unmanaged memory
// which now serves as the unmanaged representation
// of test_struct_01.
Marshal.StructureToPtr(test_struct_01, ptest_struct_01, false);
// Call the API using a pointer to the unmanaged test_struct_01.
ReferenceTestStruct01_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.
//
// Remember that the spirit and intent of the
// ReferenceTestStruct01_ByPointer() API is to
// pass a TestStruct01 structure 2 ways : "in" and "out".
// Hence whatever changes done to the structure
// inside the API must be reflected on return.
test_struct_01 = (TestStruct01)Marshal.PtrToStructure(ptest_struct_01, typeof(TestStruct01));
// We now display the contents of the managed test_struct_01.
DisplayTestStruct01(test_struct_01);
// We must remember to destroy the unmanaged test_struct_01 structure.
// Doing this will free any fields which are references
// to memory.
Marshal.DestroyStructure(ptest_struct_01, typeof(TestStruct01));
// Finally, the block of memory allocated for the
// unmanaged test_struct_01 must itself be freed.
Marshal.FreeHGlobal(ptest_struct_01);
ptest_struct_01 = IntPtr.Zero;
}

This code will produce the following output on the
console :

Value of ptest_struct_01 -> m_szString before change : [From managed code.].
test_struct_01.m_szString : [From unmanaged code.].

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 and
    its string field assigned.
  • An unmanaged representation of this structure will need
    to be allocated and initialized properly with field values from the managed
    version.
  • We first 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 unmanaged TestStruct01
    structure is a replica of its managed counterpart. That is, all field values of
    the managed structure must be copied to the same values in the corresponding
    unmanaged fields. After all, remember that the structure is to be passed as
    an “in” parameter.
  • We do this by using the Marshal.StructureToPtr()
    method.
  • After this is done, the unmanaged representation of the
    TestStruct01 structure is complete. The IntPtr may be passed to the
    ReferenceTestStruct01_ByPointer() API.
  • The ReferenceTestStruct01_ByPointer() API will use the
    passed in value of ptest_struct_01 -> m_szString to display the
    contained string. After that it will assign character values into the same
    string buffer.
  • Now, remember that the spirit and intent of calling
    ReferenceTestStruct01_ByPointer() is to have this API display and then 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 changed) 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 :

static void InitializeStruct(ref TestStruct01 test_struct_01, string strInitial)
{
test_struct_01.m_strString = strInitial;
} static void InitializeStruct(ref TestStruct02 test_struct_02, string strInitial)
{
test_struct_02.m_strString = strInitial;
} static void InitializeStruct(ref TestStruct03 test_struct_03, string strInitial)
{
test_struct_03.m_strString = strInitial;
} delegate void Delegate_InitializeStruct<T>(ref T t, string strInitial); delegate void Delegate_DisplayTestStruct0x<T>(T t); [DllImport("TestDLL01.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "ReferenceTestStruct01")]
public static extern void ReferenceTestStruct01_ByPointer(IntPtr ptest_struct_01); [DllImport("TestDLL01.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "ReferenceTestStruct02")]
public static extern void ReferenceTestStruct02_ByPointer(IntPtr ptest_struct_02); [DllImport("TestDLL01.dll", CallingConvention = CallingConvention.StdCall, EntryPoint = "ReferenceTestStruct03")]
public static extern void ReferenceTestStruct03_ByPointer(IntPtr ptest_struct_03); delegate void Delegate_ReferenceTestStruct0x_ByPointer(IntPtr ptest_struct_0x); static void ReferenceTestStruct0x_ByPointer<T>
(
Delegate_InitializeStruct<T> pInitializer,
Delegate_ReferenceTestStruct0x_ByPointer pReferenceFunction,
Delegate_DisplayTestStruct0x<T> pDisplayFunction
) where T : new()
{
// Create an instance of T and assign value to its field as usual.
T test_struct_0x = new T(); pInitializer(ref test_struct_0x, "From managed code."); // 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);
// Transfer the contents of the managed T
// (i.e. test_struct_0x) to the unmanaged memory
// which now serves as the unmanaged representation
// of T.
Marshal.StructureToPtr(test_struct_0x, ptest_struct_0x, false);
// Call the API using a pointer to the unmanaged test_struct_0x.
pReferenceFunction(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.
//
// Remember that the spirit and intent of the
// ReferenceTestStruct0x_ByPointer() API is to
// pass a T structure 2 ways : "in" and "out".
// Hence whatever changes done to the structure
// inside the API must be reflected on return.
test_struct_0x = (T)Marshal.PtrToStructure(ptest_struct_0x, typeof(T));
// We now display the contents of the managed test_struct_01.
pDisplayFunction(test_struct_0x);
// We must remember to destroy the unmanaged test_struct_0x structure.
// Doing this will free any fields which are references
// to memory.
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;
}

The above code provides the following :

  • 3 overloaded initializer functions (InitializeStruct()),
    one each for the structs TestStruct01, TestStruct02 and TestStruct03. We have
    met these 3 functions previously in part 1.
  • Delegate Delegate_InitializeStruct<T> which is
    meant to generically point to one of the initializer
    functions.
  • Delegate Delegate_DisplayTestStruct0x<T> which is
    meant to point to one of the versions of the DisplayTestStructXX()
    APIs.
  • C# declarations for the
    ReferenceTestStruct01_ByPointer(), ReferenceTestStruct02_ByPointer() and
    ReferenceTestStruct03_ByPointer() APIs.
  • Delegate Delegate_ReferenceTestStruct0x_ByPointer
    which is meant to point to one of the ReferenceTestStructXX_ByPointer() APIs
    (i.e. ReferenceTestStruct01_ByPointer(), ReferenceTestStruct02_ByPointer() or
    ReferenceTestStruct02_ByPointer()).
  • ReferenceTestStruct0x_ByPointer<T>() which
    generically performs the equivalent of the ReferenceTestStruct01_ByPointer()
    function (listed in point 6.7) but for all the test
    structs.

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 3, we have examined how to pass a
structure (which contains a string field) from managed to unmanaged code as an
“in” and “out” (ref) parameter.

7.2 We have essentially combined the code actions done
in part 1 and part 2. The dual directionality of marshaling has been duely
emphasized.

7.3 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 passed to and returned from unmanaged code.

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

7.5 Throughout this series of blogs, I hope to
have demonstrated to the reader the basic principles behind marshaling :
that managed data structures must first be transformed into unmanaged
equivalents before they can be passed to unmanaged code. And that data to be
received from unmanaged code must be transformed to its managed equivalent
before it can be used in managed code.

7.6 It may take a while for the newbie to sink this in.
But it is certainly consistent and repeatable.