PInvoke

来源:互联网 发布:cf一键领取软件 编辑:程序博客网 时间:2024/04/28 13:54

PInvoke

If you want to call one of the many Win32 API functions that are not available directly from the .NET framework, you should be able to call them using PInvoke (Platform Invoke). (PInvoke is not supported with classes. To call an interface method in a COM dll use COM Interop.) There are excellent tutorials on the Microsoft Developers Network on using PInvoke, so I will not belabor this technique. Here is a snippet of code that plays a sound from Eric Gunnerson's tutorial Using Win32 and Other Libraries. The class WrapK32 encapsulates or hides the call to InteropServices. To the user of the Wrap32 class, the Beep function appears as managed code.

using System;using System.Runtime.InteropServices;
namespace TestPInvoke{/// <summary>/// Summary description for Class1./// </summary>class WrapK32{[DllImport("kernel32.dll")]public static extern bool Beep(int frequency, int duration);/// <summary>/// The main entry point for the application./// </summary>[STAThread]static void Main(string[] args){//// TODO: Add code to start application here//Random random = new Random();
for (int i = 0; i < 10000; i++){Beep(random.Next(10000), 100);}}}}

To invoke the Beep Win32 function, you declare the C# prototype as public static extern as in:

public static extern bool Beep(int frequency, int duration);

The Beep function is in the kernal32 dll so you tell the runtime using the DllImport attribute directive. 

[DllImport("kernel32.dll")]public static extern bool Beep(int frequency, int duration);

String Stuff

Strings are confusing. Under Win9x, strings are ANSI single byte (8 bit) or double byte (8 _or_ 16 bit aka multi-byte) characters. Under NT, W2K and WXP, strings are 16 bit WIDE characters (Unicode). The LPTSTR type is both! It acts as ANSI on Win9x and as WIDE on WinNT. COM uses BSTR which is just a typedef to a WIDE string:

typedef wchar_t *BSTR;

However, a BSTR must adhere to a very specific set of rules that differs from a LPWSTR:

typedef wchar_t *LPWSTR;

The default marshaling type for strings in PInvoke is LPTSTR. This differs from COM Interop where the default marshaling type for strings is BSTR. If the actual parameter type differs from the PInvoke default then you will need to use the MarshalAs attribute in the function prototype declaration. For instance, it is an error to pass an immutable C# String as an out parameter. Instead, you will need to pass a StringBuffer and use the MarshalAs attribute. Here again is a sample from Eric Gunnerson's tutorial:

[DllImport("kernel32.dll", CharSet = CharSet.Auto)]public static extern int GetShortPathName(   [MarshalAs(UnmanagedType.LPTStr)]   string path,   [MarshalAs(UnmanagedType.LPTStr)]   StringBuilder shortPath,   int shortPathLength);

The CharSet= CharSet.Auto attribute is used to tell the CLR to automatically use the type of string supported by the OS, which is either ANSI or WCHAR (Unicode). If you do not declare the CharSet, the default is CharSet.Ansi which could cause performance problems under Win NT. You should declare CharSet as Auto unless the Win32 API call exist only as ANSI or WCHAR.

Calling Convention

The default calling convention is CallingConvention.StdCall which puts the function's last parameter on the stack first and the callee cleans the stack. You may need to set the CallingConvention to CallingConvention.Cdecl if the caller cleans the stack (Cdecl supports a variable number of arguments in a function, but C# does not support VARARG). The enum CallingConvention.Winapi automates the calling convention to StdCall on Windows and Cdecl on Windows CE .NET.

Creating and Calling a C++ Win32 DLL

You can create your own C++ Win32 dll using the Visual Studio Project Wizard. If you tell the wizard to create a dll and export the dll functions, you can see the exported functions using the DUMPBIN tool (You may need to modify the system path to include the dumpbin.exe and mspdb71.dll). Once you have the path set up correctly, run DUMPBIN with the /EXPORTS option. For example, if you use Visual Studio 2003 to create a C++ Win32 dll project named Win32Test and choose to export your functions, the wizard will create a sample exported function

int fnWin32Test(){return 42;}

Change the return type to long and then look at the exported function using dumpbin.

>dumpbin /exports Win32Test.dll

This outputs the exported fnWin32Test function as "?fnWin32Test@@3HA". To look at the undecorated name use the undname tool as in:

>undname ?fnWin32Test@@3HA

This converts the decorated name to "long fnWin32Test". 

It may be easier to use the undecorated name when calling from C#. You can do this by exporting the function using "C" linkage in your Win32 dll using the syntax:

extern "C" long declspect(dllexport)fnWin32Test() {...}

or

#ifdef WIN32TEST_EXPORTS#define WIN32TEST_API __declspec(dllexport)#else#define WIN32TEST_API __declspec(dllimport)#endif
extern "C" WIN32TEST_API long fnWin32Test();

You can now call this function from C# as:

class Class1{[DllImport("Win32Test.dll")]public static extern int fnWin32Test();/// <summary>/// The main entry point for the application./// </summary>[STAThread]static void Main(string[] args){//// TODO: Add code to start application here//System.Console.WriteLine(fnWin32Test());System.Console.ReadLine();}}

Output: 42. 

Note: The C++ return type long  has been mapped to the C# type int.

Using the EntryPoint DllImportAttribute 

You can rename an unmanaged dll function call from within C# using the EntryPoint attribute. To call a function using its fully decorated name "?fnWin32Test2@@YAJXZ" as "Win32Test2" you can specify the static entry point as "?fnWin32Test2@@YAJXY":

[DllImport("Win32Test.dll", EntryPoint= "?fnWin32Test2@@YAJXZ")]

public static extern int fnWin32Test2();

And call it as:

System.Console.WriteLine(fnWin32Test2());

原创粉丝点击