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 a 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 :
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]. ", 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 :
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]. ", 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 :
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]. ", 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 :
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]. ", 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 :
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 :
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.