- 静态链接库与动态链接库:lib(静态链接库)与DLL(动态链接库)采用的都是共享代码的方式,当我们引用了lib,那么lib中的指令会被直接包含在最终生成的exe文件中,而我们若使用DLL,该DLL不必被包含在最终DLL文件中,exe文件执行时可以“动态”地引用和卸载这个DLL文件,并且lib中不可包含其他lib和DLL,而DLL中仍可包含其他的DLL和lib。
- 常用DLL文件:Kernel32.DLL:是windows中非常重要的32位动态链接库文件,属于内核级文件,它控制着系统的内存管理、数据的输入输出操作和中断处理以及进程调度等;user32.DLL中的函数主要控制用户界面;gdi32.DLL中的函数则负责图形方面的操作。
- DLL优点:
- 使用较少的资源:当多个程序使用同一个函数库时,DLL可以减少在磁盘和物理内存中加载的代码的重复量。这不仅可以大大影响在前台运行的程序,而且可以影响到其他在Windows操作系统上运行的程序。
- 推广模块式体系结构:DLL 有助于促进模块式程序的开发。这可以帮助您开发要求提供多个语言版本的大型程序或要求具有模块式体系结构的程序。
- 简化部署和安装:当 DLL 中的函数需要更新或修复时,部署和安装 DLL 不要求重新建立程序与该 DLL 的链接。此外,如果多个程序使用同一个 DLL,那么多个程序都将从该更新或修复中获益。当您使用定期更新或修复的第三方 DLL 时,此问题可能会更频繁地出现。
4.现代编译器的主要工作流程:源程序(source code)->预处理器(preprocessor)->编译器(compiler)->汇编程序(assembler)->目标程序(object)->连接器(链接器,linker)->可执行程序(executables)
与静态链接库的关系:在我们使用#pragma comment(lib,”****\\debug\\***.lib”)以后,本文件生成的obj文件会与libTest.lib一起连接,在vs里的附加依赖项里添加lib文件也是如此。
5.导出函数的声明:
- 在函数声明前加上__declspec(dllexport);
- 采用模块定义(.def)文件声明,.def文件为链接器提供了有关被链接程序的导出、属性及其他方面的信息。
6. .def:
这段代码演示了怎样用.def文件将函数声明为DLL导出函数(需在工程中添加lib.def文件)
;lib.def:导出DLL函数
;LIBRARY语句说明def文件相应的DLL
LIBRARY dllT
;后面列出要导出函数的名称
EXPORTS
;可以在def文件中的导出函数名后加@n,表示要导出函数的序号为n(在进行函数调用时,这个序号将发挥其作用)
add@1
.def文件中的注释由每个逐行开始处的分号(;)指定,且注释不能与语句共享一行
可以在命令行中通过dumpbin/exports *.DLL 命令查看*.DLL文件的导出符号
7.DLL的调用方式:
- 动态调用:
由“LoadLibray-GetProcAddress-FreeLibrary”这样的系统api提供的“DLL加载-DLL函数地址获取-DLL释放”方式被我们成为DLL的动态调用。
特点:可以完全由开发者决定何时加载和卸载DLL。
- 静态调用:
静态调用的进行需要完成两个步骤:告诉编译器与DLL对应的.lib文件所在的路径及文件名(#pragma comment(lib,”***.lib”));声明导入函数(__declspec(dllimport)***(***))。
特点:由编译系统完成对DLL的加载和应用程序结束时DLL的卸载。当调用某DLL的应用程序结束时,若系统中还有其他程序使用该DLL,则windows对DLL的引用记录减1,知道所有使用该DLL的程序都结束时才释放它。静态调用方式简单实用,但不如动态调用方式灵活。
静态调用方式不再使用系统API来加载、卸载DLL以及获取DLL中导出函数的地址。这是因为:当我们通过静态链接方式编译生成应用程序时,应用程序中调用的与.lib文件中导出符号相匹配的函数符号将进入到生成的exe 文件中,.lib文件中所包含的与之对应的DLL文件的文件名也被编译器存储在 exe文件内部。当应用程序运行过程中需要加载DLL文件时,Windows将根据这些信息发现并加载DLL,然后通过符号名实现对DLL函数的动态链接。这样,exe将能直接通过函数名调用DLL的函数,就象调用程序内部的其他函数一样。
DLL加载方式补充(道理一样):
- 隐式加载:
隐式加载是程序载入内存时加载所需的DLL文件,且该DLL随主进程始终占用内存。在编码时需要使用#pragma comment(lib,”myDLL.lib”)获得所需函数的入口。注意该.lib与静态链接库的.lib文件不同,静态链接库的.lib中包含了所需函数的代码,动态链接库的.lib仅指示函数在DLL文件中的入口。
- 显式加载:
显示加载是在程序运行过程中加载,不需要该DLL时则将其释放。在需要时使用loadLibrary加载,不需要时使用FreeLibrary释放。如果在LoadLibrary时该DLL已经在内存,则只需将其引用计数加1,如果其引用计数减为0则移出内存。
8.DllMain函数:
DllMain函数是DLL程序的入口函数,这个函数是DLL的内部函数,这意味着不能在应用工程中引用DllMain函数。
DllMain函数的框架结构:
BOOL APIENTRY DllMain( HMODULE hModule, //本模块句柄
DWORD ul_reason_for_call, //调用的原因
LPVOID lpReserved //显式加载与隐式加载
)
{
switch (ul_reason_for_call)
{
//动态链接库刚被映射到某个进程的地址空间
case DLL_PROCESS_ATTACH:
//应用程序创建了一个新的线程
case DLL_THREAD_ATTACH:
//应用程序某个线程正常终止
case DLL_THREAD_DETACH:
//动态链接库将被卸载
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
ul_reason_for_call参数的值表示本次调用的原因,可能是下面四种情况中的一种:
·DLL_PROCESS_ATTACH:表示动态链接库刚被某个进程加载,程序可以在这里做一些初始化工作,并返回TRUE表示初始化成功,返回FALSE表示初始化出错。当DLL被映射到进程的地址空间时,系统调用该DLL的DllMain函数,传递的fdwReason参数为DLL_PROCESS_ATTACH,这种调用只会发生在第一次映射时,如果同一个进程后来为已经映射进来的DLL在此调用LoadLibrary或LoadLibraryEx,操作系统只会增加DLL的使用次数。
·DLL_PROCESS_DETACH:表示动态链接库即将被卸载,程序可以在这里进行一些资源的释放工作,如释放内存、关闭文件等。什么时候DLL会从进程的地址空间解除映射:
- FreeLibrary解除DLL映射,有几个LoadLibrary,就要有几个FreeLibrary。
- 进程结束而解除DLL映射,如果进程的终结是因为调用了TerminateProcess,系统就不会使用DLL_PROCESS_DETACH来调用DLL的DllMain函数,这就意味着DLL在进程结束前没有机会执行任何清理工作。
注意:当用DLL_PROCESS_ATTACH调用DLL的DllMain函数时,如果返回FALSE,说明没有初始化成功,系统仍会用DLL_PROCESS_DETACH调用DLL的DllMain函数。因此,必须确保清理那些没有成功初始化的东西。
·DLL_THREAD_ATTACH:表示应用程序创建了一个新的线程,当进程创建一个线程时,系统查看当前映射到进程地址空间中的所有DLL文件映像,并用值DLL_THREAD__ATTACH调用DLL的DllMain函数。新创建的线程负责执行这次DLL的DllMain函数,只有当所有的DLL都处理完这一通知后,系统才允许进程开始执行它的线程函数。
注意:进程每次建立线程,都会用DLL_THREAD__ATTACH来初始化,哪怕是从线程中建立线程也是一样。
·DLL_THREAD_DETACH:表示某个线程正常终止,如果线程调用了ExitThread来结束线程(线程函数返回时,系统也会自动调用ExitThread),系统查看当前映射到进程空间中的所有DLL文件映像,并用DLL_THREAD_DETACH来调用DllMain函数,通知所有的DLL去执行线程级的清理工作。
注意:如果线程的结束是因为系统中的一个线程调用了TerminateThread,系统就不会用值DLL_THREAD_DETACH来调用所有DLL的DllMain函数。
9.DLL导出变量:
DLL定义的全局变量可以被调用进程访问;DLL也可以访问调用进程的全局数据。
若要导出某全局变量,我们需要往.def文件的EXPORTS后添加 变量名 DATA ,在应用程序函数中使用 extern type funcName; 这条语句声明导入的不是DLL中的全局变量本身,而是其地址,应用程序必须通过强制指针转换来使用DLL中的全局变量。例如使用时应:*(type*)funcName = *; 千万不能不经过强制类型转换就进行赋值,那样会将这个变量的地址改变。
而如果是使用_declspec(dllimport)来进行导入的话,使用变量的时候就不需要进行强制类型转换,这个方式导入的就是DLL中的全局变量本身而不是其地址了,个人认为这个方式最为方便简单。