Marshalling the Fundamental Data Types


The previous section explained the fundamentals of calling into a native DLL. It touched on such topics as where to put the DLL, what the restrictions are for making a call, and how to declare a native function as part of a managed class. A simple example proved that everything worked as expected.

ERROR HANDLING IN THE SAMPLE APPLICATIONS

Note this application and all of the others in this chapter are made to be as simple as possible while demonstrating the intended concepts. Thus, there is minimal input checking or error handling code, which can clog the project with extra code and detract from the central concept being demonstrated. For example, if you enter an alphabetic character into a field that is expected to be numeric, then you are likely to see an exception. Obviously, true production code should have much more error handling code built into it.


It is now time to dig deeper into the subject of marshalling, the act of moving function call arguments and return values back and forth between the managed runtime and the native side. This section discusses how to marshal fundamental data types. That is, those data types that are not part of a more complex structure. When marshalling fundamental data types, there are certain rules and definitions that must be adhered to. The two most important rules are discussed in the paragraphs that follow.

Fundamental data types can be passed by value or by reference. When a data type is passed by value, a copy of the data is made when it is copied across the native/managed code boundary. When a data type is passed by reference, only the address of the data object in the managed world is passed to the native side. The native code accesses the data object by referring to the address that was passed to it.

In the .NET Compact Framework, only data objects that are 32 bits or smaller in size may be passed by value. Anything larger must be passed by reference. Attempting to pass a data object larger than 32 bits long will result in a NotSupportedException . For example, long integers, which are 64 bits wide, must be passed by reference.

Data objects that are passed by reference to the native side can be altered by the native code. Thus, you can use pass by reference to create out or in/out parameters for calls into native code.

In order to be able to pass a fundamental data type into a native function, you must first declare the function by using DllImport (C#) or Declare Sub (VB), as described in the previous section. The next section shows how to set up declarations to pass a wide variety of fundamental data types by value and by reference. The easiest way to do this is by showing small snippets of code for the declarations and use of the native functions. As the text shows code snippets to demonstrate how to pass fundamental data types, we will call out special caveats for the specific data types.

Passing 32-bit Signed Integers

The 32-bit integer ( int in C#, Integer in Visual Basic) can be passed by value or by reference. Following are sample declarations to pass by value into the IntInFunc and sample usage of the IntInFunc :

 
 C# [DllImport("Foo.dll")] private static extern void IntInFunc(int in_Int); // Usage, where l_Int is an integer IntInFunc(l_Int) VB Declare Sub IntInFunc Lib "Foo.dll" (ByVal in_Int As Integer) ' Usage, where l_Int is an Integer IntInFunc(l_Int) 

Passing 32-bit Unsigned Integers

The C# keyword uint corresponds to the UInt32 data type, which is available in both C# and Visual Basic. This is a 32-bit data type. Sample declarations for pass by value into the UIntInFunc and sample usage of the UIntInFunc include the following:

 
 C# [DllImport("Foo.dll")] private static extern void UIntInFunc(uint in_UInt); // Usage, where l_UInt is an unsigned integer UIntInFunc(l_UInt); VB Declare Sub UIntInFunc Lib "Foo.dll" (ByVal in_UInt As UInt32) ' Usage, where l_UInt is an unsigned integer UIntInFunc(l_UInt) 

Passing Single Precision Floating-Point Types

The single precision floating-point data type ( float in C#, Single in Visual Basic) uses 64 bits to provide single precision floating-point representation of a number. As such, it cannot be passed by value and must be passed by reference. The following sample declaration for FloatInFunc shows how to set up to call into a native function that expects a reference to a float as input. The sample code also shows how to pass a reference to a floating-point value into the function.

 
 C# [DllImport("Foo.dll")] private static extern void FloatInFunc(ref float in_Float); // Usage... float in_Float = 4.00; FloatInFunc(ref in_Float); VB Declare Sub FloatInFunc Lib "Foo.dll" (ByRef in_Float As Single) ' Usage... Dim in_Float As Single = 4.00 FloatInFunc(in_Float) 

Passing Signed Long Integer Types

The long integer data type ( long in C#, Long in Visual Basic), also available as the Int64 type, uses 64-bit-wide representation for a signed integer. Thus, it can only be passed to native code by reference. The following is a sample declaration and usage of a function that is passed a reference to a long data type:

 
 C# [DllImport("Foo.dll")] private static extern void LongInFunc(ref long in_Long); // Usage... long in_Long = Convert.ToInt64(4); LongToString(ref in_Long, out_StringBuilder); VB Declare Sub LongInFunc Lib "Foo.dll" (ByRef in_Long As Long) ' Usage... Dim in_Long As Long = Convert.ToInt64(4) LongToString(in_Long, out_StringBuilder) 

Passing Unsigned Long Data Types

Unsigned long integer data types ( ulong in C#, UInt64 in C# and Visual Basic) is a 64-bit-wide data object that can only be passed by reference. The following is a sample declaration and usage of a function that is passed a reference to an unsigned long data type:

 
 C# [DllImport("FundamentalToString.dll")] private static extern void ULongToString(ref ulong in_ULong); // Usage... ulong in_ULong = Convert.ToUInt64(4); ULongToString(ref in_ULong, out_StringBuilder); VB Declare Sub ULongToString Lib "Foo.dll" (ByRef in_ULong As UInt64) ' Usage... Dim in_ULong As UInt64 = Convert.ToUInt64(4) ULongInFunc(in_ULong) 

Passing Short Integers

The short integer data type ( short in C#, Short in Visual Basic) uses 16 bits to represent a signed integer value. Thus, it can be passed by value or by reference. The following sample demonstrates how to declare a function that receives a short by value and includes a sample invocation:

 
 C# [DllImport("Foo.dll")] private static extern void ShortInFunc(short in_Short); // Usage... short in_Short = Convert.ToInt16(this.txtShortIn.Text); ShortInFunc(in_Short); VB Declare Sub ShortInFunc Lib "Foo.dll" (ByVal in_Short As Short) ' Usage... Dim in_Short As Short = Convert.ToInt16(Me.txtShortIn.Text) ShortInFunc(in_Short) 

Passing Unsigned Short Integers

The unsigned short integer ( ushort in C#, Uint16 in C# and Visual Basic) is like the short data type, except it uses its 16 bits to represent positive integers only, as seen in the following sample:

 
 C# [DllImport("Foo.dll")] private static extern void UShortInFunc(ushort in_UShort); // Usage... ushort in_UShort = Convert.ToUInt16(this.txtUShortIn.Text); UShortInFunc(in_UShort); VB Declare Sub UShortInFunc Lib "Foo.dll" (ByVal in_UShort As UInt16) ' Usage... Dim in_UShort As UInt16 = Convert.ToUInt16(Me.txtUShortIn.Text) UShortInFunc(in_UShort) 

Passing Single Characters

Single characters ( char in C#, Char in Visual Basic) use 8 bits to represent a character value. Thus, they can be passed by value or by reference. The following sample code shows a declaration and usage of a native function to which a character value is passed by value:

 
 C# [DllImport("Foo.dll")] private static extern void CharInFunc(char in_Char); // Usage... char in_Char = Convert.ToChar("C"); CharInFunc(in_Char); VB Declare Sub CharInFunc Lib "Foo.dll" (ByVal in_Char As Char) ' Usage... Dim in_Char As Char = Convert.ToChar(Me.txtCharIn.Text) CharInFunc(in_Char) 

Passing Bytes

Single byte values ( byte in C#, Byte in Visual Basic) are 8-bit unsigned values. Thus, they can be passed by value or by reference. The code in Listing 12.2 demonstrates the declaration and usage of a native code function that receives a byte value by reference:

Listing 12.2 Declaration and usage of a native code function that receives a byte value by reference
 C# [DllImport("Foo.dll")] private static extern void ByteInFunc(byte in_Byte); // Usage... byte in_Byte = Convert.ToByte(this.txtByteIn.Text); ByteInFunc(in_Byte); VB Declare Sub ByteInFunc Lib "Foo.dll" (ByVal in_Byte As Byte) ' Usage... Dim in_Byte As Byte = Convert.ToByte(Me.txtByteIn.Text) ByteInFunc(in_Byte) 

Passing Boolean Values

Boolean values ( bool in C#, Boolean in Visual Basic) consume 8 bits of storage, and so they can be passed by value or by reference. The following sample code demonstrates the declaration and usage of a native function that receives a Boolean value by value.

 
 C# [DllImport("Foo.dll")] private static extern void BoolInFunc(bool in_Bool); // Usage... bool in_Bool = false; BoolInFunc(in_Bool, out_StringBuilder); VB Declare Sub BoolToString Lib "Foo.dll" (ByVal in_Bool As Boolean) ' Usage... Dim in_Bool As Boolean = False BoolInFunc(in_Bool) 

Passing a DWORD

The DWORD data type is commonly used in native code on Windows CE platforms. This data type is compatible with the UInt32 data type in the .NET Compact Framework.

Passing an IntPtr

The IntPtr data type corresponds to handles in the Windows CE operating system. For example, the Windows CE API PlaySound() function, discussed later in this section, receives an HMODULE as a parameter. Chapter 14, "Cryptography," uses Windows CE API functions that receive such handle types as HCRYPTKEY and HCRYPTHASH . These are all examples of handles in the Windows CE operating system. Handle types in Windows CE are designated with all capital letters and begin with an "H."

You can pass in an IntPtr type by value to a Windows CE API function that expects a handle of some sort . The IntPtr is internally a pointer to an integer. Thus, if native code changes the value of an IntPtr , then the changes are reflected on the managed side. If you want to pass a NULL value into native code, you can pass IntPtr.Zero . To extract the numeric value of an IntPtr , you can use IntPtr.ToInt32 or IntPtr.ToInt64 , both of which are supported by the .NET Compact Framework.

Typically, an IntPtr gets a value from the Windows CE operating system. For example, you might receive a handle to an encryption key, and the handle is stored in an IntPtr on the managed side.

The PlaySound example later in this chapter demonstrates the use of an IntPtr , and Chapter 14 uses them extensively.

Passing Non-Mutable Strings

String values ( string in C#, String in Visual Basic) can be passed into native code, where they appear as pointers to an array of wide (16-bit) characters. That is, a string passed in from managed code appears as WCHAR * in native C language.

When you pass a string type into native code, it is read only on the native side. Altering the string in native code will not change the value of the string on the managed side, and it is not recommended to try this because it can result in illegal memory accesses. If you want to see changes made to a string by native code in managed code, then use a StringBuilder in the manner described in following sections.

Passing Mutable Strings with System.Text.StringBuilder

The StringBuilder class can be passed by value to native code, where it appears as a pointer to an array of wide (16-bit) characters. That is, it appears as WCHAR * in native C language. The native code can alter the string, and the changes are reflected in the StringBuilder on the managed side. On the managed side, developers can extract a string altered by native code simply by calling StringBuilder.ToString() .

In order to use a StringBuilder correctly to pass string values to and from native code, instantiate the StringBuilder with a specified amount of storage before passing it to native code. Make sure the native side somehow knows how much space the StringBuilder has to avoid buffer overruns. This tactic is an example of an overall important strategy when dealing with native code from the managed world: Allocate memory on the managed side. Allocating memory in native code can get very messy because you are not using the managed heap allocation system with garbage collection. This means that memory allocated by native code must be freed by the native code later, or it will be leaked memory.

The code in Listing 12.3 is taken from the FundamentalToString sample application. The code demonstrates the declaration and usage of the native IntToString function. The function is passed an Int32 and writes a string representation of the integer to the StringBuilder passed to it. The native C code that performs the conversion is also shown.

Listing 12.3 Code taken from the FundmamentalToString sample application
 C# // Declaration [DllImport("FundamentalToString.dll")] private static extern void IntToString(int in_Int, StringBuilder          out_IntAsString); // Usage... private void DoIntConversion() {    StringBuilder out_StringBuilder = new StringBuilder(MAX_CAPACITY);    int in_Int = Convert.ToInt32(this.txtIntegerIn.Text);    IntToString(in_Int, out_StringBuilder);    this.txtIntegerOut.Text = out_StringBuilder.ToString(); } VB ' Declaration Declare Sub IntToString Lib "FundamentalToString.dll"         (ByVal in_Int As Integer,         ByVal out_IntAsString As System.Text.StringBuilder) ' Usage... Private Sub DoIntConversion()     Dim out_StringBuilder As System.Text.StringBuilder = New             System.Text.StringBuilder(MAX_CAPACITY)     Dim in_Int As Integer = Convert.ToInt32(Me.txtIntegerIn.Text)     IntToString(in_Int, out_StringBuilder)     Me.txtIntegerOut.Text = out_StringBuilder.ToString() End Sub Native C code void __cdecl IntToString(int in_Int, WCHAR * out_IntAsString) {         swprintf(out_IntAsString, L"%d", in_Int); } 

Converting Data to Strings with the FundamentalToString Sample Application

The FundamentalToString sample application is in the folder \SampleApplications\ Chapter12 . There is a C# and Visual Basic version of the managed sample application. FundamentalToString uses a native DLL, FundamentalToString.dll . The source code for this DLL is in the folder \SampleApplications\Chapter12\NativeBinaries\FundamentalToString . Just like the SquareAnInt sample application, there are also pre-compiled DLL binaries in the \SampleApplications\Chapter12\NativeBinaries\FundamentalToString directory. Each binary is in its own subdirectory according to the hardware and operating system for which the DLL was built.

The FundamentalToString sample application demonstrates how to pass each of the fundamental types discussed in this section into native code. The native code DLL converts each of the fundamental types into a string representation. The string is marshalled back to the managed side through a StringBuilder , as described in the previous chunk of sample code.

To use the FundamentalToString application, build and deploy the managed project to your device. Copy the appropriate FundamentalToString.dll library to your device, either to the \Windows directory or the directory where the managed executable, FundamentalToString.exe , resides. You can now launch the application.

When the FundamentalToString application launches, it shows a form with labels demarking these data types: Int , Float , Short , UShort , Byte , DWORD , Unsigned Int , Long , Unsigned Long , Char , and Bool . Next to each data type is a white text box into which default values are placed. Next to each text box is an empty gray read-only text box.

When the button labeled Convert is clicked, the application makes a series of calls into native code. There is a unique native function for each input data type.

Each call passes two things to the native side: a data value from one of the white text boxes and a StringBuilder . The native function receives the data, converts it into a string representation, and writes the string representation to the array of wide characters it sees passed in to it. When the native function call returns, the managed code writes the value of the StringBuilder into the gray read-only text box.



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