.net 调用 windowsApi
<p>在.Net Framework SDK文档中,关于调用Windows API的指示比较零散,并且其中稍全面一点的是针对Visual Basic .net讲述的。本文将C#中调用API的要点汇集如下,希望给未在C#中使用过API的朋友一点帮助。另外如果安装了Visual Studio .net的话,在C:\Program Files\Microsoft Visual Studio .NET\FrameworkSDK\Samples\Technologies\Interop\PlatformInvoke\WinAPIs\CS目录下有大量的调用API的例子。</p><p></p><p> 一、调用格式</p><p><br/> using System.Runtime.InteropServices; //引用此名称空间,简化后面的代码<br/> ...<br/> //使用DllImportAttribute特性来引入api函数,注意声明的是空方法,即方法体为空。<br/> <br/> public static extern ReturnType FunctionName(type arg1,type arg2,...);<br/> //调用时与调用其他方法并无区别 </p><p><br/> 可以使用字段进一步说明特性,用逗号隔开,如:</p><p><br/> [ DllImport( "kernel32", EntryPoint="GetVersionEx" )] </p><p> DllImportAttribute特性的公共字段如下:<br/> 1、CallingConvention 指示向非托管实现传递方法参数时所用的 CallingConvention 值。 <br/> CallingConvention.Cdecl : 调用方清理堆栈。它使您能够调用具有 varargs 的函数。<br/> CallingConvention.StdCall : 被调用方清理堆栈。它是从托管代码调用非托管函数的默认约定。</p><p> 2、CharSet 控制调用函数的名称版本及指示如何向方法封送 String 参数。</p><p> 此字段被设置为 CharSet 值之一。如果 CharSet 字段设置为 Unicode,则所有字符串参数在传递到非托管实现之前都转换成 Unicode 字符。这还导致向 DLL EntryPoint 的名称中追加字母“W”。如果此字段设置为 Ansi,则字符串将转换成 ANSI 字符串,同时向 DLL EntryPoint 的名称中追加字母“A”。</p><p> 大多数 Win32 API 使用这种追加“W”或“A”的约定。如果 CharSet 设置为 Auto,则这种转换就是与平台有关的(在 Windows NT 上为 Unicode,在 Windows 98 上为 Ansi)。CharSet 的默认值为 Ansi。CharSet 字段也用于确定将从指定的 DLL 导入哪个版本的函数。</p><p> CharSet.Ansi 和 CharSet.Unicode 的名称匹配规则大不相同。对于 Ansi 来说,如果将 EntryPoint 设置为“MyMethod”且它存在的话,则返回“MyMethod”。如果 DLL 中没有“MyMethod”,但存在“MyMethodA”,则返回“MyMethodA”。</p><p> 对于 Unicode 来说则正好相反。如果将 EntryPoint 设置为“MyMethod”且它存在的话,则返回“MyMethodW”。如果 DLL 中不存在“MyMethodW”,但存在“MyMethod”,则返回“MyMethod”。如果使用的是 Auto,则匹配规则与平台有关(在 Windows NT 上为 Unicode,在 Windows 98 上为 Ansi)。如果 ExactSpelling 设置为 true,则只有当 DLL 中存在“MyMethod”时才返回“MyMethod”。</p><p> 3、EntryPoint 指示要调用的 DLL 入口点的名称或序号。 <br/> 如果你的方法名不想与api函数同名的话,一定要指定此参数,例如:</p><p><br/> <br/> public static extern int MsgBox(IntPtr hWnd,string txt,string caption, int type); </p><p><br/> 4、ExactSpelling 指示是否应修改非托管 DLL 中的入口点的名称,以与 CharSet 字段中指定的 CharSet 值相对应。如果为 true,则当 DllImportAttribute.CharSet 字段设置为 CharSet 的 Ansi 值时,向方法名称中追加字母 A,当 DllImportAttribute.CharSet 字段设置为 CharSet 的 Unicode 值时,向方法的名称中追加字母 W。此字段的默认值是 false。 </p><p> 5、PreserveSig 指示托管方法签名不应转换成返回 HRESULT、并且可能有一个对应于返回值的附加 参数的非托管签名。 </p><p> 6、SetLastError 指示被调用方在从属性化方法返回之前将调用 Win32 API SetLastError。 true 指示调用方将调用 SetLastError,默认为 false。运行时封送拆收器将调用 GetLastError 并缓存返回的值,以防其被其他 API 调用重写。用户可通过调用 GetLastWin32Error 来检索错误代码。<br/>二、参数类型:</p><p> 1、数值型直接用对应的就可。(DWORD -> int , WORD -> Int16)<br/> 2、API中字符串指针类型 -> .net中string<br/> 3、API中句柄 (dWord) -> .net中IntPtr<br/> 4、API中结构 -> .net中结构或者类。注意这种情况下,要先用StructLayout特性限定声明结构或类</p><p> 公共语言运行库利用StructLayoutAttribute控制类或结构的数据字段在托管内存中的物理布局,即类或结构需要按某种方式排列。如果要将类传递给需要指定布局的非托管代码,则显式控制类布局是重要的。它的构造函数中用LayoutKind值初始化 StructLayoutAttribute 类的新实例。 LayoutKind.Sequential 用于强制将成员按其出现的顺序进行顺序布局。</p><p> LayoutKind.Explicit 用于控制每个数据成员的精确位置。利用 Explicit, 每个成员必须使用 FieldOffsetAttribute 指示此字段在类型中的位置。如:</p><p><br/> <br/> public class MySystemTime <br/> {<br/> public ushort wYear; <br/> public ushort wMonth;<br/> public ushort wDayOfWeek; <br/> public ushort wDay; <br/> public ushort wHour; <br/> public ushort wMinute; <br/> public ushort wSecond; <br/> public ushort wMilliseconds; <br/> } </p><p> 下面是针对API中OSVERSIONINFO结构,在.net中定义对应类或结构的例子:<br/> <br/> /**********************************************<br/> * API中定义原结构声明<br/> * OSVERSIONINFOA STRUCT<br/> * dwOSVersionInfoSize DWORD ?<br/> * dwMajorVersion DWORD ?<br/> * dwMinorVersion DWORD ?<br/> * dwBuildNumber DWORD ?<br/> * dwPlatformId DWORD ?<br/> * szCSDVersion BYTE 128 dup (?)<br/> * OSVERSIONINFOA ENDS<br/> *<br/> * OSVERSIONINFO equ <OSVERSIONINFOA><br/> *********************************************/ </p><p></p><p> //.net中声明为类<br/> [ StructLayout( LayoutKind.Sequential )] <br/> public class OSVersionInfo <br/> { <br/> public int OSVersionInfoSize;<br/> public int majorVersion; <br/> public int minorVersion;<br/> public int buildNumber;<br/> public int platformId;<br/> [ MarshalAs( UnmanagedType.ByValTStr, SizeConst=128 )] <br/> public String versionString;<br/> }<br/> //或者<br/> //.net中声明为结构<br/> [ StructLayout( LayoutKind.Sequential )] <br/> public struct OSVersionInfo2 <br/> {<br/> public int OSVersionInfoSize;<br/> public int majorVersion; <br/> public int minorVersion;<br/> public int buildNumber;<br/> public int platformId;<br/> </p><p><br/> [ MarshalAs( UnmanagedType.ByValTStr, SizeConst=128 )] <br/> public String versionString;<br/> } </p><p><br/> 此例中用到MashalAs特性,它用于描述字段、方法或参数的封送处理格式。用它作为参数前缀并指定目标需要的数据类型。例如,以下代码将两个参数作为数据类型长指针封送给 Windows API 函数的字符串 (LPStr): </p><p><br/> <br/> String existingfile;<br/> <br/> String newfile; </p><p><br/> 注意结构作为参数时候,一般前面要加上ref修饰符,否则会出现错误:对象的引用没有指定对象的实例。</p><p><br/> [ DllImport( "kernel32", EntryPoint="GetVersionEx" )] <br/> public static extern bool GetVersionEx2( ref OSVersionInfo2 osvi ); </p><p><br/> 三、如何保证使用托管对象的平台调用成功?<br/> 如果在调用平台 invoke 后的任何位置都未引用托管对象,则垃圾回收器可能将完成该托管对象。这将释放资源并使句柄无效,从而导致平台invoke 调用失败。用 HandleRef 包装句柄可保证在平台 invoke 调用完成前,不对托管对象进行垃圾回收。</p><p> 例如下面:</p><p> FileStream fs = new FileStream( "a.txt", FileMode.Open );<br/> StringBuilder buffer = new StringBuilder( 5 );<br/> int read = 0;<br/> ReadFile(fs.Handle, buffer, 5, out read, 0 ); //调用Win API中的ReadFile函数 </p><p><br/> 由于fs是托管对象,所以有可能在平台调用还未完成时候被垃圾回收站回收。将文件流的句柄用HandleRef包装后,就能避免被垃圾站回收:</p><p><br/> [ DllImport( "Kernel32.dll" )]<br/> public static extern bool ReadFile( <br/> HandleRef hndRef, <br/> StringBuilder buffer, <br/> int numberOfBytesToRead, <br/> out int numberOfBytesRead, <br/> ref Overlapped flag );<br/> ......<br/> ......<br/> FileStream fs = new FileStream( "HandleRef.txt", FileMode.Open );<br/> HandleRef hr = new HandleRef( fs, fs.Handle );<br/> StringBuilder buffer = new StringBuilder( 5 );<br/> int read = 0;<br/> // platform invoke will hold reference to HandleRef until call ends<br/> ReadFile( hr, buffer, 5, out read, 0 ); </p><p></p> 什么东西啊,完全看不懂
页:
[1]