动态链接库技术
动态链接库与静态库的区别是:静态库的代码在主程序代码连接时,一起进入到主程序中,成为主程序不可分割的一部分,在程序运行时由程序加载器(Loader)进行加载;动态链接库的代码,则是单独编译的,与主程序一样以独立的文件形式给出,在主程序运行时由主程序进行加载。
由于是独立的文件,因此动态链接库文件与主程序文件类似,也具有固定名称的入口函数。但动态链接库的这个入口函数,仅作为库文件使用信息的管理,而与操作系统或主程序进行交互,只处理库加载与库卸载消息(根据应用场景,具体分为DLL_PROCESS_ATTACH,DLL_PROCESS_DETACH,DLL_THREAD_ATTACH,DLL_THREAD_DETACH)。
想要真正地学好编程,就不得不了解程序的存储结构,在Windows系统中,所有的可执行文件都遵循PE形式的文件结构。该结构规定了所有可执行文件在内外存间的映射方式。动态链接库技术的实现,依赖于PE文件结构。(关于PE文件结构,可参见adam001521的博文《PE文件结构详解》,博文地址是:https://blog.csdn.net/adam001521/article/details/84658708)
在PE可执行文件头的结尾处,有一个大数组,里面记录了编译器在生成PE文件时所产生的各函数的地址及长度,也就是所谓的导出表和导入表等,这些内容结合可执行文件加载时的内存块起始地址,可以准确地计算出各个函数的内存地址。其中,导出表是提供给其他程序使用的本文件中定义的函数,导入表记录的是本程序运行里需要导入的其他文件的函数(也就是外部函数)。
在动态链接库文件编制时,可以在声明时通过关键字__declspec(dllexport)和__declspec(dllimport)指明哪些函数将被记录在PE结构的导出表与导入表中。
在主程序中使用动态链接库时,首先需要把动态链接库加载到内存中,可以使用LoadLibrary(“动态链接库文件”)来加载,该函数返回动态链接库加载后的实例句柄(也就是加载到内存后的内存块起始地址等)。然后通过函数GetProcAddress(动态链接库句柄,“函数名”),可以得到所要调用的具体函数的内存地址等信息。实际上GetProcAddress是动态链接库句柄和PE结构的导入表(导出表)中的函数名所对应的地址,通过计算得到的函数加载后真实的内存地址的。
不过,仅仅这样还无法正确的使用动态链接库中的函数。想要正确的使用动态链接中的函数,还必须知道该函数的声明形式,并以同样的形式声明一个函数指针后,并把得到的函数信息按该形式转换,并赋予该函数指针后,方可使用该函数指针进行对应的函数调用。因为正确的函数调用,所需要的不仅仅是函数代码的入口地址,还需要函数返回时的堆栈信息。
不同的程序设计语言,对堆栈的使用具有不同的规则。由于动态链接库文件是独立于主程序的外部文件,在编制时所采用的语言可能与主程序存在差异,因此,在主程序中使用动态链接库时,尚应指明动态链接库文件编译时所使用的进出栈规则(也就是调用约定)。
DLL文件的编制
1.导出函数的声明
__declspec(dllexport) 类型说明 函数名(参数声明);
2.导入函数的声明
__declspec(dllimport) 类型说明 函数名(参数声明);
DLL库函数的调用
1.定义函数指针的类型,用于定义函数指针及强制转换得到的函数地址为包括返回堆栈的函数
typedef 返回值类型 (CALLBACK *PDLLFUN)(参数声明);
这里的参数声明必须与动态链接库中函数定义时所采用的参数声明一致
2.使用定义的指针类型声明函数指针,用于接收动态链接库函数
PDLLFUN pFun;/注意,不需要加星号,星号已含在类型定义中了/
3.声明DLL文件对象句柄,用于接收加载的DLL文件对象
HINSTANCE hDLL;/文件对象句柄/
4.加载DLL文件到内存
hDLL=LoadLibrary(“动态链接库文件”);
5.取得动态链接库函数
pFun=(PDLLFUN)GetProcAddress(hDLL,“动态链接库中的函数名”);
这里的pFun,PDLLFUN,hDLL是前面所定义的