Calling Win32 DLLs in C# with P/Invoke(节录)

最近,项目中经常需要Calling Win32 DLLs in C# 。

1、use of P/Invoke for calling Windows API functions

2、COM Interop

这里讨论1.

A、Enter P/Invoke

sample:

the Win32 MessageBeep function whose unmanaged declaration is shown in the following code:

BOOL MessageBeep(
  UINT uType   // beep type
);
You'll need the following code to add to a class or struct definition in C# in order to call MessageBeep:

[DllImport("User32.dll")]
static extern Boolean MessageBeep(UInt32 beepType);

A possible call from managed code might look like this:

MessageBeep(0);

the extern method must be defined using CLR types, as you saw in the preceding code snippet. This requirement to use CLR types that are different from, but compatible with, the underlying API function types is one of the more difficult aspects of using P/Invoke. Therefore, I'll devote a whole section to data marshaling a little later in this column.

B、Style

In general, if the class library offers a way to achieve your goals, it is preferable to use that API rather than making direct calls to unmanaged code because of the significant difference in style between the CLR types and Win32. I can sum up my advice on this matter in a single sentence. When you P/Invoke, don't subject your application logic directly to any extern methods or artifacts thereof.

Sample:

namespace Wintellect.Interop.Sound{
   using System;
   using System.Runtime.InteropServices;
   using System.ComponentModel;

   sealed class Sound{
      public static void MessageBeep(BeepTypes type){
         if(!MessageBeep((UInt32) type)){
            Int32 err = Marshal.GetLastWin32Error();
            throw new Win32Exception(err);
         }
      }

      [DllImport("User32.dll", SetLastError=true)]
      static extern Boolean MessageBeep(UInt32 beepType);

      private Sound(){}
   }

   enum BeepTypes{
      Simple = -1,
      Ok                = 0x00000000,
      IconHand          = 0x00000010,
      IconQuestion      = 0x00000020,
      IconExclamation   = 0x00000030,
      IconAsterisk      = 0x00000040
   }
}
C、The DLL Import Attribute

Toward the bottom of the topic, notice that it says that the library file is User32.lib; this indicates that MessageBeep is exported from User32.dll.

根据.lib名确定对应的.dll名。

D、Optional DllImportAttribute Properties

EntryPoint You can set this property to indicate the entry point name of the exported DLL function in cases where you do not want your extern managed method to have the same name as the DLL export. This is particularly useful when you are defining two extern methods that call into the same unmanaged function.

 

CharSet The only time you should explicitly select a CharSet value of CharSet.Ansi or CharSet.Unicode, rather than going with CharSet.Auto, is when you are explicitly naming an exported function that is specific to one or the other of the two flavors of Win32 OS. An example of this is the ReadDirectoryChangesW API function, which exists only in Windows NT-based operating systems and supports Unicode only; in this case you should use CharSet.Unicode explicitly.(只支持CharSet.Ansi 或者CharSet.Unicode时,不能用CharSet.Auto。如,ReadDirectoryChangesW只支持CharSet.Unicode。)
Sometimes it is unclear whether a Windows API has a character set affinity. A surefire way to find out is to check the C-language header file for the function in the Platform SDK. (If you are unsure which header file to look in, the Platform SDK documentation lists the header file for each API function.) If you find that the API function is really defined as a macro that maps to a function name ending in A or W, then character set matters for the function that you are trying to call. An example of a Windows API function that you might be surprised to learn has A and W versions is the GetMessage API declared in WinUser.h.(以这种方式判断支持字符集。)
SetLastError Error handling is an important but frequently avoided aspect of programming. And when you are P/Invoking, you are faced with the additional challenge of dealing with the differences between Windows API error handling and exceptions in managed code. I have a few suggestions for you.
If you are using P/Invoke to call into a Windows API function for which you use GetLastError to find extended error information, then you should set the SetLastError property to true in the DllImportAttribute for your extern method.
CallingConvention In general, starting with the default, CallingConvention.Winapi, is your best bet. And then, with C-runtime DLL functions and a very few functions you might have to change the convention to CallingConvention.Cdecl.
 
E、Data Marshaling
Data marshaling is a challenging aspect of P/Invoke. When passing data between the managed and unmanaged worlds, the CLR follows a number of rules that few developers will encounter often enough to memorize.
Marshaling Numerical and Logical Scalars
Non-Pointer Data Types :
Win32 Types Specification CLR Type
char, INT8, SBYTE, CHAR  8-bit signed integer System.SByte
short, short int, INT16, SHORT 16-bit signed integer System.Int16
int, long, long int, INT32, LONG32, BOOL , INT 32-bit signed integer System.Int32
__int64, INT64, LONGLONG 64-bit signed integer System.Int64
unsigned char, UINT8, UCHAR, BYTE 8-bit unsigned integer System.Byte
unsigned short, UINT16, USHORT, WORD, ATOM, WCHAR , __wchar_t 16-bit unsigned integer System.UInt16
unsigned, unsigned int, UINT32, ULONG32, DWORD32, ULONG, DWORD, UINT 32-bit unsigned integer System.UInt32
unsigned __int64, UINT64, DWORDLONG, ULONGLONG 64-bit unsigned integer System.UInt64
float, FLOAT Single-precision floating point System.Single
double, long double, DOUBLE Double-precision floating point System.Double
In Win32 this type is an integer with a specially assigned meaning; in contrast, the CLR provides a specific type devoted to this meaning.

Sample:

BOOL Beep(
  DWORD dwFreq,      // Frequency
  DWORD dwDuration   // Duration in milliseconds
);
==>

[DllImport("Kernel32.dll", SetLastError=true)]
static extern Boolean Beep(
   UInt32 frequency, UInt32 duration);

Parameters that are Pointers:

BOOL FileEncryptionStatus(
  LPCTSTR lpFileName,  // file name
  LPDWORD lpStatus     // encryption status
);
Notice that the function doesn't return the status using its return value, but instead returns a Boolean value indicating whether the call succeeded. In the success case, the actual status value is returned through the second parameter.

==>

[DllImport("Advapi32.dll", CharSet=CharSet.Auto)]
static extern Boolean FileEncryptionStatus(String filename,
   out UInt32 status);

Marshaling Opaque Pointers: a Special Case

Remember, any API function that returns or accepts a handle is really working with an opaque pointer. Your code should marshal handles in Windows as System.IntPtr values.

Marshaling Text:

 

Once you have established whether the text parameter is input only or input/output, you can determine which CLR type to use for the parameter type. Here are the rules. If the string parameter is input only, use the System.String type. Strings are immutable in managed code, and are well suited to be used as buffers that will not be changed by the native API function.
If the string parameter can be input and/or output, then use the System.StringBuilder type. The StringBuilder type is a useful class library type that helps you build strings efficiently, and it happens to be great for passing buffers to native functions that the functions fill with string data on your behalf. Once the function call has returned, you need only call ToString on the StringBuilder object to get a String object.
The GetShortPathName API function is great for showing when to use String and when to use StringBuilder because it takes only three parameters: an input string, an output string, and a parameter that indicates the length in characters of the output buffer.
The commented documentation for the unmanaged GetShortPathName function below indicates both an input and output string parameter. This leads to the managed extern method definition, also shown below. Notice that the first parameter is marshaled as a System.String because it is an input-only parameter. The second parameter represents an output buffer, and System.StringBuilder is used.

// ** Documentation for Win32 GetShortPathName() API Function
// DWORD GetShortPathName(
//   LPCTSTR lpszLongPath,      // file for which to get short path
//   LPTSTR lpszShortPath,      // short path name (output)
//   DWORD cchBuffer            // size of output buffer
// );

[DllImport("Kernel32", CharSet = CharSet.Auto)]
static extern Int32 GetShortPathName(
   String path,                // input string             (常量、不变)
   StringBuilder shortPath,    // output string       (可变)
   Int32 shortPathLength);     // StringBuilder.Capacity

Summing it Up
The P/Invoke features covered in this month's column are sufficient to call many of the API functions in Windows. However, if your interop needs are significant, you will eventually find yourself marshaling complex data structures and perhaps even needing access to memory directly through pointers in managed code. In fact, interop into native code can be a veritable Pandora's box of details and low-level bits. The CLR, C#, and managed C++ offer many features to help; perhaps in a later installment of this column I will cover advanced P/Invoke topics.
Meanwhile, whenever you find that the .NET Framework Class Library won't play a sound for you or perform some other bit of magic on your behalf, you know how to lean on the good old Windows API for some assistance.


 

 

 

As an increasing developer base moves its production applications to managed code, it seems only natural that there will be even more occasions for developers to dip down into the underlying operating system for some critical tidbit of functionality—at least for the time being.
Thankfully, the interop features of the common language run-time (CLR), called Platform Invoke (P/Invoke), are very complete. In this column I am going to focus on the practical use of P/Invoke for calling Windows API functions. P/Invoke is used as a noun when referring to the COM Interop functionality of the CLR and is used as a verb when referring to the use of this feature. I am not going to address COM Interop directly because it is paradoxically both more accessible and more complex than P/Invoke, making it less straightforward as a column topic.

 

 

http://msdn.microsoft.com/en-us/magazine/cc164123.aspx

你可能感兴趣的:(Win32)