.Net P/Invoke学习日记(二)

来源:互联网 发布:出国劳务移民知乎 编辑:程序博客网 时间:2024/05/17 22:09
在.NET里面调用非托管DLL,这里就用本地调用Win32 Api来说,首先需要确定的是非托管函数的原型->.NET函数的映射关系。WIN32 API可以在MSDN以及很多工具里面查到,里面定义的常量,结构也可以在相关的头文件里面找到,比较麻烦。这里推荐一个好工具API浏览器.NET,可以方便的查询数千个API以及结构,上万个常量。该软件需要免费注册,不过没注册也没关系,只有一些小小的限制而已。: )  确定了非托管代码原型后,就可以在.NET里面写对应的函数了。这里需要注意的是,.NET提供了方便的映射方法——“extern”关键字,在C#2.0和VC++2005/CLI里都这样。采用特性标记来标明该方法的封送方式,引用DLL等属性。如:
[DllImport("user32.dll",EntryPoint="MessageBox",CharSet=CharSet.Auto)]
 public static extern int MessageBox(int hWnd, String text, String caption, uint type);
DllImport特性的属性可以在MSDN里面找到。注意的几点是,该特性的构造函数需要包含非托管函数的DLL的路径,如果未指定,则采用默认策略尝试搜索,如果搜索不到,则抛出"DllNotFoundException",这里可以省略“.dll”。其次,如果想完全按照原函数的签名进行调用,那么不用指定EntryPoint命名参数。实际上,指定该入口点,对于我们在.NET里面声明新的方法映射到该非托管函数是非常有用的。一般来说,通常CharSet字符集在C#里面默认是CharSet.Ansi,通过反编译,我们可以看到该枚举:
[Serializable, ComVisible(true)]
public enum CharSet
{
      // Fields
      Ansi = 2,
      Auto = 4,
      None = 1,
      Unicode = 3
}
   指定了入口点后,就可以编写自己的映射函数了,函数名可以不一样。但是参数个数一定要一样。至于参数类型,有必要参考MSDN上的“平台调用数据类型”来进行映射。此外,MSDN也有完整的源码可以参考。这里值得注意的是,由于32位CPU架构的特性,有的类型之间可以互换,.NET在进行封送处理的时候,不严格检查类型。所以,这也给我们带来了很好的灵活性。就用C#调用 WIN32 API GetSystemInfo函数来说:
函数的原型为void GetSystemInfo(LPSYSTEM_INFO lpSystemInfo); 通过MSDN可以知道LPSYSTEM_INFO是一个指向SYSTEM_INFO结构的 指针。而SYSTEM_INFO定义为:
typedef struct _SYSTEM_INFO { 
union {   
    DWORD dwOemId;
    struct { 
         WORD wProcessorArchitecture;
          WORD wReserved;
      }; 
}; 
DWORD dwPageSize; 
LPVOID lpMinimumApplicationAddress; 
LPVOID lpMaximumApplicationAddress; 
DWORD_PTR dwActiveProcessorMask; 
DWORD dwNumberOfProcessors; 
DWORD dwProcessorType; 
DWORD dwAllocationGranularity; 
WORD wProcessorLevel; 
WORD wProcessorRevision;
} SYSTEM_INFO;
由此可以声明结构,
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEM_INFO
{
public uint dwOemId;
public uint dwPageSize;
public uint lpMinimumApplicationAddress;
public uint lpMaximumApplicationAddress;
public uint dwActiveProcessorMask;
public uint dwNumberOfProcessors;
public uint dwProcessorType;
public uint dwAllocationGranularity;
public uint dwProcessorLevel;
public uint dwProcessorRevision;
}
这里,注意里面有个union,我们选择用它的第一个OemId填充。根据MSDN对该参数lpSystemInfo的说明,是一个out参数。所以,我们映射函数原型如下:
[DllImport("kernel32.dll", CharSet=CharSet.Auto, EntryPoint = "GetSystemInfo")]
public static extern void GetSystemInfomation( ref SYSTEM_INFO info );
其中,ref 的默认封送格式是[in,out],我们这里也可以单指定out。这是C#的语法支持。不过MSDN并不推荐这样做。
如此,就可以方便的调用了:代码如下
SYSTEM_INFO info = new SYSTEM_INFO();
.GetSystemInfo([out或者ref,和申明一样即可] info);
这样API填充了info结构的字段,我就可以获取信息。比如info.dwProcessorType。唔,显示是586。。。这个常数原定义为PROCESSOR_INTEL_PENTIUM。^_^
还可以用另一种方法调用,既然是结构,就可以利用C#不安全代码,于是声明如下:
[DllImport("kernel32.dll", CharSet=CharSet.Auto, EntryPoint = "GetSystemInfo")]
public unsafe static extern void GetSystemInfo( SYSTEM_INFO* info );
这里需要注意的是,由于使用了非托管指针,所以必须用关键字unsafe,并打开/unsafe编译选项(当然,如果用C++/CLI自然不用管了。)于是,调用代码也就是:
unsafe
{
     SYSTEM_INFO _info;
     SYSTEM_INFO* pi = &_info;
     GetSystemInfo(pi);
}
如此,就可以用pi->dwProcessorType来获取字段(总算找到点C++的味道。。。)
这里值得注意的是,由于SYSTEM_INFO里的字段都是值类型的,所以可以用&运算符在unsafe下获取改结构的地址,如果里面有托管引用类型,比如String,StringBuilder,IntPtr,那么无法获取其大小也自然无法得知其地址了。

很多API里面的结构参数允许为空(NULL),这在C/C++里面分别定义为0/(void *)0,性质都是32位的0x0。但是在.NET下面采用强类型,NULL只作用在引用类型上。(注:.NET下结构可采用Nullable<T>泛型,但是该技巧无法应用在平台调用上)。所以,对于要求可以声明该结构为class,而不是struct,同样要标明布局模式为Sequential,如此,就可以在调用时传递null了。其实,完全可以按照上面一样声明一个结构,和一个非托管指针,由于指针可以设置为null,故调用的时候只需把该指针传入。还有个更为简洁的技巧,在确定本次调用某结构指针参数为NULL的情况下,函数原型声明中,可以声明该参数类行为int,调用时直接用0即可。