动态库
动态链接库(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
//接口API
DLL_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文件参考VS2005中添加lib文件的方法文章。
在MultiThread 工程中,主要关键代码如下:
stdafx.h
#pragma once
#include "targetver.h"
#include <stdio.h>
#include <tchar.h>
//新增内容,添加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工具;