动态库
动态链接库(Dynamic Link Library,DLL)是Windows操作系统中实现共享函数库概念的一种方式,这些库函数的扩展名是 ”.dll”、”.ocx”(包含ActiveX控制的库);
在使用动态库的时候,往往需要提供两个文件:一个引人库(.lib)和一个DLL文件(.dll);
引人库(.lib)
引入库是包含该dll导出的函数和变量的符号名,可以认为是函数和变量的声明;在编译和链接可执行文件时,需要引用引人库中的符号名;
DLL文件(.dll)
当可执行文件运行并且使用到DLL文件中的函数和变量时,系统才去加载相对应的DLL,并将DLL文件映射到进程的地址空间,然后去访问DLL文件中的导出函数;一句话,dll文件应用于程序运行时。
创建Win32 DLL文件
1.创建工程
在工程中右键添加新项目,项目类型:Win32,模板:Win32项目,输入Dll名字;在弹出的对话框中,选择应用程序类型为“DLL(D)选项”。如下图所示:
2.添加导出函数
应用程序如果想要访问某个dll中的函数,那么该函数必须是已经被导出的函数;设置方法如下:
需要在每一个被导出的函数前面增加标识符:_declspec(dllexport);
形如:_declspec(dllexport) int add(int a, int b);
按照指定格式添加其他的导出函数后,点击Build按钮就可以生成.lib文件和.dll文件;我将动态链接库的工程名命名为Sampledll,生成结果位于Ctest解决方案下的Debug目录:
隐式链接方式加载dll头
1.函数名字改编问题
由于C++语言中有函数重载特性,在实际目标文件中函数名会被编译器作特殊处理,比如目标文件的函数名会包含参数类型,而C语言则不会包含参数类型,比如:
int add(int a, int b);//代码函数声明_add //C语言的目标文件函数名_add_int_int //c++语言的目标文件函数名
为了C语言和C++都能调用dll文件中API函数,我们希望动态链接库文件在编译时,导出函数的名称不要发生变化,为了实现这个目的,在定义导出函数时,需要添加上限定符:extern “C”,C一定要大写。
利用限定符:extern “C”可以解决C++和C语言之间相互调用时函数命名的问题,但是该方法只能用于导出全局函数,不能用于导出一个类的成员函数。
另外,如果导出函数的调用约定方式发生了变化,即使添加了限定符extern “C”,函数名字在编译dll时还会发生变化。2.隐式加载dll方式
利用extern声明的外部函数,形如:
extern int _stdcall add(int a, int b);
利用_declspec(dllimport)声明的外部函数
_declspec(dllexport) int _stdcall add(int a, int b);
与extern关键字这种方式相比,使用_declspec(dllimport)标识符声明的外部函数时,它将告诉编译器该函数是从动态链接库中引入的,编译器可以生成运行效率更高的代码。因此,如果调用的函数来自于动态链接库,应该采用_declspec(dllimport)声明外部函数。
3.头文件设计
为了方便其他模块引用DLL文件中的API函数,我们在DLL工程中,添加Sampledll.h头文件;其他模块需要使用dll文件中的函数,则先添加Sampledll.h至本工程; 需要注意的,如果使用这种共享头文件的方式,一定要用_declspec(dllexport)声明的方式,否则没有.lib文件生成。
头文件内容如下:
#ifndef _SAMPLE_H_#define _SAMPLE_H_#ifdef DLL_API //内部调用#else #define DLL_API extern "C" _declspec(dllexport)//外部调用#endif//接口APIDLL_API int _stdcall add(int a, int b);DLL_API int _stdcall Multiply(int a,int b);#endif
4.使用动态链接库编程
我们创建一个MultiThread Win32控制台程序,并在该工程中使用dll文件中的API函数,主要关键步骤如下:
1. MultiThread工程中添加.lib文件; 2. 将.lib文件和dll文件放在MultiThread 工程目录下; 3. #include”Sampledll.h”添加.lib文件参考文章。
在MultiThread 工程中,主要关键代码如下:
stdafx.h
#pragma once#include "targetver.h"#include#include //新增内容,添加lib文件#pragma comment(lib, "..\\Debug\\SampleDll.lib")
MultiThread .cpp
#include "..\\SampleDll\\SampleDll.h"int _tmain(int argc, _TCHAR* argv[]){ cout << "add func: " << add(2,4) << endl; cout << "Multiply func: " << Multiply(2,4) << endl; }
运行结果:
显示加载方式加载dll
显示加载dll需要用到以下几个API函数,其函数声明如下:
//加载dll文件到进行地址空间HMODULE WINAPI LoadLibrary( __in LPCTSTR lpFileName);//获取导出函数的地址FARPROC WINAPI GetProcAddress( __in HMODULE hModule, __in LPCSTR lpProcName);//释放加载的dll文件BOOL WINAPI FreeLibrary( __in HMODULE hModule);//如果有DllMain函数,系统加载dll会调用该函数BOOL WINAPI DllMain( __in HINSTANCE hinstDLL, __in DWORD fdwReason, __in LPVOID lpvReserved);
需要注意的是,若dll中的导出函数采用的是标准调用约定时,则访问该dll的客户端程序也应该是采用标准调用约定的导出函数,两者需要一致。
导通加载dll时,客户端程序不再需要包含导出函数声明的头文件和引入库文件,需要的dll文件即可。
对于显示加载和隐式链接加载dll的区别如下:
- 隐式链接方式加载dll客户端调用简单、便捷;
- 在程序启动时,dll文件已经完成加载,并映射到进程的地址空间,可以随时的调用导出函数,没有调用的顺序要求;
- 若应用程序需要加载较多的dll文件,则更适用于显示加载dll方式,可以在需要用到某个dll的某个导出函数时,再加载dll,节约程序的启动时间。
最后,我们想要查看动态链接库导出信息的时,可以使用Dumpbin命令和Depends工具;