浅谈动态链接库(DLL)的显式链接和隐式链接
0 动态链接库
简介
动态链接库(dynamic-link library,DLL)是Windows操作系统的基石,Windows系统所有的应用程序编程接口都包含在DLL中,其中最重要的三个DLL是:
- Kernel.dll:包含的函数用来管理内存、进程、线程
- User32.dll:包含的函数用来执行与用户界面相关任务,比如创建窗口和发送消息
- GDI32.dll:负责绘制图像和显示文字
DLL通常由一组独立的函数组成,DLL可以认为是一组源代码模块,每个模块包含一些可供PE程序或者其他DLL调用的函数。
一般会把DLL称为导出模块,因为他主要是导出一些函数和变量供其他程序使用(当然DLL可以导入DLL来用),而把可执行程序(PE程序)称为导入模块,因为他导入dll的符号(符号是指函数和变量)来使用。
在PE程序或其他DLL调用一个DLL函数之前,要先将这个DLL文件映像映射到程序进程的地址空间中,这就是链接过程,有两种方法:
- 隐式载入时链接(implicit load-time linking)
- 显式运行时链接(explicit run-time linking)
当DLL载入进程的地址空间之后,那就可以看成程序的一部分了,也就是一堆外部提供的函数或数据。
从某种意义上说,一个地址空间是由一个可执行模块和多个DLL模块组成的
1 隐式链接过程
以下内容都是Windows核心编程的,看那个图就能懂得7788了。
隐式载入dll: 直接让PE程序的源代码中引用dll中的符号(函数、变量等),这样加载PE程序的时候会自动链接,这里需要借助生成dll文件时产生的.lib
文件。



2 显式载入dll
强烈建议看这个视频前半部分
极安御信网络安全系列课程-Windows系统编程-动态链接库基础与远程线程注入
当PE程序正在执行的时候,通过一些API,可以将DLL调入进程地址空间使用,所以这里用不到.lib
文件
//借助的就是这个API //这两个函数会在系统中对DLL文件映像进行定位,然后调入进程地址空间 HMODULE LoadLibrary(PCTSTR pszDLLPathName); HMODULE LoadLibraryEx( PCTSTR pszDLLPathName; HANDLE hFile; //将来扩充保留的参数,一般设为NULL就行 DWORD dwFlags; //可以设为0,也有一些其他的关键字 ) //HMODULE: 表示文件映像被映射到的虚拟内存地址,HMODULE等价于HINSTANCE,失败则返回NULL
显式和隐式的区别就在于,显式是在程序运行时完成的

2.1 显式卸载DLL
当进程不再需要使用DLL中的符号时,可以调用下面的函数显式的卸载DLL
BOOL FreeLibrary(HMODULE hInstDll); //或者用这个函数(这是函数声明) VOID FreeLibraryAndExitThread( HMODULE hInstDll, DWORD dwExitCode); //函数原型,在Kernel32.dll中有 //这是Microsoft特意封装的一个函数 VOID FreeLibraryAndExitThread(HMODULE hInstDll, DWORD dwExitCode) { FreeLibrary(hInstDll); ExitThread(dwExitCode); }
2.2 进程中的dll计数
每一个进程在首次调入dll时,会对其进行计数。同一进程的不同线程再次调入dll,只会计数器自增,而不会把dll再调入内存。也就是说同一进程内的dll是共享的。
不同进程间的dll计数是独立的。、
当计数器为0的时候,说明dll已经调出进程地址了。
//用GetModuleHandle来检测一个DLL是否映射到进程地址中 //比如当Mylib.dll未调入进程时,下面代码会把他调入 HMODULE hInstDll = GetModuleHandle (TEXT("MyLib")) if (hInstDll == NULL) { hInstDll = LoadLibrary(TEXT("MyLib")); }
2.3 显式的链接到导出符号
在导入dll之后,想要使用dll中的符号,我们可以用下面的函数进行显式的链接
//线程通过调用这个函数获得想要应用的符号地址 FARPROC GetProcAddress( HMODULE hInstDll, PCSTR pszSymbolName); """ 参数说明: hInstDll:指定包含我们想要符号的dll句柄 pszSymbolName:有两种形式: 1. 符号名:一个ANSI字符串,以“\0”结尾的,比如“MyDll” 2. 序号:但是不建议使用!!! 返回值是一个函数指针,但是使用这个指针前要做转换 """
上述函数返回值是一个函数指针,使用时要先转换为匹配的函数类型
//比如我想用MyDll中的DumpModule函数 //DumpModule函数声明如下 void DumpModule(HMODULE hModule); //函数指针的定义,这里callback是函数调用约定(wyl跟我说的) typedef void (CALLBACK *PFN_DUMPMODULE)(HMODULE hModule); //那么显式链接可以这么写,记得做类型转换 PFN_DUMPMODULE pfnDumpModule = (PFN_DUMPMODULE)GetProcAddress(MyDll, "DumpModule"); if(pfnDumpModule != NULL) { pfnDumpModule (MyDll); //调用dll中的符号 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步