C#中调用Dll动态链接库

起始

受限于语言的不同,我们有的时候可能会用别人提供的函数及方法

或者其他的什么原因、反正就是要调!!!

恰巧别人所使用的的语言跟自己又不是一样的

这个时候想要调用别人的函数库就需要借用一些别的东西了

今天我们要说的是“UnmanagedExports”

当前我所要实现的目的只是为某一QQ机器人编写插件

但我又不喜欢某中文编程语言,编程习惯导致 233333

在这里我们还可以使用进程间UDP通信来解决这个问题(编写插件的问题)

但是这种方法局限性比较大,操作起来又略显繁琐

所以今天介绍一下“UnmanagedExports”这个nuget包

经过

打开nuget包管理器,为你所在的项目的安装上这个包,这里就不在复述了

之后便可以以类似下面的写法来调用Dll

首先需要声明需要调用的函数及其对用的Dll

[DllImport("user32.dll")]//DllImportAttribute
public static extern int MsgBox(int hWnd, String text, String caption, uint type);

这里告诉编译器我们需要调用的Dll名称及其对应的方法定义

使用“extern”关键字来标识这个方法是从外部引用

关于“DllImportAttribute”的属性会在下面讲到

DllImportAttribute详解

DllImportAttribute是一个重要的角色,其主要作用是给CLR指示哪个Dll是需要调用的外部库。

字段 说明
BestFitMapping 启用或禁用最佳匹配映射。
CallingConvention 指定用于传递方法参数的调用约定。 默认值为 WinAPI,该值对应于基于 32 位 Intel 的平台的 __stdcall。
CharSet 控制名称重整以及将字符串参数封送到函数中的方式。 默认值为 CharSet.Ansi。
EntryPoint 指定要调用的 DLL 入口点。
ExactSpelling 控制是否应修改入口点以对应于字符集。 对于不同的编程语言,默认值将有所不同。
PreserveSig 控制托管方法签名是否应转换成返回 HRESULT 并且返回值有一个附加的 [out, retval] 参数的非托管签名。默认值为 true(不应转换签名)。
SetLastError 允许调用方使用 Marshal.GetLastWin32Error API 函数来确定执行该方法时是否发生了错误。 在 Visual Basic 中,默认值为 true;在 C# 和 C++ 中,默认值为 false。
ThrowOnUnmappableChar 控件引发的异常,将无法映射的 Unicode 字符转换成一个 ANSI"?"字符。

除了指出所调用的Dll外,DllImportAttribute还包含了一些可选属性

其中有以下几个比较常用:

entrypoint

入口点,用于标识函数在Dll中的位置。

你可以将入口点映射到一个不用的名称,这实际上就是将被调用的函数重命名。

这里也说明以下重命名Dll函数的可能原因

  • 避免使用区分大小写的API函数名
  • 符合现行的命名标准
  • 提供采用不同数据类型的函数(通过声明同一Dll函数的多个版本)
  • 简化对包含ANSI和Unicode版本的API的使用
[DllImport("dllname", EntryPoint="MyFunctionname")]
[DllImport("dllname", EntryPoint="#123")]

指定入口点名称时,您可以提供一个字符串来指示包含入口点的 DLL 的名称,或者也可以按序号来标识入口点。序号以 # 符号为前缀,如 #1。(序号看不太明白,不用先)

下面来演示一下如何使用Entrypoint字段将我们自己的函数MessageBoxA映射(替换)为Dll库中的MsgBox

[DllImport("user32.dll", EntryPoint="MessageBoxA")]
public static extern int MsgBox(int hWnd, String text, String caption, uint type);

CharSet(部分摘自MSDN)

charset字段控制字符串封送处理并确定平台调用在dll查找函数名的方式。

对于采用字符串参数的函数,有些 API 将导出它们的两个版本:窄版本 (ANSI) 和宽版本 (Unicode)。例如,Win32 API 包含 MessageBox 函数的以下入口点名称:

  • MessageBoxA

提供单字节字符 ANSI 格式,其特征是在入口点名称后附加一个“A”。对 MessageBoxA 的调用始终会以 ANSI 格式封送字符串,它常见于 Windows 95 和 Windows 98 平台。

  • MessageBoxW

提供双字节字符 Unicode 格式,其特征是在入口点名称后附加一个“W”。对 MessageBoxW 的调用始终会以 Unicode 格式封送字符串,它常见于 Windows NT、Windows 2000 和 Windows XP 平台。

CharSet 字段接受以下值:

CharSet.Ansi(默认值)

  • 字符串封送处理

平台调用将字符串从托管格式 (Unicode) 封送为 ANSI 格式。

  • 名称匹配

在 ExactSpelling 字段为 true(它是 Visual Basic 2005 中的默认值)时,平台调用将只搜索您指定的名称。例如,如果指定MessageBox,则平台调用将搜索 MessageBox,如果它找不到完全相同的拼写则失败。

当 ExactSpelling 字段为 false(它是 C++ 和 C# 中的默认值)时,平台调用将首先搜索未处理的别名 (MessageBox),如果找不到未处理的别名,则将搜索已处理的名称 (MessageBoxA)。请注意,ANSI 名称匹配行为与 Unicode 名称匹配行为不同。

CharSet.Unicode

  • 字符串封送处理

平台调用会将字符串从托管格式 (Unicode) 复制为 Unicode 格式。

  • 名称匹配

当 ExactSpelling 字段为 true(它是 Visual Basic 2005 中的默认值)时,平台调用将只搜索您指定的名称。例如,如果指定MessageBox,则平台调用将搜索 MessageBox,如果它找不到完全相同的拼写则失败。

当 ExactSpelling 字段为 false(它是 C++ 和 C# 中的默认值)时,平台调用将首先搜索已处理的名称 (MessageBoxW),如果找不到已处理的名称,则将搜索未处理的别名 (MessageBox)。请注意,Unicode 名称匹配行为与 ANSI 名称匹配行为不同。

CharSet.Auto

  • 平台调用在运行时根据目标平台在 ANSI 和 Unicode 格式之间进行选择。( 针对目标操作系统适当地自动封送字符串。在 Windows NT、Windows 2000、Windows XP 和 Windows Server2003 系列上默认值为 System.Runtime.InteropServices.CharSet.Unicode;在 Windows 98

    和 Windows Me 上默认值为 System.Runtime.InteropServices.CharSet.Ansi。)

下面的示例演示用于指定字符集的 MessageBox 函数的三个托管定义。在第一个定义中,通过省略,使 CharSet 字段默认为 ANSI 字符集。



[DllImport("user32.dll")]

public static extern int MessageBoxA(int hWnd, String text, String caption, uint type);

[DllImport("user32.dll", CharSet=CharSet.Unicode)]

public static extern int MessageBoxW(int hWnd, String text, String caption, uint type);

[DllImport("user32.dll", CharSet=CharSet.Auto)]

public static extern int MessageBox(int hWnd, String text, String caption, uint type);

CharSet.Ansi 和 CharSet.Unicode 的名称匹配规则大不相同。对于 Ansi 来说,如果将 EntryPoint 设置为“MyMethod”且它存在的话,则返回“MyMethod”。如果 DLL 中没有“MyMethod”,但存在“MyMethodA”,则返回“MyMethodA”。对于 Unicode 来说则正好相反。如果将 EntryPoint 设置为“MyMethod”且它存在的话,则返回“MyMethodW”。如果 DLL 中不存在“MyMethodW”,但存在“MyMethod”,则返回“MyMethod”。如果使用的是 Auto,则匹配规则与平台有关(在 Windows NT 上为 Unicode,在 Windows 98 上为 Ansi)。如果 ExactSpelling 设置为 true,则只有当 DLL 中存在“MyMethod”时才返回“MyMethod”。

如果 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 两种版本。

以上内容采取直译,大意就是那样

这里我们一般不设置,即使用Auto即可。

各位Dalao有见解的话欢迎补充说明。

SetLastError(摘自MSDN)

SetLastError 错误处理非常重要,但我们在编程时经常会遗忘,或者直接偷懒而导致程序容错性差。

对于该函数,我们可以使用 GetLastError 来查找扩展的错误信息,则应该在外部方法的 DllImportAttribute 中将 SetLastError 属性设置为 true。

这会导致 CLR 在每次调用外部方法之后缓存由 API 函数设置的错误。

然后,在包装方法中,可以通过调用类库的 System.Runtime.InteropServices.Marshal 类型中定义的 Marshal.GetLastWin32Error方法来获取缓存的错误值。

我的建议是检查这些期望来自 API 函数的错误值,并为这些值引发一个可感知的异常。

对于其他所有失败情况(包括根本就没意料到的失败情况),则引发在 System.ComponentModel 命名空间中定义的 Win32Exception,并将 Marshal.GetLastWin32Error返回的值传递给它。

CallingConvention

该字段的值有以下几个:

  • CallingConvention.Cdecl : 调用方清理堆栈。它使您能够调用具有 varargs 的函数(如printf)。

  • CallingConvention.StdCall : 被调用方清理堆栈。它是从托管代码调用非托管函数的默认约定。

  • CallingConvention 字段的默认值为 Winapi,而后者又默认为 StdCall 约定。

这里不做详解,用到的地方不多,大多是时候默认。

这里给一个例子

[DllExport("about", CallingConvention = CallingConvention.Cdecl)]
public static void about()
{
}

这里我们需要自己实现该函数

ExactSpelling

ExactSpelling 指示是否应修改非托管 DLL 中的入口点的名称,以与 CharSet 字段中指定的 CharSet 值相对应。

如果为 true,则当 DllImportAttribute.CharSet 字段设置为 CharSet 的 Ansi 值时,向方法名称中追加字母 A,当 DllImportAttribute.CharSet 字段设置为 CharSet 的 Unicode 值时,向方法的名称中追加字母 W。

此字段的默认值是 false。

定义明确的情况下,不刻意使用该字段。

个人认为会把自己绕进去

结果

MarkDown真是太好使了!

C#中调用Dll动态链接库的更多相关文章

  1. c#下调用dll动态链接库[转]

    C# 调用传统的 API 动态链接库,是.NET开发经常被讨论的问题. 比如有这么一个动态链接库(delphi 语言): library DelphiDLL; uses SysUtils, Class ...

  2. [转]如何在Java中调用DLL方法

    转载地址:http://developer.51cto.com/art/200906/129773.htm Java语言本身具有跨平台性,如果通过Java调用DLL的技术方便易用,使用Java开发前台 ...

  3. 在VS2012中采用C++中调用DLL中的函数 (4)

    这两天因为需要用到VS2012来生成一个DLL代码,但是之前并没有用过DLL相关的内容,从昨天开始尝试调试DLL的文件调用,起初笔者在网络上找到了3片采用VSXXX版本进行调试的例子,相关的内容见本人 ...

  4. 在C++中调用DLL中的函数

    如何在C++中调用DLL中的函数 应用程序使用DLL可以采用两种方式:一种是隐式链接,另一种是显式链接.在使用DLL之前首先要知道DLL中函数的结构信息.Visual C++6.0在VC\bin目录下 ...

  5. java中调用dll文件的两种方法

    一中是用JNA方法,另外是用JNative方法,两种都是转载来的, JNA地址:http://blog.csdn.net/shendl/article/details/3589676   JNativ ...

  6. Unity中调用DLL库

    DLL -- Dynamic Link Library(动态链接库文件),这里以Window平台为例. Unity支持的两种语言生成的DLL库(C++.C#),这里以C#为例,C++网上可以搜索很详细 ...

  7. 【原创】在VS2012中采用C++中调用DLL中的函数(4)

    这两天因为需要用到VS2012来生成一个DLL代码,但是之前并没有用过DLL相关的内容,从昨天开始尝试调试DLL的文件调用,起初笔者在网络上找到了3片采用VSXXX版本进行调试的例子,相关的内容见本人 ...

  8. 在VS2012中采用C++中调用DLL中的函数(4)

    转自:http://www.cnblogs.com/woshitianma/p/3683495.html 这两天因为需要用到VS2012来生成一个DLL代码,但是之前并没有用过DLL相关的内容,从昨天 ...

  9. 在C++中调用DLL中的函数 (3)

    1.dll的优点 代码复用是提高软件开发效率的重要途径.一般而言,只要某部分代码具有通用性,就可将它构造成相对独立的功能模块并在之后的项目中重复使用.比较常见的例子是各种应用程序框架,ATL.MFC等 ...

随机推荐

  1. 剑指 Offer——和为 S 的两个数字

    1. 题目 2. 解答 由于数组是已经排好序的,我们可以定义两个指针,第一个指针指向第一个元素,第二个指针指向最后一个元素,然后求出这两个元素的和,与目标和进行比较.若小于目标和,第一个指针向前移动: ...

  2. 使用 Mesos 管理虚拟机

    摘要 为了满足渲染.基因测序等计算密集型服务的需求,UCloud 推出了“计算工厂”产品,让用户可以快速创建大量的计算资源(虚拟机).该产品的背后,是一套基于 Mesos 的计算资源管理系统.本文简要 ...

  3. 团队介绍&学长采访

    1. 团队介绍 刘畅 博客园ID:森高Slontia 身份:PM 个人介绍: 弹丸粉 || 小说创作爱好者 || 撸猫狂魔 我绝对不知道,我一个写代码的怎么就当PM去了? 张安澜 博客园ID:Mins ...

  4. nginx upstream 名称下划线问题

    原始配置: user  nobody;worker_processes  1; #pid        logs/nginx.pid; worker_connections  1024;} http ...

  5. TCP的三次握手(建立连接)和四次挥手(关闭连接)(转)

    转自:(http://www.cnblogs.com/Jessy/p/3535612.html) 参照: http://course.ccniit.com/CSTD/Linux/reference/f ...

  6. ES6的新特性(13)——Symbol

    Symbol 概述 ES5 的对象属性名都是字符串,这容易造成属性名的冲突.比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin 模式),新方法的名字就有可能与现有方法产生冲突. ...

  7. PHPDoc 学习记录

    https://zh.wikipedia.org/wiki/PHPDoc PHPDoc 是一个 PHP 版的 Javadoc.它是一种注释 PHP 代码的正式标准.它支持通过类似 phpDocumen ...

  8. 互评Alpha版本——可以低头,但没必要——取件帮

    基于NABCD评论作品,及改进建议: 1.根据(不限于)NABCD评论作品的选题 (1)N(Need,需求) 随着电商平台的发展,越来越多的人选择网购,但是东师的一部分快递网点不在校内,需要走很长的一 ...

  9. Kotlin 学习笔记(一)

    (Kotlin 学习笔记的文档结构基本按照 Java 核心技术一书的目录排列) 基本程序设计结构 数据类型 数字 类型 宽度 Double 64 Float 32 Long 64 Int 32 Sho ...

  10. 第四周 实验一 Java开发环境的熟悉 报告

    Java开发环境的熟悉 实验内容 1.IDEA的安装过程 2.使用IDEA代替虚拟机运行.编译.调试Java程序 实验要求 1.没有Linux基础的同学建议先学习<Linux基础入门(新版)&g ...