现在是更深入地进行探讨的时候了。在对托管代码进行 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. Win7中隐藏的上帝模式——GodMode

    Win7中隐藏的上帝模式——GodMode ~ Windows7中的隐藏模式 ~   随意新建一个文件夹吧,然后重命名为:   GodMode.{ED7BA470-8E54-465E-825C-997 ...

  2. 大数求模 sicily 1020

        Search

  3. 返璞归真vc++之字符类型

    在今天,大量使用java与.net的程序员已经很少去真实了解字符的底层表达,但是使用VC++编程,对字符的处理却非常慎重,刚学习vc++肯定会为其中的字符类型给晕头转向,今天本人学习第一节,从字符开始 ...

  4. JAVA泛型? T K V E含义

    ? 表示不确定的java类型,类型是未知的. T  表示java类型. K V 分别代表java键值中的Key Value. E 代表Element,特性是枚举.

  5. 关于IOS9更新的适应与适配

    最下面一行为刚刚添加的 iOS9中新增App Transport Security(简称ATS)特性, 主要使到原来请求的时候用到的HTTP,都转向TLS1.2协议进行传输.这也意味着所有的HTTP协 ...

  6. Lucene使用IKAnalyzer分词实例 及 IKAnalyzer扩展词库

    文章转载自:http://www.cnblogs.com/dennisit/archive/2013/04/07/3005847.html 方案一: 基于配置的词典扩充 项目结构图如下: IK分词器还 ...

  7. Spark 大数据平台 Introduction part 2 coding

    Basic Functions sc.parallelize(List(1,2,3,4,5,6)).map(_ * 2).filter(_ > 5).collect() *** res: Arr ...

  8. perl中shift 和unshift 操作

    ##################################################################### unshift 和shift 对一个数组的开头进行操作(数组 ...

  9. xv6实验环境搭建

    安装bochs 因为要运行的是xv6,所以不能直接使用 apt-get 直接获取软件.apt-get获取到的软件不支持SMP (Symmetric Multi-Processing).因此,需要下载源 ...

  10. 使用Sass优雅并高效的实现CSS中的垂直水平居中(附带Flex布局,CSS3+SASS完美版)

    实现css水平垂直居中的方法有很多,在这里我简单的说下四种比较常用的方法: 1.使用CSS3中的Flex布局 对于flex,我们要了解的是它是一个display的属性,而且必须要给他的父元素设置fle ...