Unsafe Code in Visual C# 2005

Kaushik Datta

Unsafe code can access unmanaged memory, which is outside the realm of the Common Language Runtime (CLR). Conversely, safe code is limited to accessing the managed heap. The managed heap is controlled by the CLR under the auspices of the garbage collector (GC). Code that addresses the managed heap is intrinsically safer. The CLR automatically releases unused objects, performs type verification, and conducts other checks on managed memory. Developers can focus on core application development instead of administrative tasks such as memory management. For this reason, safe code improves user productivity and satisfaction.

Pointers to unmanaged memory are available in unsafe code. Like unmanaged memory, pointers are also outside the realm of the CLR. Pointers point to a fixed location in unmanaged memory, whereas reference types point to a moveable location in managed memory. The CLR manages reference types, which includes controlling the lifetime of objects and calling cleanup code. Developers do not delete memory allocated for reference types and are not overly involved in the intricacies of memory management. In C and C++ application development, developers were preoccupied with memory management. Despite this, improper management of pointers is a primary contributor to unsafe code in the unmanaged environment, including memory leaks, access of invalid memory, deletion of pointers, and fence post errors. Abstracting the nuances of pointer management and manipulation with reference types has made managed code considerably safer. However, when needed, you can exempt yourself from secure code and access pointers directly.

When is unsafe code appropriate? Not often. Unsafe code is provided within C# as the exception, not the rule. There are specific circumstances in which unsafe code is recommended:

    *      Unmanaged code often relies heavily on pointers. When porting this sort of code to C#, unsafe code is a possible solution. Most nontrivial C and C++ programmers heavily leverage pointers.

*      Implementing a software algorithm, in which pointers are integral to the design, might necessitate unsafe code.

*      Calling an unmanaged function might require function pointers.

*      Pointers might be required when working with binary memory resident data structures.

    *      Unmanaged pointers might improve performance and efficiencies in certain circumstances.

Code in an unmanaged module is also considered unsafe. Code in an unmanaged module is shielded from the CLR. Therefore, no code verification, stack tracing, or other checking is performed on the unmanaged code, which makes the code less safe.

Developers sometimes need to call unmanaged code from managed applications. Although the Microsoft .NET Framework class library (FCL) contains most of the code needed for .NET application development, the FCL umbrella does not encompass everything. You might need to call functions (APIs) in operating system libraries for behavior outside the FCL. In addition, proprietary and vendor software might not be available as managed code. Alternatively, you can call managed code from an unmanaged module, such as during a callback. Managed code might also be exposed to unmanaged clients.

Platform invocation services (PInvoke) is the bridge between managed and unmanaged execution. The bridge is bidirectional. From managed code, PInvoke is responsible for locating, loading, and executing a function in an unmanaged module. Marshaling is the primary concern of cross-platform calls and the responsibility of the Interop marshaler. Marshaling converts parameters and return types between unmanaged and managed acceptable formats. Fortunately, marshaling is not always required, which avoids unnecessary overhead. Certain types, such as blittable types, do not require transformation and are the same in managed and unmanaged memory.

You can also build bridges between managed code and COM components, which contain unmanaged code. The Runtime Callable Wrapper (RCW) helps managed code call COM components. The COM Callable Wrapper (CCW) portrays a managed component as a COM component. This makes the managed component indirectly accessible to COM clients. COM components are also available via PInvoke. However, the CCW and RCW are more convenient and are the recommended solutions in most scenarios. COM interoperability is not a topic for this article. COM Programming with Microsoft .NET, by John Paul Mueller and Julian Templeman (Microsoft Press, 2003), is an excellent resource for additional information on COM interoperability and .NET.

Unsafe code is also not trusted code. Code access security does not extend to unsafe code. Type verification, which helps prevent buffer overrun attacks, is not performed. Code verification is not performed. Therefore, the reliability of the code is undetermined. These are some of the reasons why unsafe code is not trusted. Because it is not trusted, elevated permissions are required to call unsafe code from managed code. Applications that rely on unsafe code might not execute successfully in every deployment situation and should be thoroughly tested in all potential scenarios. Managed code requires the SecurityPermission.UnmanagedCode permission to call unsafe code. The SuppressUnmanagedCodeSecurityAttribute attribute disables the stack walk that confirms the SecurityPermission.UnmanagedCode permission in callers. This attribute is a free pass for other managed code to call unsafe code. It is a convenient option, but also dangerous.

Managed applications that include unsafe code must be compiled with the unsafe option. The C# compiler option is simply /unsafe. In Microsoft Visual Studio 2005, this option is found in the project properties. The project properties are accessible from Solution Explorer. Open the context menu on the project name and choose the Properties menu item. In the Build window, choose the Allow unsafe code option.

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-1 describes the various symbols that are used with pointers.

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 Example.csharp{ 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 Example.csharp{ 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 Example.csharp{ 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; }

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 Example.csharp{
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 Example.csharp{
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 Example.csharp{
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.

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 Example.csharp{
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 Example.csharp{
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 Example.csharp{
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-2lists the members of the CharSet enumeration.

Table 15-2: 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 Example.csharp{    
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-3 lists the members of the LayoutKind enumeration.

Table-3: 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 -4 lists the attributes and related keywords.

Table-4: 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. 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.

Summary

Although pointers are not normally available in C#, developers can choose to use pointers at their discretion. Pointers are available in the context of unsafe code, which requires the unsafe keyword. You can create pointers to unmanaged types, such as the int, float, and char types. In addition, unsafe code must be compiled with the unsafe compiler option.
You cannot create pointers to moveable memory. Managed memory is moveable and managed by the garbage collector (GC). The fixed statement pins managed memory within a block. Pinned memory is fixed. While pinned, memory is not accessible to the GC.

The DllImportAttribute, which describes an unmanaged function that is exported from a library, has various options to configure the importing of a function. The Interop marshaler marshals parameters and returns values between managed and unmanaged memory during the function call. Blittable types do not require conversion. Nonblittable types require conversion to unmanaged memory. Developers can explicitly marshal nonblittable types using the MarshalAs attribute.

You can reach the author from the following email-id. KDatta23@gmail.com








}