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 struc... ...
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.