Summary


Unsafe Keyword

The unsafe keyword sets the perimeter of an unsafe context and prevents the inadvertent placement of unsafe code. Code within the unsafe context can be unsafe, which allows unmanaged pointers to be declared and used in expressions. The unsafe keyword can be applied to a class, struct, interface, or delegate. When it is applied to a type, all the members of that type are also considered unsafe. You can also apply the unsafe keyword to specific members of a type. If applied to a function member, the entire function operates in the unsafe context.

In the following code, the ZStruct contains two fields that are pointers. Each is annotated with the unsafe keyword.

 public struct ZStruct {     public unsafe int *fielda;     public unsafe double *fieldb; } 

In this example, ZStruct is marked as unsafe. The unsafe context extends to the entire struct, which includes the two fields. Both fields are unsafe.

 public unsafe struct ZStruct {     public int *fielda;     public double *fieldb; } 

You can also create an unsafe block using the unsafe statement. All code encapsulated by the block is in the unsafe context. The following code has an unsafe block and method. Within the unsafe block in the Main method, MethodA is called and passed an int pointer as a parameter. MethodA is an unsafe method. It assigns the int pointer to a byte pointer, which now points to the lower byte of the int value. The value at that lower byte is returned from MethodA. For an int value of 296, MethodA returns 40.

 public static void Main(){     int number=296;     byte b;     unsafe {         b=MethodA(&number);     }     Console.WriteLine(b); } public unsafe static byte MethodA(int *pI) {     byte *temp=(byte*) pI;     return *temp; } 

The unsafe status of a base class is not inherited by a derived class. Unless explicitly designated as unsafe, a derived class is safe. In an unsafe context, the derived class can use unsafe members of the base class that are accessible.

In the following code, a compile error occurs in the derived type. The fieldb member of YClass requires an unsafe context, which is not inherited from the ZClass base class. Add the unsafe keyword explicitly to fieldb, and the code will compile successfully.

 public unsafe class ZClass {     protected int *fielda; } public class YClass: ZClass {     protected int *fieldb;  // compile error } 

Pointers

Unsafe code is mostly about direct access to pointers, which point to a fixed location in memory. Because the location is fixed, the pointer is reliable and can be used for dereferencing, pointer math, and other traditional pointer type manipulation. Pointers are outside the control of the GC. The developer is responsible for managing the lifetime of the pointer, if necessary.

C# does not automatically expose pointers. Exposing a pointer requires an unsafe context. In C#, pointers are usually abstracted using references. The reference abstracts a pointer to memory on the managed heap. That reference and related memory is managed by the GC and subject to relocation. A moveable pointer underlies a reference, which is why references are not available for direct pointer manipulation. Pointer manipulation on a moveable address would yield unreliable results.

This is the syntax for declaring a pointer:

  • unmanagedtype* identifier;

  • unmanagedtype* identifier=initializer;

You can declare multiple pointers in a single statement using comma delimiters. Notice that the syntax is slightly different from C or C++ languages:

 int *pA, pB, pC;  // C++:  int *pA, *pB, *pC; 

The unmanaged types (a subset of managed types) are sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, and enum. Some managed types, such as string, are not included in this list. You can create pointers to user-defined structs, assuming that they contain all unmanaged types as fields. Pointer types do not inherit from System.Object, so they cannot be cast to or from System.Object.

Void pointers are allowed but dangerous. This is a typeless pointer that can emulate any other pointer type. Any pointer type can be implicitly cast to a void pointer. This unpredictability makes void pointers extraordinarily unsafe. Except for void pointers, pointers are moderately type-safe. Although you cannot implicitly cast between different pointer types, explicitly casting between most pointer types is always allowed. As expected, the following code would cause a compiler error because of the pointer mismatch. This assignment could be forced with an explicit cast to another pointer type. In that circumstance, the developer assumes responsibility for the safeness of the pointer assignment.

 int val=5; float* pA=&val; 

You can initialize a pointer with the address of a value or with another pointer. In the following code, both methods of initializing a pointer are shown:

 public unsafe static void Main() {     int ival=5;     int *p1=&ival;  // dereference     int *p2=p1;     // pointer to pointer } 

In the preceding code, the asterisk (*) is used to declare a pointer. The ampersand (&) is used to dereference a value. Table 15-1 describes the various symbols that are used with pointers.

Table 15-1: Pointer Symbols

Symbol

Description

Pointer declaration (*)

For pointers, the asterisk symbol has two purposes. The first is to declare new pointer variables.

 int *pA; 

Pointer dereference (*)

The second purpose of the asterisk is to dereference a pointer. Pointers point to an address in memory. Dereferencing a pointer returns the value at that address in memory.

 int val=5; int *pA=&val; Console.WriteLine(*pA); // displays 5 

You cannot dereference a void pointer.

Address of (&)

The ampersand symbol returns the memory location of a variable, which is a fixed value. The following code returns the memory address of an int. It is used to initialize an int pointer.

 int *pA=&val; 

Member access (->)

Arrow notation dereferences members of a type found at a memory location. For example, you can access members of a struct using arrow notation and a pointer. In the following code, ZStruct is astruct, and fielda is a member of that type.

 ZStruct obj=new ZStruct(5); ZStruct *pObj=&obj; int val1=pObj->fielda; 

Alternatively, you can deference the pointer and access a member using dot syntax (.).

 int val2=(*pObj).fielda;  // dot syntax 

Pointer element ([])

A pointer element is an offset from the memory address of a pointer. For example, p[2] is an offset of two. Offsets are incremented by the size of the pointer type. If p is an int pointer, p[2] is an increment of eight bytes. In the following code, assume that ZStruct has two int fields in contiguous memory: fielda and fieldb.

 ZStruct obj=new ZStruct(5); int *pA=&obj.fielda; Console.WriteLine(pA[1]) // fieldb 

Pointer to a pointer (**)

A pointer to a pointer points to a location in memory that contains the address of a pointer. Although rarely useful, you can extend the chain of pointers even further (***, ****, and so on). You can dereference a pointer to a pointer with a double asterisk (**). Alternatively, you can dereference a pointer to a pointer using individual asterisks in separate steps.

 int val=5; int *pA=&val; int **ppA=&pA; // Address stored in ppA, which is pA. Console.WriteLine((int)ppA); // Address stored in pA. Console.WriteLine((int)*ppA); // value at address stored in pA (5). Console.WriteLine((int)**ppA); 

Pointer addition (+)

Pointer addition adds the size of the pointer type to the memory location.

 ZStruct obj=new ZStruct(5); int *pA=&obj.fielda; pA=pA+2; // Add eight to pointer 

Pointer subtraction (-)

Pointer subtraction subtracts from the pointer the size of the pointer type.

 ZStruct obj=new ZStruct(5); int *pA=&obj.fielda; pA=pA-3; // Subtract twelve from pointer 

Pointer increment (++)

Pointer increment increments the pointer by the size of the pointer type.

 ZStruct obj=new ZStruct(5); int *pA=&obj.fielda; ++pA; // increment pointer by four 

Pointer decrement (--)

Pointer decrement decrements the pointer by the size of the pointer type.

 ZStruct obj=new ZStruct(5); int *pA=&obj.fielda; --pA; // decrement pointer by four 

Relational symbols

The relational operators, such as < > >= <= != ==, can be used to compare pointers. The comparison is based on memory location and not pointer type. In the following code, pA and pB are compared, even though pA and pB are pointers of different types.

 ZStruct obj=new ZStruct(5); int *pA=&obj.fielda; int val=5; int *pB=&val; if(pA==pB) { } 

Pointer Parameters and Return

A pointer is a legitimate variable. As such, a pointer can be used as a variable in most circumstances, including as a parameter or return type. When used as a return type, ensure that the lifetime of the pointer is the same as or exceeds that of the target. For example, do not return a pointer to a local variable from a function—the local variable loses scope outside of the function and the pointer is then invalid.

In the following code, a pointer is used as both a parameter and return type. MethodA accepts a pointer as a parameter. It then returns the same pointer. After the method call, both pB and pA point to the same location in memory. They are aliases. Therefore, Console.WriteLine displays the same number when the values at the pointers are displayed.

 using System; namespace Donis.CSharpBook{     public class Starter{         public unsafe static void Main() {             int val=5;             int *pA=&val;             int *pB;             pB=MethodA(pA);             Console.WriteLine("*pA = {0} | *pB = {0}",                 *pA, *pB);         }         public unsafe static int *MethodA(int *pArg) {             *pArg+=15;             return pArg;         }     } } 

The ref or out modifiers can be applied to pointer parameters. Without the modifiers, the memory location is passed by pointer. The pointer itself is passed by value on the stack. In the function, you can dereference the pointer and change values at the memory location. These changes will persist even after the function exits. However, changes to the pointer itself are discarded when the function exists. With the ref or out modifier, a pointer parameter is passed by reference. In the function, the pointer can be changed directly. Those changes continue to persist even after the function exits.

In the following code, both MethodA and MethodB have a pointer as a parameter. MethodA passes the pointer by value, whereas MethodB passes the pointer by reference. In both methods, the pointer is changed. The change is discarded when MethodA exists. When MethodB exits, the change persists.

 using System; namespace Donis.CSharpBook{     public class Starter{         public unsafe static void Main() {             int val=5;             int *pA=&val;             Console.WriteLine("Original: {0}", (int) pA);             MethodA(pA);             Console.WriteLine("MethodA:  {0}", (int) pA);             MethodB(ref pA);             Console.WriteLine("MethodB:  {0}", (int) pA);         }         public unsafe static void MethodA(int *pArg) {             ++pArg;         }         public unsafe static void MethodB(ref int *pArg) {             ++pArg;         }     } } 

Fixed

What is wrong with the following code?

 int [] numbers={1,2,3,4,5,6}; int *pI=numbers; 

The problem is that the numbers variable is an array, which is a reference type. The code will not compile. References are moveable types and cannot be converted to pointers. Because objects are moveable, you cannot obtain a reliable pointer. Conversely, structs are value types and are placed on the stack and outside of the control of the GC. Struct values have a fixed address and are easily converted into pointers. In the preceding code, if the type were changed from an array to a struct, it would compile successfully. With the fixed statement, you pin the location of a moveable type—at least temporarily. Pinning memory for an extended period of time can interfere with efficient garbage collection.

Here is the code revised with the fixed statement. This code compiles successfully.

 int [] numbers={1,2,3,4,5,6}; fixed(int *pI=numbers) {     // do something } 

The fixed statement pins memory for the span of a block. In the block, the memory is unmovable and is exempt from garbage collection. You can access the pinned memory using the pointer declared or initialized in the fixed statement, which is a read-only pointer. When the fixed block exits, the memory is unpinned. Multiple pointers can be declared in the fixed statement. The pointers are delimited with commas, and only the first pointer is prefixed with the asterisk (*):

 int [] n1={1,2,3,4}; int [] n2={5,6,7,8}; int [] n3={9,10,11,12}; fixed(int *p1=n1, p2=n2, p3=n3) { } 

This is a more complete example of using the fixed statement:

 using System; namespace Donis.CSharpBook{     public class Starter{         private static int [] numbers={5,10,15,20,25,30};         public unsafe static void Main(){             int count=0;             Console.WriteLine(" Pointer Value\n");             fixed(int *pI=numbers) {                 foreach(int a in numbers){                     Console.WriteLine("{0} : {1}",                         (int)(pI+count), *((int*)pI+count));                     ++count;                 }             }         }     } } 

In the following code, ZClass is a class and a moveable type. The fixed statement makes the ZClass object fixed in memory. A pointer to the integer member is then obtained.

 public class Starter{     public unsafe static void Main() {         ZClass obj=new ZClass();         fixed(int *pA=&obj.fielda) {         }     } } public class ZClass {     public int fielda=5; } 

stackalloc

The stackalloc command allocates memory dynamically on the stack instead of the heap. The lifetime of the allocation is the duration of the current function, which provides another option for allocating memory at run time. The stackalloc command must be used within an unsafe context. It can be used to initialize only local variables. The CLR will detect buffer overruns caused by the stackalloc command.

Here is the syntax for stackalloc:

  • type * stackalloc type[expression]

The stackalloc command returns an unmanaged type. The expression should evaluate to an integral value, which is the number of elements to be allocated. The base pointer of the memory allocation is returned. This memory is fixed and not available for garbage collection. It is automatically released at the end of the function. These are the particulars of the stackalloc command.

The following code allocates 26 characters on the stack. The subsequent for loop assigns alphabetic characters to each element. The final loop displays each character.

 using System; namespace Donis.CSharpBook{     public unsafe class Starter{         public static void Main(){             char* pChar=stackalloc char[26];             char* _pChar=pChar;             for(int count=0;count<26;++count) {                 (*_pChar)=(char)(((int)('A'))+count);                 ++_pChar;             }             for(int count=0;count<26;++count) {                 Console.Write(pChar[count]);             }         }     } } 

Platform Invoke

You can call unmanaged functions from managed code using platform invoke (PInvoke). Managed and unmanaged memory might be laid out differently, which might require marshaling of parameters or the return type. In .NET, marshaling is the responsibility of the Interop marshaler.

Interop Marshaler

The Interop marshaler is responsible for transferring data between managed and unmanaged memory. It automatically transfers data that is similarly represented in managed and unmanaged environments. For example, integers are identically formatted in both environments and automatically marshaled between managed and unmanaged environments. Types that are the same in both environments are called blittable types. Nonblittable types, such as strings, are managed types without an equivalent unmanaged type and must be marshaled. The Interop marshaler assigns a default unmanaged type for many nonblittable types. In addition, developers can explicitly marshal nonblittable types to specific unmanaged types with the MarshalAsAttribute type.

DllImport

The DllImportAttribute imports a function exported from an unmanaged library, and the unmanaged library must export a function. DllImportAttribute is in the System.Runtime .InteropServices namespace. DllImportAttribute has several options that configure the managed environment to import the function. The library is dynamically loaded, and the function pointer is initialized at run time. Because the attribute is evaluated at run time, most configuration errors are not found at compile time; they are found later.

This is the syntax of the DllImportAttribute:

  • [DllImport(options)] accessibility static extern returntype functionname(parameters)

Options are used to configure the import. The name of the library is the only required option. The name of the library should include the fully qualified path if it is not found in the path environment variable. Accessibility is the visibility of the function, such as public or protected. Imported functions must be static and extern. The remainder is the managed signature of the function.

The following code imports three functions to display the vertical and horizontal size of the screen. GetDC, GetDeviceCaps, and ReleaseHandle are Microsoft Win32 APIs. The imported functions are configured and exposed in the API class, which is a static class.

 using System; using System.Runtime.InteropServices; namespace Donis.CSharpBook{     public class Starter{         public static void Main(){             IntPtr hDC=API.GetDC(IntPtr.Zero);             int v=API.GetDeviceCaps(hDC, API.HORZRES);             Console.WriteLine("Vertical size of window {0}mm.", v);             int h=API.GetDeviceCaps(hDC, API.HORZRES);             Console.WriteLine("Horizontal size of window {0}mm.", h);             int resp=API.ReleaseDC(IntPtr.Zero, hDC);             if(resp!=1) {                 Console.WriteLine("Error releasing hdc");             }         }     }     public static class API {         [DllImport("user32.dll")] public static extern         IntPtr GetDC(IntPtr hWnd);         [DllImport("user32.dll")] public static extern         int ReleaseDC(IntPtr hWnd, IntPtr hDC);         [DllImport("gdi32.dll")]public static extern         int GetDeviceCaps(IntPtr hDC, int nIndex);         public const int HORZSIZE=4;  // horizontal size in pixels         public const int VERTSIZE=6;  // vertical size in pixels         public const int HORZRES=8;   // horizontal size in millimeters         public const int VERTRES=10;  // vertical size in millimeters     } } 

In the preceding code, the name of the library was the option used. There are several other options, which are described in the following sections.

EntryPoint This option explicitly names the imported function. Without this option, the name is implied from the managed function signature. When the imported name is ambiguous, the EntryPoint option is necessary. You can then specify a different name for the entry point and the managed function.

In the following code, MessageBox is being imported. However, the managed name for the function is ShowMessage.

 using System; using System.Runtime.InteropServices; namespace Donis.CSharpBook{     public class Starter{         public static void Main() {             string caption="Visual C# 2005";             string text="Hello, world!";             API.ShowMessage(0, text, caption, 0);         }     }     public class API {         [DllImport("user32.dll", EntryPoint="MessageBox")]         public static extern int ShowMessage(int hWnd,             string text, string caption, uint type);     } } 

CallingConvention This option sets the calling convention of the function. The default calling convention is Winapi, which maps to the standard calling convention in the Win32 environment. The calling convention is set with the CallingConvention enumeration. Table 15-2 lists the members of the enumeration.

Table 15-2: CallingConvention Enumeration

Member

Description

Cdecl

The caller removes the parameters from the stack, which is the calling convention for functions that have a variable-length argument list.

FastCall

This calling convention is not supported.

StdCall

The called method removes the parameters from the stack. This calling convention is commonly used for APIs and is the default for calling unmanaged functions with Platform invoke.

ThisCall

The first parameter of the function is the this pointer followed by the conventional parameters. The this pointer is cached in the ECX register and used to access instance members of an unmanaged class.

WinApi

Default calling convention of the current platform. For Win32 environment, this is the StdCall calling convention. For Windows CE .NET, CDecl is the default.

The following code imports the printf function, which is found in the C Runtime Library. The printf function accepts a variable number of parameters and supports the CDecl calling convention.

 using System; using System.Runtime.InteropServices; namespace Donis.CSharpBook{     public class Starter{         public static void Main() {             int val1=5, val2=10;             API.printf("%d+%d=%d", val1, val2, val1+val2);         }     }     public class API {         [DllImport("msvcrt.dll", CharSet=CharSet.Ansi,             CallingConvention=CallingConvention.Cdecl)]         public static extern int printf(string formatspecifier,             int lhs, int rhs, int total);     } } 

ExactSpelling This option stipulates that the exact spelling of the function name is used to resolve the symbol. Names are not always what they seem. For example, the function names of many Win32 APIs are actually macros that map to the real API, which is an A or W suffixed method. The A version is the ANSI version, whereas the W (wide) version is the Unicode version of the function. The ANSI versus Unicode extrapolation pertains mostly to Win32 APIs that have string parameters. For example, the supposed CreateWindow API is a macro that maps to either the CreateWindowW or CreateWindowA API. For the DllImportAttribute, the version selected is determined in the CharSet option. If ExactSpelling is false, the function name is treated as the actual name and not translated. The default is false.

The following code imports the GetModuleHandleW function specifically. ExactSpelling is true to prevent mapping to another name.

 using System; using System.Runtime.InteropServices; namespace Donis.CSharpBook{     public class Starter{         public static void Main() {             int hProcess=API.GetModuleHandleW(null);         }     }     public class API {         [DllImport("kernel32.dll", ExactSpelling=true)]         public static extern int GetModuleHandleW(string filename);     } } 

PreserveSig This option preserves the signature of the method when resolving the symbol. COM functions usually return an HRESULT, which is the error status of the call. The real return is the parameter decorated with the [out, retval] Interface Definition Language (IDL) attribute. In managed code, the HRESULT is consumed and the [out,retval] parameter is the return. To resolve a COM function, that managed signature cannot be preserved; it should be mapped to a COM signature. Conversely, the signature of non-COM functions should be preserved. PreserveSig defaults to true.

The following code demonstrates the PreserveSig option with a fictitious COM function:

 public class API {     [DllImport("ole32.dll", PreserveSig=false)]     public static extern int SomeFunction(); } 

The signature is not preserved and would become the following:

 HRESULT SomeFunction([out, retval] int param) 

SetLastError This option requests that the CLR cache the error code of the named Win32 API. Most Win32 APIs return false if the function fails. False is minimally descriptive, so developers can call GetLastError for an integer error code. GetLastError must be called immediately after the failed API; if not, the next API might reset the error code. In managed code, call Marshal.GetLastWin32Error to retrieve the error code. The Marshal type is in the System.Runtime .InteropServices namespace. SetLastError defaults to false.

In the following code, CreateDirectory and FormatMessage are imported in the API class. CreateDirectory creates a file directory; FormatMessage converts a Win32 error code into a user-friendly message. For CreateDirectory, the SetLastError option is set to true. In Main, CreateDirectory is called with an invalid path. The "c*" drive is probably an incorrect drive on most computers. The resulting error code is stored in the resp variable, which is then converted into a message in the FormatMessage API. FormatMessage returns the user-friendly messages as an out parameter.

 using System; using System.Text; using System.Runtime.InteropServices; namespace Donis.CSharpBook{     public class Starter{         public static void Main() {             bool resp=API.CreateDirectory(@"c*:\file.txt",                 IntPtr.Zero);             if(resp==false) {                 StringBuilder message;                 int errorcode=Marshal.GetLastWin32Error();                 API.FormatMessage(                     API.FORMAT_MESSAGE_ALLOCATE_BUFFER |                     API.FORMAT_MESSAGE_FROM_SYSTEM |                     API.FORMAT_MESSAGE_IGNORE_INSERTS,                     IntPtr.Zero, errorcode,                     0, out message, 0, IntPtr.Zero);                 Console.WriteLine(message);             }         }     }     public class API {         [DllImport("kernel32.dll", SetLastError=true)]         public static extern bool CreateDirectory(             string lpPathName, IntPtr lpSecurityAttributes);         [DllImport("kernel32.dll", SetLastError=false)]         public static extern System.Int32 FormatMessage(             System.Int32 dwFlags,             IntPtr lpSource,             System.Int32 dwMessageId,             System.Int32 dwLanguageId,             out StringBuilder lpBuffer,             System.Int32 nSize,             IntPtr va_list);         public const int FORMAT_MESSAGE_ALLOCATE_BUFFER=256;         public const int FORMAT_MESSAGE_IGNORE_INSERTS=512;         public const int FORMAT_MESSAGE_FROM_STRING=1024;         public const int FORMAT_MESSAGE_FROM_HMODULE=2048;         public const int FORMAT_MESSAGE_FROM_SYSTEM=4096;         public const int FORMAT_MESSAGE_ARGUMENT_ARRAY=8192;         public const int FORMAT_MESSAGE_MAX_WIDTH_MASK=255;     } } 

CharSet

This option indicates the proper interpretation of strings in unmanaged memory, which can affect the ExactSpelling option. CharSet is also an enumeration with three members. The default is CharSet.Ansi. Table 15-3 lists the members of the CharSet enumeration.

Table 15-3: CharSet Enumeration

Value

Description

CharSet.Ansi

Strings should be marshaled as ANSI.

CharSet.Unicode

Strings should be marshaled as Unicode.

CharSet.Auto

The appropriate conversion is decided at run time depending on the current platform.

The following code marshals string for unmanaged memory as ANSI. The ExactSpelling option defaults to false, and the GetModuleHandleA API is called.

 using System; using System.Runtime.InteropServices; namespace Donis.CSharpBook{     public class Starter{         public static void Main() {             int hProcess=API.GetModuleHandle(null);         }     }     public class API {         [DllImport("kernel32.dll", CharSet=CharSet.Ansi)]         public static extern int GetModuleHandle(string filename);     } } 

BestFitMapping This option affects the Unicode-to-ANSI mapping of text characters passed from managed to unmanaged functions running in the Windows 98 or Windows Me environment. If true, best fit mapping is enabled. When there is not a direct character match, the Unicode character is mapped to the closest match in the ANSI code page. If no match is available, the Unicode character is mapped to a "?" character. The default is true.

ThrowOnUnmappableChar The ThrowOnUnmappableChar option can request an exception when an unmappable character is found in the Unicode-to-ANSI translation for Windows 98 and Windows Me. If true, an exception is raised when a Unicode character cannot be mapped to ANSI, and the character is converted to a "?" character. If false, no exception is raised. See the BestFitMapping option for additional details on Unicode-to-ANSI mapping.

Blittable Types

Blittable types are identically represented in managed and unmanaged memory. Therefore, no conversion is necessary from the Interop marshaler when marshaling between managed and unmanaged environments. Because conversion can be expensive, blittable types are more efficient than nonblittable types. For this reason, when possible, parameters and return types should be blittable types, which include System.Byte, System.SByte, System.Int16, System.UInt16, System.Int32, System.UInt32, System.Int64, System.IntPtr, System.UIntPtr, System.Single, and System.Double. Vectors of blittable types are also considered blittable. Formatted value types that contain only blittable types are also considered blittable.

Nonblittable types have different representation in managed and unmanaged memory. Some nonblittable types are automatically converted by the Interop marshaler, whereas others require explicit marshaling. Strings and user-defined classes are examples of nonblittable types. A managed string can be marshaled as a variety of unmanaged strings: LPSTR, LPTSTR, LPWSTR, and so on. Classes are nonblittable unless formatted. A formatted class marshaled as a formatted value type is blittable.

Formatted Type

A formatted type is a user-defined type in which the memory layout of the members is explicitly specified. Formatted types are prefixed with the StructLayoutAttribute, which sets the layout of the members as described in the LayoutKind enumeration. Table 15-4 lists the members of the LayoutKind enumeration.

Table 15-4: LayoutKind Enumeration

Value

Description

LayoutKind.Auto

The CLR sets the location of members in unmanaged memory. The type cannot be exposed to unmanaged code.

LayoutKind.Sequential

Members are stored in contiguous (sequential) and textual order in unmanaged memory. If desired, set packing with the StructLayoutAttribute.Pack option.

LayoutKind.Explicit

This value allows the developer to stipulate the order of the fields in memory using FieldOffsetAttribute. This value is useful for representing a C or C++ union type in unmanaged code.

In the following code, the API class contains the GetWindowRect unmanaged API. This function returns the location of the client area in the screen. The parameters of GetWindowRect are a window handle and a pointer to a Rect structure, which is also defined in the API class. The Rect structure, which is initialized inside the function, is a formatted value type and is blittable. By default, value types are passed by value. To pass by pointer, the out modifier is assigned to the Rect parameter.

 public class API {     [DllImport("user32.dll")]     public static extern bool GetWindowRect(         IntPtr hWnd,         out Rect windowRect);     [StructLayout(LayoutKind.Sequential)]     public struct Rect     {         public int left;         public int top;         public int right;         public int bottom;     } } 

Here is the code that uses the GetWindowRect API and the Rect structure:

 API.Rect client = new API.Rect(); API.GetWindowRect(this.Handle, out client); string temp=string.Format("Left {0} : Top {1} : "+     "Right {2} : Bottom {3}", client.left,     client.top, client.right, client.bottom); MessageBox.Show(temp); 

The following code is a version of the API class that defines a Rect class instead of a structure. Because the Rect class has the StructLayout attribute, it is a formatted type. Classes are passed by reference by default. The out modifier required for a structure is not necessary for a class.

 class API2 {     [DllImport("user32.dll")]     public static extern bool GetWindowRect(         IntPtr hWnd,         Rect windowRect);     [StructLayout(LayoutKind.Sequential)]     public class Rect     {         public int left;         public int top;         public int right;         public int bottom;     } } 

This is the code to call the GetWindowRect API using the Rect class:

 API2.Rect client = new API2.Rect(); API2.GetWindowRect(this.Handle, client); string temp = string.Format("Left {0} : Top {1} : " +     "Right {2} : Bottom {3}", client.left,     client.top, client.right, client.bottom); MessageBox.Show(temp); 

Unions are fairly common in C and C++ code. A union is a type in which the members share the same memory location. This preserves memory by overlaying data in shared memory. C# does not offer a union type. In managed code, emulate a union in unmanaged memory with the LayoutKind.Explicit option of StructLayoutAttribute. Set each field of the union to the same offset, as shown in the following code:

 [StructLayout(LayoutKind.Explicit)] struct ZStruct {     [FieldOffset(0)] int fielda;     [FieldOffset(0)] short fieldb;     [FieldOffset(0)] bool fieldc; } 

Directional Attributes

Directional attributes explicitly control the direction of marshaling. Parameters can be assigned InAttribute, OutAttribute, or both attributes to affect marshaling. This is equivalent to [in], [out], and [in,out] of the IDL. InAttribute and OutAttribute are also represented by keywords in C#. Table 15-5 lists the attributes and related keywords.

Table 15-5: Directional Attributes and C# Keywords

Keyword

Attribute

IDL

Not available

InAttribute

[in]

Ref

InAttribute and OutAttribute

[in,out]

Out

OutAttribute

[out]

The default directional attribute depends on the type of parameter and any modifiers.

StringBuilder

Strings are immutable and dynamically sized. An unmanaged API might require a fixed-length and modifiable string. In addition, some unmanaged APIs initialize the string with memory allocated at run time. A string type should not be used in these circumstances. Instead, use the StringBuilder class, which is found in the System.Text namespace. StringBuilders are fixed-length and not immutable. Furthermore, you can initialize the StringBuilder with memory created in the unmanaged API.

In the following code, the GetWindowText unmanaged API is imported twice. (This code, which is from the GetWindowText application, is included on the CD-ROM for this book.) GetWindowText retrieves the text from the specified window. For an overlapped window, this is text from the title bar. The second parameter of GetWindowText is a string, which is initialized with the window text during the function call. The first version of GetWindowText in the API class has a string parameter, whereas the version in the API2 class has a StringBuilder parameter. The GetWindowText application is a Windows Forms application that has two buttons. The first button calls API.GetWindowText. The second button calls API2.GetWindowText.

Because of the string parameter in API.GetWindowText, an exception is raised because the API attempts to change that parameter. The second button invokes API2.GetWindowText successfully.

 public class API {     [DllImport("user32.dll")]     public static extern int GetWindowText(         IntPtr hWnd, ref string lpString, int nMaxCount); } public class API2 {     [DllImport("user32.dll")]     public static extern int GetWindowText(         IntPtr hWnd, StringBuilder lpString, int nMaxCount); } 

This is the code from the button-click handlers of the form:

 private void btnGetText_Click(object sender, EventArgs e) {     string windowtext=null;     API.GetWindowText(this.Handle, ref windowtext,         10);     MessageBox.Show(windowtext); } private void btnGetText2_Click(object sender, EventArgs e) {     StringBuilder windowtext=new StringBuilder();     API2.GetWindowText(this.Handle, windowtext,         25);     MessageBox.Show(windowtext.ToString()); } 

Unmanaged Callbacks

Some unmanaged APIs accept a callback as a parameter, which is a function pointer. The API invokes the function pointer to call a function in the managed caller. Callbacks are typically used for iteration. For example, the EnumWindows unmanaged API uses a callback to iterate top-level window handles.

.NET abstracts function pointers with delegates, which are type-safe and have a specific signature. In the managed signature, substitute a delegate for the callback parameter of the unmanaged signature.

These are the steps to implement a callback for an unmanaged function:

  1. Find the unmanaged signature of the callback function.

  2. Define a matching managed signature for the callback function.

  3. Implement a function to be used as the callback. The function should contain the response to the callback.

  4. Create a delegate, which is initialized with the callback function.

  5. Invoke the unmanaged API using the delegate for the callback parameter.

The following code imports the EnumWindows unmanaged API. The first parameter of EnumWindows is a callback. EnumWindows enumerates top-level windows. The callback function is called at each iteration and is given the current window handle. In this code, APICallback is a delegate and provides a managed signature for the callback.

 class API {     [DllImport("user32.dll")]     public static extern bool EnumWindows(         APICallback lpEnumFunc,         System.Int32 lParam);     public delegate bool APICallback(int hWnd, int lParam); } 

EnumWindows is called in the click handler of a Windows Forms application, where GetWindowHandle is the callback function. GetWindowHandle is called for each iterated window handle. The function adds each window handle to a list box:

 private void btnHandle_Click(object sender, EventArgs e) {     API.EnumWindows(new API.APICallback(GetWindowHandle), 0); } bool GetWindowHandle(int hWnd, int lParam) {     string temp = string.Format("{0:0000000}", hWnd);     listBox1.Items.Add(temp);     return true; } 

Explicit Marshaling

Explicit marshaling is sometimes required to convert nonblittable parameters and fields, and returns types to proper unmanaged types. Marshaling is invaluable for strings, which have several possible representations in unmanaged memory. Strings default to LPSTR. Use the MarshalAsAttribute to explicitly marshal a managed type as a specific unmanaged type. The UnmanagedType enumeration defines the unmanaged types available to the MarshalAsAttribute. Table 15-6 lists the members of the UnmanagedType enumeration.

Table 15-6: UnmanagedType Enumeration

Member

Description

AnsiBStr

Length-prefixed ANSI string

AsAny

Dynamic type for which the type of the value is set at run time

Bool

Four-byte Boolean value

BStr

Length-prefixed Unicode string

ByValArray

Marshals an array by value; SizeConst sets the number of elements

ByValTStr

Inline fixed-length character array that is a member of a structure

Currency

COM currency type

CustomMarshaler

A custom marshaler to be used with MarshalAsAttribute.MarshalType or MarshalAsAttribute.MarshalTypeRef

Error

HRESULT

FunctionPtr

C-style function pointer

I1

One-byte integer

I2

Two-byte integer

I4

Four-byte integer

I8

Eight-byte integer

IDispatch

IDispatch pointer for COM

Interface

COM interface pointer

IUnknown

IUnknown interface pointer

LPArray

Pointer to the first element of an unmanaged array

LPStr

Null-terminated ANSI string

LPStruct

Pointer to an unmanaged structure

LPTStr

Platform-dependent string

LPWStr

Unicode string

R4

Four-byte floating point number

R8

Eight-byte floating point number

SafeArray

Safe array in which the type, rank, and bounds are defined

Struct

Marshals formatted value and reference types

SysInt

Platform-dependent integer (32 bits in Win32 environment)

SysUInt

Platform-dependent unsigned integer (32 bits in Win32 environment)

TBStr

Length-prefixed, platform-dependent string

U1

One-byte unsigned integer

U2

Two-byte unsigned integer

U4

Four-byte unsigned integer

U8

Eight-byte unsigned integer

VariantBool

Two-byte VARIANT_BOOL type

VBByRefStr

Microsoft Visual Basic-specific

GetVersionEx is imported in the following code. The function is called in Main to obtain information on the current operating system. GetVersionEx has a single parameter, which is a pointer to an OSVERSIONINFO structure. The last field in the structure is szCSDVersion, which is a universally unique identifier (UUID). A UUID is a 128-byte array. The MarshalAs attribute marshals the field as a 128-character array. Each character is one byte long.

 using System; using System.Runtime.InteropServices; namespace Donis.CSharpBook{     public class Starter{         public static void Main() {             API.OSVERSIONINFO info=new API.OSVERSIONINFO();             info.dwOSVersionInfoSize=Marshal.SizeOf(info);             bool resp=API.GetVersionEx(ref info);             if(resp==false) {                 Console.WriteLine("GetVersion failed");             }             Console.WriteLine("{0}.{1}.{2}",                 info.dwMajorVersion,                 info.dwMinorVersion,                 info.dwBuildNumber);         }     }     public class API {         [DllImport("kernel32.dll")] public static extern         bool GetVersionEx(ref OSVERSIONINFO lpVersionInfo);     [StructLayout(LayoutKind.Sequential)]         public struct OSVERSIONINFO {             public System.Int32 dwOSVersionInfoSize;             public System.Int32 dwMajorVersion;             public System.Int32 dwMinorVersion;             public System.Int32 dwBuildNumber;             public System.Int32 dwPlatformId;             [MarshalAs(UnmanagedType.ByValTStr, SizeConst=128)]                 public String szCSDVersion;         }     } } 

Fixed-Size Buffers

In the previous code, the MarshalAs attribute defined a fixed-size field of 128 characters or bytes. As an alternative to the MarshalAs attribute, C# 2.0 introduces fixed-size buffers using the fixed keyword. The primary purpose of this keyword is to embed aggregate types, such as an array, in a struct. Fixed-size buffers are accepted in structs but not in classes.

There are several rules for using fixed-size buffers:

  • Fixed-size buffers are available only in unsafe context.

  • Fixed-size buffers can represent only one-dimensional arrays (vectors).

  • The array must have a specific length.

  • Fixed-size buffers are allowed only in struct types.

  • Fixed-sized buffers are limited to bool, byte, char, short, int, long, sbyte, ushort, uint, ulong, float, or double types.

This is the syntax of the fixed-sized buffer:

  • attributes accessibility modifier fixed type identifier [expression]

The following code rewrites the OSVERSIONINFO structure that used the MarshalAs attribute. This version uses the fixed keyword for the szCSDVersion field.

 public class API { [StructLayout(LayoutKind.Sequential)]      unsafe public struct OSVERSIONINFO {           public System.Int32 dwOSVersionInfoSize;           public System.Int32 dwMajorVersion;           public System.Int32 dwMinorVersion;           public System.Int32 dwBuildNumber;           public System.Int32 dwPlatformId;           public fixed char szCSDVersion[128]; } 




Programming Microsoft Visual C# 2005(c) The Language
Microsoft Visual Basic 2005 BASICS
ISBN: 0619267208
EAN: 2147483647
Year: 2007
Pages: 161

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