之前的DLL1项目动态链接库的创建只有一个cpp源文件,里面包含函数的声明和实现,这样的方式生成lib和dll后,再添加到其他项目时,还需手动的在项目添加导出函数声明.
还好我们的DLL1项目只有两个导出函数,若要有几十上百个,查找改编前的函数名,再手动添加导入声明,属实麻烦.
这里优化的方式就是为我们的DLL1项目添加一个Dll1.h头文件, 在开发DLL时,同步的更新Dll1.h中的声明,这样别人使用我们的Dll1.dll时,除了lib和dll文件外,再在项目中包含dll1.h头文件即可,而无需另外声明.
注意: 后续示例均已隐式导入为例,因前面动态链接库的使用已详细说明的如何导入,因此后续更新dll时步骤不再详细介绍.
补充: _declspec 和 __declspec 的区别
即单下划线和双下划线的区别. 通过MSDN可以得知,双下划线的__declspec才是标准的C++关键字,当然在VS中_declspec也没有问题,两个功能一致,但后续使用上尽量还是使用双下划线的版本:
通常在编写动态链接库时,都会提供一个头文件,在此文件中提供DLL导出函数原型的声明以及函数的有关注释文档.
这里我们为DLL1项目添加一个头文件Dll1.h:
因为Dll1.h头文件是对外的,即提供给其他项目包含的,因此这里是**__declspec(dllimport)**而不是__declspec(dllexport).
在DLL_test中添加Dll1.h头文件,包含代替此前的手动声明即可:
这里的Dll1.h头文件仅对外使用,可以直接在DLL1项目内部使用吗?即头文件声明,源文件实现:
重新生成,会看到如下提示:
因为这是一个DLL项目,Dll1.cpp中的实现要么是不导出函数,要么是导出函数,不能是导入函数,所以这里会这样提示,但实现生成的dll中还是会有这两个导出函数:
相当于忽略了Dll1.h中的声明了.
可以发现这样声明的Dll1.h头文件只能对外使用,而不能对内使用,主要原因就是对外时必须是__declspec(dllimport),对内时必须是__declspec(dllexport).
这里可以使用宏定义来解决这个问题,区分包含Dll1.h头文件的项目是生成DLL的项目还是使用DLL的项目.
修改Dll1.h头文件如下:
头文件中使用自定义的宏DLL1_API 来代替标识符__declspec(dllimport).
在头文件中,我们首先使用条件编译判断是否定义了DLL1_API 符号,如果已经定义了该符号,那么就不做任何处理;否则定义该符号为_declspec(dllimport) ,告诉编译器该函数是从动态库中引入的.
同时修改Dll1.cpp源文件如下:
在动态库的源程序Dll1.cpp中,首先利用#define 定义DLL1_API宏,然后包含上面头文件. 之后在源文件中定义函数时就不用_declspec(dllexport) 标识符了.
因为在该DLL程序编译时,头文件不参与编译,源文件单独编译.
因此在编译Dll1.cpp时,首先定义DLL1_API 宏,将其定义成:__declspec(dllexport).
然后再包含Dll1.h,这时将展开该头文件,判断DLL1_API宏是否已经定义,此时DLL1_API 已定义为__declspec(dllexport),所以直接编译其后的两个函数的声明.
因为在头文件的两个函数的声明中使用了DLL1_API 宏(__declspec(dllexport)),所以这里相当于__declspec(dllexport) int add(int a, int b); 即表明这两个函数是动态链接库的导出函数.
之后将这个DLL交给其他程序使用,后者有引用Dll1.h 头文件,展开后DLL1_API宏没有定义,那么就将该宏定义成__declspec(dllimport), 告诉编译器这两个函数是从DLL动态库中引入的,即add和subtract函数是导入函数.
重新生成dll,更新到DLL_test 中,隐式加载所需文件:Dll1.lib,Dll1.dll以及Dll1.h
重新编译运行如下:
这里的技巧是将DLL1_API宏定义在cpp文件中,在包含头文件:
而不是这样:
首先这里宏重定义了,会取最后一次定义为准。
这里对内使用时不会有差异,关键是对外使用时会有问题,例在DLL_test中再次定义宏:
在DLL_test项目中的Dll1.h头文件中的类型为__declspec(dllexport)了, 发现依旧可以正常调用:
这是因为虽然当前Dll1.h声明为__declspec(dllexport), 但Dll1.dll中的函数已经是__declspec(dllexport)的了,所以这里的声明只是起到了提示作用,并不影响实际已经生成的导出函数,所以才能正常调用.
虽然不会影响使用,但就可读性来说还是会对后续维护人员有一定影响,所以后续还是使用之前的定义方式较为稳妥,即:
对于DLL1_API宏的定义,也可在项目属性中配置,来代替Dll1.cpp源文件中的定义,以避免忘记定义的问题.
右键Dll1项目 –》C/C++ -》预处理器 –》编辑,添加DLL1_API,如下:
这里只是定义而没有值,所以并不适用上面的第一种方式。
为此前的Dll1项目添加的说明性质的Dll1.h头文件, 对内对外使用.
注意 对DLL1_API宏 定义的位置, 可能会影响代码的可读性.
扩展介绍了在项目属性的预处理器项中定义宏的方式, 该方式只声明, 而不提供实际值的定义.
最后附上本文涉及代码:
//DLL1项目
//Dll1.h
#pragma once
#ifdef DLL1_API
#else
#define DLL1_API __declspec(dllimport)
#endif
int DLL1_API add(int a, int b);
int DLL1_API subtract(int a, int b);
//Dll1.cpp
#define DLL1_API __declspec(dllexport)
#include "Dll1.h"
int add(int a, int b)
{
return a + b;
}
int subtract(int a, int b)
{
return a - b;
}
//DLL_test项目
//main.cpp
#include
using namespace std;
#include "Dll1.h"
int main()
{
cout << "累加函数测试: " << add(5, 3) << endl;
cout << "减法函数测试: " << subtract(5, 3) << endl;
}