受欢迎的博客标签

C++保留了一部分过程式语言的特点,因而它可以定义不属于任何类的全局变量和函数。但是,C++毕竟是一种面向对象的程序设计语言,为了支持函数的重载,C++对全局函数的处理方式与C有明显的不同。
extern "C"的主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。

    extern "C"

     你用C 开发了一个DLL 库,为了能够让C ++语言也能够调用你的DLL输出(Export)的函数,你需要用extern "C"来强制编译器不要修改你的函数名。

假设某个函数的原型为:

void foo( int x, int y );


该函数被C编译器编译后在符号库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。

.def文件的作用
       前面提到,不同厂商开发的两个C编译器也会有一些差异,最突出的就是microsoft的C编译器,它对函数名字的处理很特别(究竟是什么样子,可以使用Dumpbin工具查看dll的导出函数),所以要在使用他方编写的库时,程序链接能成功,有两种方法:1使用库编写者使用的C编译器(这里指VC++),显然这种方法不合理;2库的编写者在使用VC++编写库时使用.def文件。
       .def文件的作用即是,告知编译器不要以microsoft编译器的方式处理函数名,而以指定的某方式编译导出函数(比如有函数func,让编译器处理后函数名仍为func)。这样,就可以避免由于microsoft VC++编译器的独特处理方式而引起的链接错误

2.导出函数

2.1 使用__declspec(dllexport)和使用.def文件的区别

 

使用__declspec(dllexport)和使用.def文件是有区别的。如果 DLL是提供给VC++用户使用的,你只需要把编译DLL时产生的.lib提供给用户,它可以很轻松地调用你的DLL。但是如果你的DLL是供其他程序如 VB、delphi,以及.NET 用户使用的,那么会产生一个小麻烦。因为VC++对于 __declspec(dllexport)声明的函数会进行名称转换,如下面的函数: __declspec(dllexport) int __stdcall IsWinNT() 会转换为IsWinNT@0,这样你在VB中必须这样声明: Declare Function IsWinNT Lib "my.dll" Alias "IsWinNT@0" () As Long @的后面的数由于参数类型不同而可能不同。这显然不太方便。所以如果要想避免这种转换,就要使用.def文件方式。 EXPORTS后面的数可以不给,系统会自动分配一个数。对于VB、PB、 Delphi用户,通常使用按名称进行调用的方式,这个数关系不大,但是对于使用.lib链接的VC程序来说,不是按名称进行调用,而是按照这个数进行调用的,所以最好给出。

2.2 VS2019环境中,查看Dll中的导出函数的方法

step 1:进入PowerShell命令窗口

进入VS开发环境,然后Tools -> Visual studio  Command Prompt

step 2:用dumpbin查看导出函数

dumpbin /exports a.dll

重定向输出至b.txt文本文件,则命令格式如下:

d:\dumpbin /exports a.dll /out:b.txt

举例:

点击菜单栏中生成-生成解决方案,打开项目目录生成dll所在的文件夹,使用dumpbin.exe查看下dll中函数结构,如下图:

dumpbin.exe

可以看到有四个函数,但是显示的就像乱码,顶多只能看出每个函数叫啥,这样对于使用的用户来说是不友好的,因为看一堆乱码是无法知道这些函数是干啥的,有哪些参数和返回值,这样是无法使用的,我们可以在菜单栏项目-添加新项-Visaul C++-代码选择模块定义文件(.def),设置文件名,在添加的def文件中给出如下代码:

LIBRARY
EXPORTS
double_add_double_double=?add@Funcs@@YANNN@Z
double_maxValue_double_double=?maxValue@Funcs@@YANNN@Z
double_minValue_double_double=?minValue@Funcs@@YANNN@Z
double_subtract_double_double=?subtract@Funcs@@YANNN@Z

其中后面的乱码对应之前dump出来的dll函数名乱码,写这个def文件相当于把乱码重新定义名字,让它更好识别,这里我定义的规则是返回类型_函数名_参数列表类型,当然你可以定义自己的规则,这样使用者一看就知道这个函数干啥的,有啥参数和返回值,再dump一下dll看看:

3、调用约定

动态链接库的使用有两种方式,一种是显式调用。一种是隐式调用。

3.1 隐式的加载时链接,有三种方法

    1  LIB文件直接加入到工程文件列表中

  在VC中打开File View一页,选中工程名,单击鼠标右键,然后选中"Add Files to Project"菜单,在弹出的文件对话框中选中要加入DLL的LIB文件。然后在首先要使用该函数的地方加上该LIB的头文件,如#include "..\lib.h"即可(没有头文件当然就不用了)。

  2  设置工程的 Project Settings来加载DLL的LIB文件

  打开工程的 Project Settings菜单,选中Link,然后在Object/library modules下的文本框中输入DLL的LIB文件,如you.lib(或者lib文件的路径,包括文件名)。然后在首先要使用该函数的地方加上该LIB的头文件,如#include "..\lib.h"即可(没有头文件当然就不用了)。

  3  通过程序代码的方式

  加入预编译指令#pragma comment (lib,"*.lib"),这种方法优点是可以利用条件预编译指令链接不同版本的LIB文件。因为,在Debug方式下,产生的LIB文件是Debug版本,如Regd.lib;在Release方式下,产生的LIB文件是Release版本,如Regr.lib。然后在首先要使用该函数的地方加上该LIB的头文件,如#include "..\lib.h"即可(没有头文件当然就不用了)。

当应用程序对DLL的LIB文件加载后,还需要把DLL对应的头文件(*.h)包含到其中,在这个头文件中给出了DLL中定义的函数原型,然后声明

3.2  显式的运行时链接 

     隐式链接虽然实现较简单,但除了必须的*.dll文件外还需要DLL的*.h文件和*.lib文件,在那些只提供*.dll文件的场合就无法使用,而只能采用显式链接的方式。这种方式通过调用API函数来完成对DLL的加载与卸载,能更加有效地使用内存,在编写大型应用程序时往往采用此方式。

     这种方法编程具体实现步骤如下:

    ①使用Windows API函数Load Library或者MFC提供的AfxLoadLibrary将DLL模块映像到进程的内存空间,对DLL模块进行动态加载。

    ②使用GetProcAddress函数得到要调用DLL中的函数的指针。

    ③不用DLL时,用Free Library函数或者AfxFreeLibrary函数从进程的地址空间显式卸载DLL。

Create a simple managed app to call the DLL