Interop with Native DLLs

PInvoke, short for Platform Invocation Services, lets C# access functions, structs, and callbacks in unmanaged DLLs. For example, perhaps you wish to call the MessageBox function in the Windows user32.dll:

int MessageBox(HWND hWnd, LPCTSTR lpText, 
               LPCTSTR lpCation, UINT uType);

To call this function, you can write a static extern method decorated with the DllImport attribute:

using System.Runtime.InteropServices;
[DllImport("user32.dll")]
static extern int MessageBox(int hWnd, string text, 
                             string caption, int type);

PInvoke then finds and loads the required Win32 DLLs and resolves the entry point of the requested function. The CLR includes a marshaler that knows how to convert parameters and return values between .NET types and unmanaged types. In this example the int parameters translate directly to four-byte integers that the function expects, and the string parameters are converted to null-terminated arrays of characters using one-byte ANSI characters under Win9x or two-byte Unicode characters under WinNT/Win2K.

Marshaling Common Types

The CLR marshaler is a .NET facility that knows about the core types used by COM and the Windows API and provides default translations to CLR types for you. The bool type, for instance, can be translated into a two-byte Windows BOOL type or a four-byte Boolean type. You can override a default translation using the MarshalAs attribute:

using System.Runtime.InteropServices;
static extern int Foo([MarshalAs(UnmanagedType.LPStr)]
                      string s);

In this case, the marshaler was told to use LPStr, so it will always use ANSI characters. Array classes and the StringBuilder class will copy the marshaled value from an external function back to the managed value, as follows:

using System.Runtime.InteropServices;
[DllImport("kernel32.dll")]
static extern int GetWindowsDirectory(StringBuilder sb,
                                      int maxChars);
class Test {
   static void Main( ) {
      StringBuilder s = new String(256);
      GetWindowsDirectory(s, 256);
      Console.WriteLine(s);
   }
}

Marshaling Classes and Structs

Passing a class or struct to a C function requires marking the struct or class with the StructLayout attribute:

using System.Runtime.InteropServices;
[StructLayout(LayoutKind.Sequential)]
class SystemTime {
   public ushort wYear; 
   public ushort wMonth;
   public ushort wDayOfWeek; 
   public ushort wDay; 
   public ushort wHour; 
   public ushort wMinute; 
   public ushort wSecond; 
   public ushort wMilliseconds; 
}
class Test {
   [DllImport("kernel32.dll")]
   static extern void GetSystemTime(SystemTime t);
   static void Main( ) {
      SystemTime t = new SystemTime( );
      GetSystemTime(t);
      Console.WriteLine(t.wYear);
   }
}

In both C and C#, fields in an object are located at n number of bytes from the address of that object. The difference is that a C# program finds this offset by looking it up using the field name; C field names are compiled directly into offsets. For instance, in C, wDay is just a token to represent whatever is at the address of a SystemTime instance plus 24 bytes.

For access speed and future widening of a datatype, these offsets are usually in multiples of a minimum width, called the pack size . For .NET types, the pack size is usually set at the discretion of the runtime, but by using the StructLayout attribute, field offsets can be controlled. The default pack size when using this attribute is 8 bytes, but it can be set to 1, 2, 4, 8, or 16 bytes, and there are also explicit options to control individual field offsets. This lets a .NET type be passed to a C function.

In and Out Marshaling

The previous Test example works if SystemTime is a struct and t is a ref parameter, but is actually less efficient:

struct SystemTime {...}
static extern void GetSystemTime(ref SystemTime t);

This is because the marshaler must always create fresh values for external parameters, so the previous method copies t when going in to the function and then copies the marshaled t when coming out of the function. By default, pass-by-value parameters are copied in, C# ref parameters are copied in/out, and C# out parameters are copied out, but there are exceptions for the types that have custom conversions. For instance, array classes and the StringBuilder class require copying when coming out of a function, so they are in/out. It is occasionally useful to override this behavior, with the in and out attributes. For example, if an array should be read-only, the in modifier indicates to only copy the array going into the function, and not coming out of it:

static extern void Foo([in] int[] array);

Callbacks from Unmanaged Code

C# can not only call C functions but can also be called by C functions, using callbacks. In C# a delegate type is used in place of a function pointer:

class Test {
   delegate bool CallBack(int hWnd, int lParam);
   [DllImport("user32.dll")]
   static extern int EnumWindows(CallBack hWnd, int lParam);
   static bool PrintWindow(int hWnd, int lParam) {
      Console.WriteLine(hWnd);
      return true;
   }
   static void Main( ) {
      CallBack e = new CallBack(PrintWindow);
      EnumWindows(e, 0);
   }
}

Predefined Interop Support Attributes

The BCL provides a set of attributes you can use to mark up your objects with information that is used by the CLR marshaling services to alter their default marshaling behavior.

This section describes the most common attributes you will need when interoperating with native Win32 DLLs. These attributes all exist in the System.Runtime.InteropServices namespace.

DllImport attribute

[DllImport ( dll-name
[, EntryPoint= function-name ]?
[, CharSet= charset-enum ]?
[, SetLastError=true|false]?
[, ExactSpelling=true|false]?
[, CallingConvention= callconv-enum ]?)]
(for methods)

The DllImport attribute annotates an external function that defines a DLL entry point. The parameters for this attribute are:

dll-name

A string specifying the name of the DLL.

function-name

A string specifying the function name in the DLL. This is useful if you want the name of your C# function to be different from the name of the DLL function.

charset-enum

A CharSet enum, specifying how to marshal strings. The default value is CharSet.Auto, which converts strings to ANSI characters on Win98, and Unicode characters on WinNT.

SetLastError

If true, preserves the Win32 error info. The default is false.

ExactSpelling

If true, the EntryPoint must exactly match the function. If false, name-matching heuristics are used. The default is false.

callconv-enum

A CallingConvention enum, specifying the mode to use with the EntryPoint. Default is StdCall.

StructLayout attribute

[StructLayout( layout-enum
[, Pack= packing-size]?
[, CharSet= charset-enum]?
[, CheckFastMarshal=[true|false])?]
(for classes, structs)

The StructLayout attribute specifies how the data members of a class or struct should be laid out in memory. Although this attribute is commonly used when declaring structures that are passed to or returned from native DLLs, it can also define data structures suited to file and network I/O. The parameters for this attribute are:

layout-enum

A LayoutKind enum, which can be 1) sequential, which lays out fields one after the next with a minimum pack size; 2) union, which makes all fields have an offset of 0, so long as they are value types; 3) explicit, which lets each field have a custom offset.

packing-size

An int specifying whether the packing size is 1, 2, 4, 8, or 16 bytes. The default value is 8.

charset-enum

A CharSet enum, specifying how to marshal strings. The default value is CharSet.Auto, which converts strings to ANSI characters on Win98, and Unicode characters on WinNT.

CheckFastMarshal

A Boolean value specifying whether a compile-time warning should be generated if the type isn’t block transferable (“blittable”). The default is false.

FieldOffset attribute

[FieldOffset ( byte-offset )] (for fields)

The FieldOffset attribute is used within a class or struct that has explicit field layout. This attribute can be applied to a field and specifies the field offset in bytes from the start of the class or struct. Note that these offsets don’t have to be strictly increasing and can overlap, thus creating a union data structure.

MarshalAs attribute

[MarshalAs( unmanaged-type )
[, named-parameters]?]
(for fields, parameters, return values)

The MarshalAs attribute overrides the default marshaling behavior the marshaler applies to a parameter or field. The unmanaged-type value is taken from the UnmanagedType enum; see the following list for the permissible values:

                                 Bool
                                 LPStr
                                 VBByRefStr
                                 I1
                                 LPWStr
                                 AnsiBStr
                                 U1
                                 LPTStr
                                 TBStr
I2
                                 ByValTStr
                                 VariantBool
U2
                                 Iunknown
                                 FunctionPtr
I4
                                 Idispatch
                                 LPVoid
U4
                                 Struct
                                 AsAny
I8
                                 Interface
                                 RPrecise
U8
                                 SafeArray
                                 LPArray
                                 R4
                                 ByValArray
                                 LPStruct
                                 R8
                                 SysInt
                                 CustomMarshaler
                                 BStr
                                 SysUInt
                                 NativeTypeMax
                                 Error
  

For a detailed description of how and when to use each of these enum values, as well as other legal named-parameters, see the .NET Framework SDK documentation.

In attribute

[In] (for parameters)

The In attribute specifies that data should be marshaled into the caller and can be combined with the Out attribute.

Out attribute

[Out] (for parameters)

The Out attribute specifies that data should be marshaled out from the called method to the caller and can be combined with the In attribute.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
52.14.205.205