1. 动态链接库概述 (静态库和动态库、动态链接库的加载)
概述: Windows API中的所有函数都包含在DLL中.其中有三个最重要的DLL:
Kernel32.dll,它包含用于管理内存、进程和线程的各个函数;
Uer32.dll,它包含用于执行用户界面任务(如窗口的创建和消息的传送)的各个函数;
GDI32.dll,它包含用于画图和显示文本的各个函数.
2.静态库和动态库
静态库:函数和数据被编译进一个二进制文件(通常扩展名为.LIB).在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把它们和应用程序的其他模块组合起来创建最终的可执行文件(.EXE 文件),发布产品的时候不需要包含静态库。
动态库:在使用动态库的时候,往往提供两个文件:一个引入库.lib和一个DLL。引入库包含被DLL 导出的函数和变量的符号名,DLL 包含实际的函数和数据。
3.动态链接库的加载
隐式加载(需要.lib文件,包含头文件。 同时还需要DLL文件,程序启动的时候就把动态库的函数全部加载到内存中了)
显示加载(需要DLL文件,需要时才加载,利用LoadLibrary,GetProcAddress函数实现)
4.通用动态链接库模板
这种模式的.h文件,可以适用于导入函数,也可以适用于导出函数
在源文件中包含.h,如果源文件中定义了DLL_API ”_declspec(dllexport)“ 那么在.h中就不定义了,可以用于导出函数的声明,而如果在调用dll的程序中包含这个头就可以,因为程序中没有定义DLL_API,所以DLL_API被定义为 "_declspec(dllimport)" 这时作为导入函数的声明了,
Dll.h文件内容:
#ifdef DLL_API
#else
#define DLL_API _declspec(dllimport)
#endif
DLL_API int Add(int x,int y);
DLL_API int Subtract(int x,int y);
cpp文件内容:
#define DLL_API _declspec(dllexport)
#include "Dll.h"
int add(int x,int y)
{
return x+y;
}
int subtract(int x,int y)
{
return x-y;
}
5.查看动态链接库工具
Dumpbin (字符界面工具)
dumpbin –imports ***.exe //查看导入函数
dumpbin -exports ***.dll //查看导出函数
Depends(图形界面工具)
6.从 DLL 中导出C++类
在一个动态链接库中不仅可以导出函数,还可以导出一个C++类。
代码示例如下:
class DLL_API point
{
public:
void output(int x,int y);
void test(int a,int b);
};
如果只想导出类中的部分函数,则可以将DLL_API剪切至该函数前面.如void DLL_API output(int x,int y);
如果函数在类中被声明为private,或protect,是无法导出使用的。
7.解决名字改编问题
C++导出或导入动态链接库的函数时会发生名字改编,如果不想发生名字改编,就需要在定义导出函数时,加上限定符:extern “C”。注意:双引号中的C 必须是大写。
#define DLL_API extern "C" _declspec(dllexport)
利用限定符:extern “C”可以解决C++和C 语言之间相互调用时函数命名的问题.但是这种方法有一个缺陷:不能用于导出一个类的成员函数,只能用户导出全局函数这种情况。另外,如果导出函数的调用约定发生了改变,那么即使使用了限定符,名字一样会发生改编。
这种情况下,可以通过一个称为模块定义文件(DEF)的方式来解决名字改编的问题。接下来,添加一个模块定义文件"**.def”,并写入如下代码:
LIBRARY DLL** //dll文件的名称
EXPORTS
add
subtract
其中,LIBRARY 语句用来指定动态链接库的内部名称,该名称与生成的动态链接库的名称一定要匹配。
EXPORTS的作用是表明DLL 将要导出的函数,以及为这些导出函数指定的符号名。
如果要将导出的符号名和源文件中定义的函数名不一样,则可以按照一下语法指定导出的函数:
entryname=internalname //前面代表符号名,后面代表函数名
其中, entryname项是导出的符号名,internalname项是DLL中将要导出的函数名字
8.显示加载方式加载DLL
使用动态方式加载动态链接库时,需要用到LoadLibrary函数。该函数的作用是将指定的可执行模块映射到调用进程的地址空间。原型如下:
HMODULE LoadLibrary( LPCTSTR lpFileName );
该函数返回类型是HMODULE(注:HMOUDULE类型和HINSTANCE 类型可以通用)。当获取到动态链接库模块的句柄后,接着需要获取该动态链接库中导出函数的地址,这可以通过调用GetProcAddress 函数实现,原型如下:
FARPORC GetProcAddress (HMODULE hModule, LPCSTR lpProcName);
下面,就利用DllTest程序动态加载Dll2.dll并访问它提供的导出函数。代码如下:
void CDllTestDlg::OnBtnAdd()
{
HINSTANCE hInst;
hInst=LoadLibrary("Dll3.dll");
typedef int (/*_stdcall*/ *ADDPROC)(int a,int b); //定义的必须是函数指针,用来接收地址
//ADDPROC Add=(ADDPROC)GetProcAddress(hInst,""); //函数名称,必须是dll导出函数的名称,
ADDPROC Add=(ADDPROC)GetProcAddress(hInst,MAKEINTRESOURCE(1));
//上一句表示通过序号来加载,要用MAKEINTRESOURCE来转换
if(!Add)
{
MessageBox("获取函数地址失败!");
return;
}
CString str;
str.Format("5+3=%d",Add(5,3));
MessageBox(str);
}
说明:
这里的ADDPROC 是一个函数指针类型,而不是一个变量。
动态加载DLL 时,客户端程序不再需要包含导出函数声明的头文件和引入库文件,只需要带dll文件即可。
9.DllMain 函数
Dll文件的入口函数:DllMain。该函数是可选的。原型如下:
BOOL WINAPI DllMain( HINSTANCE hinstDLL,DWORD fdwReason,LPVOID lpvReserved);
注意:
如果提供了DllMain 函数,那么在此函数中不要进行太复杂的调用,因为在加载该动态链接库时,可能还有一些核心动态链接库没有加载,比如user32.dll和GDI32.dll等,由于我们自己的dll会比较靠前的加载,而某些核心没被加载的话,就可能调用失败。
当我们的动态链接库不再使用时可以调用FreeLibrary 使动态链接库使用计数减1,当使用计数为零时,系统会收回模块资源。
原型如下:
BOOL FreeLibrary( HMODULE hModule);