现在是更深入地进行探讨的时候了。在对托管代码进行 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 导出相同的名称的情况下,可以设置该属性来指示导出的 DLL
函数的入口点名称。当您定义两个调用相同非托管函数的外部方法时,这特别有用。另外,在 Windows
中还可以通过它们的序号值绑定到导出的 DLL 函数。如果您需要这样做,则诸如“#1”或“#129”的 EntryPoint 值指示
DLL 中非托管函数的序号值而不是函数名。

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 错误处理非常重要,但在编程时经常被遗忘。当您进行 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 返回的值传递给它。如果您回头看一下图 1 中的代码,您会看到我在 extern
MessageBeep 方法的公共包装中就采用了这种方法。

CallingConvention 我将在此介绍的最后也可能是最不重要的一个 DllImportAttribute 属性是
CallingConvention。通过此属性,可以给 CLR
指示应该将哪种函数调用约定用于堆栈中的参数。CallingConvention.Winapi
的默认值是最好的选择,它在大多数情况下都可行。然而,如果该调用不起作用,则可以检查 Platform SDK
中的声明头文件,看看您调用的 API 函数是否是一个不符合调用约定标准的异常 API。

通常,本机函数(例如 Windows API 函数或 C- 运行时 DLL
函数)的调用约定描述了如何将参数推入线程堆栈或从线程堆栈中清除。大多数 Windows API
函数都是首先将函数的最后一个参数推入堆栈,然后由被调用的函数负责清理该堆栈。相反,许多 C-运行时 DLL
函数都被定义为按照方法参数在方法签名中出现的顺序将其推入堆栈,将堆栈清理工作交给调用者。

幸运的是,要让 P/Invoke 调用工作只需要让外围设备理解调用约定即可。通常,从默认值
CallingConvention.Winapi 开始是最好的选择。然后,在 C 运行时 DLL
函数和少数函数中,可能需要将约定更改为 CallingConvention.Cdecl。

DllImport的具体用法的更多相关文章

  1. System.ServiceProcess与System.Configuration.Install命名空间的介绍

    System.ServiceProcess 命名空间提供用于实现.安装和控制 Windows 服务应用程序的类.服务是长期运行的可执行文件,其运行没有用户界面 System.ServiceProces ...

  2. C#6.0语言规范(十) 类

    类是可以包含数据成员(常量和字段),函数成员(方法,属性,事件,索引器,运算符,实例构造函数,析构函数和静态构造函数)和嵌套类型的数据结构.类类型支持继承,这是一种派生类可以扩展和专门化基类的机制. ...

  3. C# DllImport的用法

    大家在实际工作学习C#的时候,可能会问:为什么我们要为一些已经存在的功能(比如Windows中的一些功能,C++中已经编写好的一些方法)要重新编写代码,C#有没有方法可以直接都用这些原本已经存在的功能 ...

  4. .Net中C#的DllImport的用法

    大家在实际工作学习C#的时候,可能会问:为什么我们要为一些已经存在的功能(比如 Windows中的一些功能,C++中已经编写好的一些方法)要重新编写代码,C#有没有方法可以直接都用这些原本已经存在的功 ...

  5. 【转帖】.Net中C#的DllImport的用法

    在 C# 中通过 P/Invoke 调用Win32 DLL http://msdn.microsoft.com/zh-cn/library/aa686045.aspx   大家在实际工作学习C#的时候 ...

  6. DLLImport的用法C#

    它来调用WIN32的API或者调用一下C或C++编写的DLL.使用实例:将编译好的C++ DLL拷贝到BIN目录(DLLImport会从程序启动目录BIN开始查找相应名称的DLL,未找到则转至syst ...

  7. C#中DllImport用法汇总

    最近使用DllImport,从网上google后发现,大部分内容都是相同,又从MSDN中搜集下,现将内容汇总,与大家分享. 大家在实际工作学习C#的时候,可能会问:为什么我们要为一些已经存在的功能(比 ...

  8. C#中DllImport用法

    http://blog.csdn.net/u011981242/article/details/52622923 http://www.jb51.net/article/46384.htm 读取身份证 ...

  9. C# 关键字extern用法

    修饰符用于声明在外部实现的方法.extern 修饰符的常见用法是在使用 Interop 服务调入非 托管代码时与 DllImport 属性一起使用:在这种情况下,该方法还必须声明为 static,如下 ...

随机推荐

  1. javaScript之 变量、作用域和内存问题

    <javaScript高级程序设计>第四章  读书笔记 4.1  基本类型 和 引用类型 的值 1. 基本类型值 包括:Undefined.Null.Boolean.Number 和 St ...

  2. python——异常处理

    python处理异常的格式一般如下: try: A部分 Except  .... [as ..]: B部分 [ finally: C部分 Else: D部分 ] ps: 1.不管有没有异常,final ...

  3. 利用rsyslog 对linux 操作进行审计

    环境:客户端和服务端都需要安装rsyslog服务 rsyslog  server端 cd /etc/rsyslog.d/ cat server.conf $ModLoad imtcp $InputTC ...

  4. <hash命令:显示、添加或清除哈希表>

    linux系统下的hash指令: 说明:linux系统下会有一个hash表,当你刚开机时这个hash表为空,每当你执行过一条命令时,hash表会记录下这条命令的路径,就相当于缓存一样.第一次执行命令s ...

  5. UILabel滚动字幕的实现

    经常需要在应用中显示一段很长的文字,比如天气或者广告等,这时候使用滚动字幕的方式比较方便. 参考文献: [1] YouXianMing, 使用UILabel实现滚动字幕移动效果, 博客园 [2] ht ...

  6. javascript Date 函数的坑

    Javascrip中对日期的解析.格式化简直是混乱不堪,各种的坑,攻城狮们多小心.   1. 不要用 Date("yyyy-mm-dd") 构造函数解析字符串. IE.firefo ...

  7. 简单的jQuery获取URL的?后带的参数

    var con_name = getQueryString("con_name"); //接收con_name        function getQueryString(val ...

  8. IOS_修改项目模板

    1. /Applications/Xcode.app/Contents/Developer/Library/Xcode/Templates/File\ Templates/Source/Cocoa\ ...

  9. 为什么要用ajax

    Ajax应用程序的优势在于:1. 通过异步模式,提升了用户体验2. 优化了浏览器和服务器之间的传输,减少不必要的数据往返,减少了带宽占用3. Ajax引擎在客户端运行,承担了一部分本来由服务器承担的工 ...

  10. WPF自定义控件(三)——Window

    一样!先来看看效果吧: 怎么样?效果很好吧,而且不只是样式哟!所有系统窗体有的交互操作都可以实现! 但可惜...有很多和系统API有关的东西本人了解得并不多,所以这个窗体是基于他人的成果上产生的.关于 ...