Passing Nested Structures by Using Custom Marshalling Code


As previously noted, the .NET Compact Framework cannot automatically marshal deep structures between the native and managed worlds . If you are in a position where you absolutely must marshal a deep structure between native and managed code, then you will have to write custom marshalling code. This sounds daunting, and it is tricky.

The fundamental idea to writing custom marshalling code is that the nested structure has a specific layout in memory. On the native side, creating a nested structure means that you have deliberately created this layout and populated it with values. On the managed side, you must duplicate the exact same memory layout with managed structures and then pass a reference to the main managed structure to the native side. When the native code accesses the structure, it makes assumptions about where in memory the data members reside. Since you have duplicated the memory layout with your managed structures, everything works fine.

THE unsafe KEYWORD

In order to do custom marshalling, the managed code must explicitly deal with memory layouts and pointers to specific memory locations. This is allowed in C# by using the unsafe keyword. Blocks of code in an unsafe block allow you to use real memory pointers to access memory directly. Doing this is not a recommended practice, and it cannot be done at all with Visual Basic, because there is no notion at all of a memory pointer in Visual Basic.


This is easy to say but harder to do. The idea will be illustrated by working through a tutorial.

The tutorial starts with a look at the native code that will be called into:

 
 struct AllIntsInside {    int m_one;    int m_two;    int m_three; }; struct DeepStruct {    char * m_message;    AllIntsInside * m_someInts; }; // Caution! Very unsafe code here used for demonstration purposes only. // inout_DeepStruct is assumed to exist as a valid pointer, and m_message is a string // with at least 1 character void __cdecl ManipulateDeepStruct(DeepStruct * inout_DeepStruct) {    inout_DeepStruct->m_message[0] = 'H';    inout_DeepStruct->m_someInts->m_one++;    inout_DeepStruct->m_someInts->m_two++;    inout_DeepStruct->m_someInts->m_three++; } 

The function to be called is named ManipulateDeepStruct . It is passed a DeepStruct . It changes the first character of the string to an "H" and increments each integer in the AllIntsInside structure that DeepStruct.m_someInts points to.

Instances of DeepStruct cannot be automatically marshalled by the .NET Compact Framework because it holds a pointer to another struct and a pointer to a null- terminated string. In order to access the ManipulateDeepStruct native function, the same memory layout used on the native side must be simulated in managed code.

The first thing needed in the managed world is a way to turn strings into null-terminated byte arrays and vice versa. The following two methods perform this task. Notice the use of the unsafe keyword because the manage code is actually accessing pointers to memory.

 
 C# // Puts a null terminator on a string, which makes it usable to // native c-style code private char[] NullTerminateString(string in_managedString) {    in_managedString += " 
 C# // Puts a null terminator on a string, which makes it usable to // native c-style code private char[] NullTerminateString(string in_managedString) { in_managedString += "\0"; return (in_managedString.ToCharArray()); } private unsafe string NativeStringToManaged(char* in_NativeNullTerminatedString) { System.Text.StringBuilder l_SB = new System.Text.StringBuilder(100); int i = 0; while (in_NativeNullTerminatedString[i] != '\0') { char[] l_toInsert = new char[1]; l_toInsert[0] = in_NativeNullTerminatedString[i]; l_SB.Insert(i, l_toInsert); i++; } return l_SB.ToString(); } 
"; return (in_managedString.ToCharArray()); } private unsafe string NativeStringToManaged(char* in_NativeNullTerminatedString) { System.Text.StringBuilder l_SB = new System.Text.StringBuilder(100); int i = 0; while (in_NativeNullTerminatedString[i] != '
 C# // Puts a null terminator on a string, which makes it usable to // native c-style code private char[] NullTerminateString(string in_managedString) { in_managedString += "\0"; return (in_managedString.ToCharArray()); } private unsafe string NativeStringToManaged(char* in_NativeNullTerminatedString) { System.Text.StringBuilder l_SB = new System.Text.StringBuilder(100); int i = 0; while (in_NativeNullTerminatedString[i] != '\0') { char[] l_toInsert = new char[1]; l_toInsert[0] = in_NativeNullTerminatedString[i]; l_SB.Insert(i, l_toInsert); i++; } return l_SB.ToString(); } 
') { char[] l_toInsert = new char[1]; l_toInsert[0] = in_NativeNullTerminatedString[i]; l_SB.Insert(i, l_toInsert); i++; } return l_SB.ToString(); }

NullTerminateString simply adds a null at the end of the string and then converts it to a character array. NativeStringToManaged uses a StringBuilder to insert the characters of the in_NativeNullTerminatedString one-by-one and then returns the string held inside the StringBuilder .

Here is the managed code definition for the ManipulateDeepStruct function:

 
 C# [DllImport("ManipulateStruct.dll")] private static extern void ManipulateDeepStruct(ref DeepStruct         inout_ShallowStruct); 

The next thing needed is a managed version of the native AllIntsInside structure. This turns out to be easy:

 
 C# public struct AllIntsInside {    public int m_one;    public int m_two;    public int m_three; } 

There also needs to be a managed code version of DeepStruct . This is also easy, but the unsafe keyword must be used in order to be allowed to use pointers:

 
 public unsafe struct DeepStruct {    public char * m_message;    public AllIntsInside * m_someInts; } 

The managed DeepStruct is just like the native version. There is a pointer to char and a pointer to an AllIntsInside structure. To call the native ManipulateDeepStruct function, instantiate an instance of DeepStruct and pass a reference to it into ManipulateDeepStruct . But how does one instantiate a valid DeepStruct whose internal pointers are also valid? A block of code from the MarshalDeepStruct sample application holds the answer:

 
 DeepStruct l_DeepStruct; l_DeepStruct = new DeepStruct(); char [] l_messageBuffer = NullTerminateString(" ello World!"); AllIntsInside l_Ints = new AllIntsInside(); l_Ints.m_one = 1; l_Ints.m_two = 2; l_Ints.m_three = 3; unsafe {    fixed (char * l_ptrMessageBuf = &l_messageBuffer[0])    {       l_DeepStruct.m_message = l_ptrMessageBuf;       l_DeepStruct.m_someInts = &l_Ints;       MessageBox.Show(NativeStringToManaged(l_DeepStruct.m_message), "BEFORE P/INVOKE");       String l_IntValuesString = "Int Values: "               + Convert.ToString(l_DeepStruct.m_someInts->m_one)          + "," + Convert.ToString(l_DeepStruct.m_someInts->m_two) + ","          + Convert.ToString(l_DeepStruct.m_someInts->m_three);       MessageBox.Show(l_IntValuesString, "BEFORE P/INVOKE");       ManipulateDeepStruct(ref l_DeepStruct);       MessageBox.Show(NativeStringToManaged(l_DeepStruct.m_message), "AFTER P/INVOKE");       l_IntValuesString = "Int Values: "               + Convert.ToString(l_DeepStruct.m_someInts->m_one) + ","          + Convert.ToString(l_DeepStruct.m_someInts->m_two) + ","          + Convert.ToString(l_DeepStruct.m_someInts->m_three);    MessageBox.Show(l_IntValuesString, "AFTER P/INVOKE");    } } 

The code is divided into two chunks : the part outside of the unsafe block and the part inside. The outside part creates a new instance of a DeepStruct, l_DeepStruct . Right now, l_DeepStruct does not have valid values in its member pointers. The block also creates a null-terminated character array, l_MessageBuffer , which holds the value Hello World! and an instance of AllIntsInside named l_Ints . The member variables of l_Ints are set to 1 , 2 , and 3 .

The first thing that happens in the unsafe block is to acquire a pointer to l_messageBuffer and store it into l_DeepStruct.m_message . The code stores the address of the l_Ints structure into l_DeepStruct.m_someInts . Now the l_DeepStruct structure is correctly instantiated .

Note that this code is also inside a fixed block that prevents l_ptrMessageBuf from being moved by the managed memory allocation system while the block is executing. This is necessary because if the managed memory is moved around at this time, then l_DeepStruct would no longer hold valid pointers.

The next block of code uses a MessageBox to display the character array value and the integer values pointed to by l_DeepStruct . The native function ManipulateDeepStruct is then called. Finally, the contents of l_DeepStruct are again displayed with a MessageBox. The end user will notice the side effect caused by the native code.

Marshalling Deep Structures with the MarshalDeepStruct Sample Application

The MarshalDeepStruct sample application is available in the folder \SampleApplications\ Chapter12\MarshalDeepStruct_CSharp . There is only a C# version of this sample because of the language limitations of Visual Basic.NET. MarshalDeepStruct relies on the ManipulateStruct.dll native library, which is available in the folder \SampleApplications\Chapter12\NativeBinaries\ ManipulateStruct . There are binaries for a variety of CPU types and Windows CE versions.

MarshalDeepStruct is an end-to-end application that implements the code in the preceding tutorial. To use it, copy the appropriate version of ManipulateStruct.dll to the device, either in \Windows or the same directory as the managed executable. When you launch the application, it shows only a form with a single button labeled Manipulate Deep Struct. When you click this button, the code discussed in the tutorial of the previous section executes. You will see a message box displaying the contents of a DeepStruct before and after calling the native ManipulateDeepStruct() function.



Microsoft.NET Compact Framework Kick Start
Microsoft .NET Compact Framework Kick Start
ISBN: 0672325705
EAN: 2147483647
Year: 2003
Pages: 206

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net