Windows下动态链接库的创建和使用(笔记)

Windows下动态链接库的创建和使用(笔记)

Dec 25, 2013
Coding
C++, Windows

在学习过程中,一次次的感觉到dll文件的重要性,所以乘着这两天有时间,又把孙鑫老师的视频回顾了一遍,很经典,很透彻,在此做下笔记,以供后续温故。

IDE:Visual C++ 6.0

动态链接库的创建 #

下面的方法是层层递进的关系,下一个方法是在上一个的基础上做改善。

方法一:单个*.cpp文件 #

  • 新建dll工程
  • 添加C/C++源文件(c++)
  • 在源文件中,编写函数(每个函数结尾不需要’;’)(如下图)
  • 在需要导出的函数前加 _declspec(dllexport)
  • 编译连接即可,获得dll和lib文件。

img.png

这是最基础的方法,但是不利于后续动态链接库的使用,因为使用者不知道该动态库中都包含了些什么变量\结构\函数。所以最好能通过一个文件给使用者提示。这就是下面的方法二解决的。

方法二:添加一个*.h头文件 #

img.png

此时用户可用通过.h文件知道dll文件提供的内容。使用者将.h文件包含进自己的Project后,就可以直接使用dll提供的函数。

我们发现cpp文件中每个函数前都使用了

_declspec(dllexport)

.h文件中每个函数前使用了

_declspec(dllimport)

这样写代码很累人且繁琐,容易出错(也许就把export写成了import,或者相反)。还有方法三就是为了解决这个问题。

方法三:添加一个*.h头文件,并使该头文件可以被dll文件使用,也可以被用户使用 #

将.h头文件包含进.cpp文件中(如下图中Dll1.cpp 和 Dll1.h),这样在编译.cpp文件时,就会展开该.h头文件,相当于给cpp文件中的函数做了原型声明,cpp中的函数前就可以不加_declspec(dllexport)说明了(注意比较下图中的Dll1.cpp中函数和方法一中的函数区别,没有了_declspecl(dllexport))。因为头文件展开时相当于做了声明,但是其使用的却是_declspecl(dllimport),必须要改成_declspecl(export),这个如何解决?

方法是:通过宏定义

在.cpp中定义宏

#define DLL1_API _declspecl(dllexport)

在.h中定义宏

#ifdef DLL1_API
#else
#define DLL1_API _declspecl(dllimport)
#endif

这样,当使用编译.cpp文件时,会展开.h文件,在.h发现已经定义了DLL1_API,就不会讲DLL1_API定义为_declspecl(dllimport),而是直接将DLL1_API替换为.cpp中已经定义的_declspecl(dllexport),供输出使用。

当客户直接用.h文件时,会将DLL1_API定义为_declspecl(dllimport),供输入使用。

就是这么巧妙

img.png img.png

除了函数,我们通常还要导出类,方法四就讲解了如何导出类。

方法四:导出类 #

img.png img.png

是不是很简单,只要在.h中声明类(当然要加上DLL1_API说明的),并在.cpp中实现类函数就可以了。如果不想导出整个类,只想导出类的一部分,比如类中的一个函数怎么办?方法五就是为此而来。

方法五:导出类的部分域和方法 #

img.png

在需要导出的函数前加上DLL_API说明即可。是不是又是很简单!一切似乎妥当了,就等着使用动态链接库了。错!为什么错?方法六会解释这一切!

方法六:防止名字改编 #

img.png

使用dumpbin工具(具体使用方法后文有说明)查看我们创建的dll中导出的函数时,由上图可见,函数名字发生改编了。也就是说,我们定义的add方法名字不加”add”了,而是叫

add@@YAHHH@Z

(这个名字与编译器相关,是有具体含义的)。这就使得使用dll文件时不能直接通过“add”函数名调用了,而是后面那个古怪的名字。这可如何是好?

这种现象叫做名字改编(或命名改编),这种改编和编译器相关,所以当同一个dll文件放到不同的编译器上时,就可能因为名字改编问题,使得函数不可以使用。解决的方法是

使用extern “C” 声明

在每个函数前加上 extern ”C” 声明。当然我们不会真的傻傻的这么做,在每个函数前在敲上 extern “C”。记得刚才那个DLL_API是怎么让我们偷懒的么?没错,直接修改宏即可。将DLL_API定义为 extern “C”_declspecl(dll**port)的结合。如下图。

img.png img.png

结果如下:

img.png

终于add可以叫add了。问题解决。但是使用 extern “C” 也是存在不足的。

Extern “C”的不足:

  • (1)只能导出全局函数,不能导出类和类的成员函数 以下两种都出错。

img.png img.png

  • (2) 调用约定发生变化时,名字改编就变化。 如将默认的C调用,改为标准调用_stdcall(即Pascal调用)

即 *.h文件中修改如下

img.png

*.cpp文件中修改如下

img.png

Dumpbin 结果如下,发生名字改编。

img.png

这个问题究竟还有没有办法解决了?不就是导出个函数么,怎么这么多问题!

为了彻底消除名字改编问题,使用模块定义文件*.def。这就是方法七要隆重推出的。

方法七:使用*.def文件 #

只要在工程中加入一个*.def文件,即可。def文件的写法如下图所示,第一行可以省略,下面的EXPORTS用来声明要导出的函数名。

工程中其它部分不变,该*.def只是在生成动态链接库时使用,其它地方都不用。

img.png

Dumpbin结果如下:

img.png

没有发生名字改编,问题解决。这就是目前创建dll的终极方法。

动态链接库的使用 #

动态链接库的使用可分为隐式连接和动态加载两种。隐式链接需要提供lib和dll,动态加载只要一个dll即可。

隐式链接 #

  • 连接时,只需要提供引入库文件(*.lib),lib只在编译时起作用,运行时不用。
  • 运行时,需要提供动态链接库文件(*.dll)

方法一 #

  1. 将lib文件复制到当前工程,在“工程–>设置–>连接”中,添加*.lib文件名
  2. 程序中使用dll中函数前,用 extern 函数声明

(以上两步,就可以保证使用dll文件中的函数时,编译连接不出错)

  1. 将dll文件复制到当前工程,运行程序即可!

方法二 #

基本同方法一,只是第二步改为:

不使用extern

使用_declspec(dllimport) (效率更高)

img.png

方法三 #

基本同方法一和方法二,改动在于通过包含 *.h头文件,就可以省略了extern 、_declspec(dllimport)等声明了,直接使用。

img.png

动态加载 #

方法四 #

将*.dll拷贝到当前工程目录下,即可。

img.png

使用FreeLibrary释放dll文件

也可以使用MAKEINTRESOURCE宏

img.png

利用Dumpbin命令查看应用程序的输入:

img.png

可见,testDll中没有加载Dll2.dll文件,这个文件是在使用时动态加载的。

总结 #

创建动态链接库最常用的方法是:添加一个.h文件提供给使用者、添加一个def文件防止名字改编。

使用动态链接库最常用的方法是:包含.h文件,添加.lib文件,直接使用dll中的函数。

工具 #

Dumpbin 命令 #

Dumpbin –exports *.dll
Dumpbin –imports *.exe

如果没有设置环境变量,先运行 VCVARS32.BAT 文件。

img.png

Depends可视化工具 #

查看一个可执行模块(dll或exe文件)依赖的dll文件。

工具路径:

开始 -> 程序 -> Microsoft Visual Studio 6.0 -> Microsoft Visual C++ 6.0 Tools -> Depends

如果没有找到,可以到安装目录 “C:/Program Files/Microsoft Visual Studio/Common/Tools” 里面找。


该文2011-06-21 16:32首发于我的CSDN专栏。有改动。