前文中说到DllImport用法汇总,说到DllImportAttribute用法,现在结合MSDN,现在把DllImportAttribute下4个字段的用法汇总如下:
DllImportAttribute属性提供调用非托管函数的规范。在对托管代码进行P/Invoke调用时,DllImportAttribute类型扮演着重要的角色。DllImportAttribute的主要作用就是给CLR指示哪个DLL导出您想要的调用的函数。相关DLL的名称被作为一个构造函数参数传递给DllImportAttribute。
如果您无法肯定哪个 DLL 定义了您要使用的 Windows API 函数,Platform SDK 文档将为您提供最好的帮助资源。在 Windows API 函数主题文字临近结尾的位置,SDK 文档指定了 C 应用程序要使用该函数必须链接的 .lib 文件。在几乎所有的情况下,该 .lib 文件具有与定义该函数的系统 DLL 文件相同的名称。例如,如果该函数需要 C 应用程序链接到 Kernel32.lib,则该函数就定义在 Kernel32.dll 中。您可以在 MessageBeep 中找到有关 MessageBeep 的 Platform SDK 文档主题。在该主题结尾处,您会注意到它指出库文件是 User32.lib;这表明 MessageBeep 是从 User32.dll 中导出的。
可选的 DllImportAttribute 属性除了指出宿主 DLL 外,DllImportAttribute 还包含了一些可选属性,其中四个特别有趣:EntryPoint、CharSet、SetLastError 和 CallingConvention。EntryPoint指定要调用的DLL入口点的名称或序号(默认入口点名称就是托管方法的名称)。序号以“#”符号为前缀,如#1。如果省略此字段,则CLR将使用以DllImportAttribute标记的.NET方法的名称。EntryPoint 在不希望外部托管方法具有与 DLL 导出相同的名称的情况下,可以设置该属性来指示导出的 DLL 函数的入口点名称。当您定义两个调用相同非托管函数的外部方法时,这特别有用。另外,在 Windows 中还可以通过它们的序号值绑定到导出的 DLL 函数。如果您需要这样做,则诸如“#1”或“#129”的 EntryPoint 值指示 DLL 中非托管函数的序号值而不是函数名。下面的代码示例(语言:C#)演示如何使用 DllImportAttribute 属性导入 Win32 MessageBox 函数。 该代码示例使用 EntryPoint 属性指定要导入的函数,然后将名称更改为 MyNewMessageBoxMethod。using System;
using System.Runtime.InteropServices;
class Example
{// Use DllImport to import the Win32 MessageBox function.
// Specify the method to import using the EntryPoint field and
// then change the name to MyNewMessageBoxMethod.
[DllImport("user32.dll", CharSet = CharSet.Auto, EntryPoint = "MessageBox")]
public static extern int MyNewMessageBoxMethod(IntPtr hWnd, String text, String caption, uint type);static void Main(){// Call the MessageBox function using platform invoke.
MyNewMessageBoxMethod(new IntPtr(0), "Hello World!", "Hello Dialog", 0);}}CharSet指示如何向方法封送字符串参数。并控制名称重整。通过一个 CharSet 枚举的成员使用此字段指定字符串参数的封送处理行为,并指定要调用的入口点名称(给定的确切名称或以“A”、“W”结尾的名称)。用于 C# 和 Visual Basic 的默认枚举成员为 CharSet.Ansi,用于 C++ 的默认枚举成员为 CharSet.None,它与 CharSet.Ansi 等效。在 Visual Basic 中可以使用 Declare 语句指定 CharSet 字段。
ExactSpelling 字段会影响 CharSet 字段在确定要调用的入口点名称时的行为。
CharSet 对于字符集,并非所有版本的 Windows 都是同样创建的。Windows 9x 系列产品缺少重要的 Unicode 支持,而 Windows NT 和 Windows CE 系列则一开始就使用 Unicode。在这些操作系统上运行的 CLR 将Unicode 用于 String 和 Char 数据的内部表示。但也不必担心 — 当调用 Windows 9x API 函数时,CLR 会自动进行必要的转换,将其从 Unicode转换为 ANSI。如果 DLL 函数不以任何方式处理文本,则可以忽略 DllImportAttribute 的 CharSet 属性。然而,当 Char 或 String 数据是等式的一部分时,应该将 CharSet 属性设置为 CharSet.Auto。这样可以使 CLR 根据宿主 OS 使用适当的字符集。如果没有显式地设置 CharSet 属性,则其默认值为 CharSet.Ansi。这个默认值是有缺点的,因为对于在 Windows 2000、Windows XP 和 Windows NT® 上进行的 interop 调用,它会消极地影响文本参数封送处理的性能。 应该显式地选择 CharSet.Ansi 或 CharSet.Unicode 的 CharSet 值而不是使用 CharSet.Auto 的唯一情况是:您显式地指定了一个导出函数,而该函数特定于这两种 Win32 OS 中的某一种。ReadDirectoryChangesW API 函数就是这样的一个例子,它只存在于基于 Windows NT 的操作系统中,并且只支持 Unicode;在这种情况下,您应该显式地使用 CharSet.Unicode。 有时,Windows API 是否有字符集关系并不明显。一种决不会有错的确认方法是在 Platform SDK 中检查该函数的 C 语言头文件。(如果您无法肯定要看哪个头文件,则可以查看 Platform SDK 文档中列出的每个 API 函数的头文件。)如果您发现该 API 函数确实定义为一个映射到以 A 或 W 结尾的函数名的宏,则字符集与您尝试调用的函数有关系。Windows API 函数的一个例子是在 WinUser.h 中声明的 GetMessage API,您也许会惊讶地发现它有 A 和 W 两种版本。示例同上。SetLastError用来指示被调用方在从属性化方法返回之前是否调用SetLastError Win32 API函数。true 指示被调用方将调用 SetLastError;否则为 false。默认值为 false。运行时封送拆收器将调用 GetLastError 并缓存返回的值,以防其被其他 API 调用覆盖。SetLastError 错误处理非常重要,但在编程时经常被遗忘。当您进行 P/Invoke 调用时,也会面临其他的挑战 — 处理托管代码中 Windows API 错误处理和异常之间的区别。我可以给您一点建议。 如果您正在使用 P/Invoke 调用 Windows API 函数,而对于该函数,您使用 GetLastError 来查找扩展的错误信息,则应该在外部方法的 DllImportAttribute 中将 SetLastError 属性设置为 true。这适用于大多数外部方法。 这会导致 CLR 在每次调用外部方法之后缓存由 API 函数设置的错误。然后,在包装方法中,可以通过调用类库的 System.Runtime.InteropServices.Marshal 类型中定义的 Marshal.GetLastWin32Error 方法来获取缓存的错误值。我的建议是检查这些期望来自 API 函数的错误值,并为这些值引发一个可感知的异常。对于其他所有失败情况(包括根本就没意料到的失败情况),则引发在 System.ComponentModel 命名空间中定义的 Win32Exception,并将 Marshal.GetLastWin32Error 返回的值传递给它。如果您回头看一下上文的代码,您会看到我在 extern MessageBeep 方法的公共包装中就采用了这种方法。示例:using System.Runtime.InteropServices;public class Win32{[DllImport("user32.dll",SetLastError=true)]public static extern int MessageBoxA(int hWnd,String text,String caption,uint type);}CallingConvention指定传递方法参数时使用的调用约定值。默认值为 CallingConvention.Winapi(Winapi默认StdCall约定),它在大多数情况下都可行。该值与 Windows CE 平台上的 __cdecl 相对应。
然而,如果该调用不起作用,则可以检查 Platform SDK 中的声明头文件,看看您调用的 API 函数是否是一个不符合调用约定标准的异常 API。通常,本机函数(例如 Windows API 函数或 C- 运行时 DLL 函数)的调用约定描述了如何将参数推入线程堆栈或从线程堆栈中清除。大多数 Windows API 函数都是首先将函数的最后一个参数推入堆栈,然后由被调用的函数负责清理该堆栈。相反,许多 C-运行时 DLL 函数都被定义为按照方法参数在方法签名中出现的顺序将其推入堆栈,将堆栈清理工作交给调用者。 幸运的是,要让 P/Invoke 调用工作只需要让外围设备理解调用约定即可。通常,从默认值 CallingConvention.Winapi 开始是最好的选择。然后,在 C 运行时 DLL 函数和少数函数中,可能需要将约定更改为 CallingConvention.Cdecl。示例:在某些情况下,Visual Basic 开发人员会使用 DllImportAttribute 来代替 Declare 语句,在托管代码中定义 DLL 函数。设置 CallingConvention 字段即属于这种情况。
using System; using System.Runtime.InteropServices; public class LibWrap { // C# doesn't support varargs so all arguments must be explicitly defined. // CallingConvention.Cdecl must be used since the stack is // cleaned up by the caller. // int printf(const char *format [, argument]...) [DllImport("msvcrt.dll", CharSet=CharSet.Ansi, CallingConvention=CallingConvention.Cdecl)] public static extern int printf(String format, int i, double d); [DllImport("msvcrt.dll", CharSet=CharSet.Ansi, CallingConvention=CallingConvention.Cdecl)] public static extern int printf(String format, int i, String s); } public class App { public static void Main() { LibWrap.printf("\nPrint params: %i %f", 99, 99.99); LibWrap.printf("\nPrint params: %i %s", 99, "abcd"); } }上文根据百度知道(http://zhidao.baidu.com/question/59216438.html)和MSDN整理。