.Net PInvoke学习日记(二)

    技术2022-05-11  75

    在.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即可。 

    最新回复(0)