没接触dll之前觉得它很神秘,就像是一个黑盒子,既不能直接运行,也不能接收消息。它们是一些独立的文件,其中包含能被可执行程序或其他dll调用来完成某项工作的函数,只有在其他模块调用dll中的函数时,dll才发挥作用。
在实际编程中,我们可以把完成某项功能的函数放在一个动态链接库里,然后提供给其他程序调用。像Windows API中所有的函数都包含在dll中,如Kernel32.dll, User32.dll, GDI32.dll等。那么dll究竟有什么好处呢?
dll的创建主要有两种方法:一是使用 __declspec(dllexport) 创建dll,二是使用模块定义(.def)文件创建dll。
首先在VS中的Visual C++中创建一个Win32 Project,取名为Dll1。在Application Type中选择DLL,在Additional options中选择Empty project,即创建一个空的动态链接库工程。
然后为工程添加一个C++源文件:Dll1.cpp,假设我要实现的是加法和减法运算,则代码如下:
__declspec(dllexport) int add(int a, int b){
return a + b;
}
__declspec(dllexport) int subtract(int a, int b){
return a - b;
}
为了让dll导出函数,需要在每一个需要被导出的函数前面加上标识符:__declspec(dllexport)。
利用Build命令生成Dll1动态链接库,这时在Dll1/Debug目录下就会生成.dll文件和.lib文件,这两个文件即为所需的动态链接库的文件。
既然已经有了这个dll文件,是不是就可以在其他程序中访问该dll中的add和subtract函数了呢?必须注意的一点是:应用程序如果想要访问某个dll中的函数,那么这个函数必须是已经被导出的函数。
为了查看一个dll中有哪些导出函数,Visual Studio提供了一个命令行工具:Dumpbin。
首先在命令行中进入到VS的安装目录下,运行一个名为VCVARS32.bat的批处理程序(对于VS2013来说,该bat文件位于\VC\bin目录下),该文件的作用是用来创建VC++使用的环境信息。(注意,当在命令行界面执行VCVARS32.bat文件后,该文件设置的环境信息只在当前命令行窗口生效。)
然后输入dumpbin命令,即可列出该命令的使用方法:
那么想要查看一个dll提供的导出函数,在Dll1.dll文件所在目录下,在命令行中输入下述命令:
dumpbin -exports Dll1.dll
在上图中可以看到我们导出了两个函数,但是导出函数的名称长得很奇怪,add导出函数的名称是“?add@@YAHHH@Z”,subtract导出函数的名称是“?subtrct@@YAHHH@Z”。这是因为在编译链接时,C++会按照自己的规则篡改函数的名称,这一过程称为“名字改编”。这会导致不同的编译器、不同的语言下调用dll发生问题。因此我们希望动态链接库文件在编译时,导出函数的名称不要发生变化。
为了实现这一目的,可以再定义导出函数时加上限定符:extern “C”,如
extern "C" __declspec(dllexport) int add(int a, int b){
//...
}
但是这种方式只能解决C++和C语言之间相互调用时函数命名的问题。为了彻底解决这个问题,可以通过模块定义(.def)文件实现。
使用def文件创建dll的话就不再需要__declspec(dllexport),因此将代码写成最原始的样子:
int add(int a, int b){
return a + b;
}
int subtract(int a, int b){
return a - b;
}
同时为工程创建一个后缀名为.def的文件,并添加进工程,编辑其内容为:
LIBRARY Dll1
EXPORTS
add
subtract
其中LIBRARY语句用于指定动态链接库的名称,该名称与生成的动态链接库名称一定要匹配。EXPORTS语句用于表明dll将要导出的函数,以及为这些导出函数指定的符号名。
将该模块定义文件链接到工程中,方法为工程属性页面>链接器>输入>模块定义文件中写入“Dll1.def”。
然后重新Build Solution,并用dumpbin工具查看现在dll导出的函数,可以发现函数的名字改编问题得到了解决!
以上就是创建dll的两种方法,个人比较提倡使用模块定义(.def)文件创建dll,代码简洁的同时还没有名字改编的问题。
接下来我们来看看如何使用创建好的dll。
dll的使用也有两种方法,一是隐式链接的方式加载dll,二是显示加载方式加载dll。
为了更好地展示dll的使用,我首先创建了一个基于MFC的对话框程序,然后为其添加两个按钮。
将生成好的Dll1.dll和Dll1.lib复制到对话框程序所在的文件夹,然后在CXXXDlg.h中注册动态链接库的引入库文件。因为.lib文件包含了Dll1.dll中导出函数的符号名,相当于告诉对话框程序相关函数应该去dll中调用。
#pragma comment(lib,"Dll1.lib")
然后在CXXXDlg.cpp中声明外部函数:
_declspec(dllimport) int add(int a, int b);
_declspec(dllimport) int subtract(int a, int b);
这样我们就可以使用这两个函数了。为两个按钮添加事件响应程序,并添加如下代码:
void CXXXDlg::OnBtnAdd()
{
// TODO: Add your control notification handler code here
CString str;
str.Format(_T("5 + 3 = %d"), add(5, 3));
MessageBox(str);
}
运行程序发现可以通过dll的导出函数来实现加法功能了。说明dll可以使用。
另一种是通过LoadLiabrary函数显示加载dll。代码如下。需要注意的是这时候我们不再需要注册.lib文件,也不需要声明外部函数。只要在需要使用的地方调用dll文件即可。
void CXXXDlg::OnBtnSubtract()
{
// TODO: Add your control notification handler code here
HINSTANCE hInst;
hInst = LoadLibrary(L"Dll1.dll");
typedef int(*SUBPROC)(int a, int b);
SUBPROC Sub = (SUBPROC)GetProcAddress(hInst, "subtract");
CString str;
str.Format(_T("5-3=%d"), Sub(5, 3));
FreeLibrary(hInst); //LoadLibrary后要记得FreeLibrary
MessageBox(str);
}
通过以上的例子,可以看到隐式链接和动态加载两种加载dll的方式各有优点。
《VC++深入详解》孙鑫