Windows下动态链接库的创建和使用(笔记)
Dec 25, 2013
在学习过程中,一次次的感觉到dll文件的重要性,所以乘着这两天有时间,又把孙鑫老师的视频回顾了一遍,很经典,很透彻,在此做下笔记,以供后续温故。
IDE:Visual C++ 6.0
动态链接库的创建 #
下面的方法是层层递进的关系,下一个方法是在上一个的基础上做改善。
方法一:单个*.cpp文件 #
- 新建dll工程
- 添加C/C++源文件(c++)
- 在源文件中,编写函数(每个函数结尾不需要’;’)(如下图)
- 在需要导出的函数前加 _declspec(dllexport)
- 编译连接即可,获得dll和lib文件。
这是最基础的方法,但是不利于后续动态链接库的使用,因为使用者不知道该动态库中都包含了些什么变量\结构\函数。所以最好能通过一个文件给使用者提示。这就是下面的方法二解决的。
方法二:添加一个*.h头文件 #
此时用户可用通过.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),供输入使用。
就是这么巧妙
除了函数,我们通常还要导出类,方法四就讲解了如何导出类。
方法四:导出类 #
是不是很简单,只要在.h中声明类(当然要加上DLL1_API说明的),并在.cpp中实现类函数就可以了。如果不想导出整个类,只想导出类的一部分,比如类中的一个函数怎么办?方法五就是为此而来。
方法五:导出类的部分域和方法 #
在需要导出的函数前加上DLL_API说明即可。是不是又是很简单!一切似乎妥当了,就等着使用动态链接库了。错!为什么错?方法六会解释这一切!
方法六:防止名字改编 #
使用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)
的结合。如下图。
结果如下:
终于add可以叫add了。问题解决。但是使用 extern “C”
也是存在不足的。
Extern “C”的不足:
- (1)只能导出全局函数,不能导出类和类的成员函数 以下两种都出错。
- (2) 调用约定发生变化时,名字改编就变化。 如将默认的C调用,改为标准调用_stdcall(即Pascal调用)
即 *.h文件中修改如下
*.cpp文件中修改如下
Dumpbin 结果如下,发生名字改编。
这个问题究竟还有没有办法解决了?不就是导出个函数么,怎么这么多问题!
为了彻底消除名字改编问题,使用模块定义文件*.def。这就是方法七要隆重推出的。
方法七:使用*.def文件 #
只要在工程中加入一个*.def文件,即可。def文件的写法如下图所示,第一行可以省略,下面的EXPORTS用来声明要导出的函数名。
工程中其它部分不变,该*.def只是在生成动态链接库时使用,其它地方都不用。
Dumpbin结果如下:
没有发生名字改编,问题解决。这就是目前创建dll的终极方法。
动态链接库的使用 #
动态链接库的使用可分为隐式连接和动态加载两种。隐式链接需要提供lib和dll,动态加载只要一个dll即可。
隐式链接 #
- 连接时,只需要提供引入库文件(*.lib),lib只在编译时起作用,运行时不用。
- 运行时,需要提供动态链接库文件(*.dll)
方法一 #
- 将lib文件复制到当前工程,在“工程–>设置–>连接”中,添加*.lib文件名
- 程序中使用dll中函数前,用 extern 函数声明
(以上两步,就可以保证使用dll文件中的函数时,编译连接不出错)
- 将dll文件复制到当前工程,运行程序即可!
方法二 #
基本同方法一,只是第二步改为:
不使用extern
使用_declspec(dllimport) (效率更高)
方法三 #
基本同方法一和方法二,改动在于通过包含 *.h头文件,就可以省略了extern 、_declspec(dllimport)等声明了,直接使用。
动态加载 #
方法四 #
将*.dll拷贝到当前工程目录下,即可。
使用FreeLibrary释放dll文件
也可以使用MAKEINTRESOURCE宏
利用Dumpbin命令查看应用程序的输入:
可见,testDll中没有加载Dll2.dll文件,这个文件是在使用时动态加载的。
总结 #
创建动态链接库最常用的方法是:添加一个.h文件提供给使用者、添加一个def文件防止名字改编。
使用动态链接库最常用的方法是:包含.h文件,添加.lib文件,直接使用dll中的函数。
工具 #
Dumpbin 命令 #
Dumpbin –exports *.dll
Dumpbin –imports *.exe
如果没有设置环境变量,先运行 VCVARS32.BAT 文件。
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专栏。有改动。