Windows核心编程——动态库的加载与导出
1.隐示加载与显示加载
使用动态DLL有两种方法,一种是隐式链接,一种是显式链接,如果用loadlibrary就是显示链接,用lib就属于隐式链接。
两种方法对于你的程序调用动态库时没有任何区别,只是步骤是不一样的。显式调用麻烦了点,但可以没有相应的lib库;隐式调用,使用起来比较简单,有函数的声明就可以了,但必须有lib库。
隐式加载默认是加载到内存中的,始终占用内存。
显示加载,你加载时占用内存,释放了就不占用内存了。如果该DLL已经载入,loadlibrary只是会增加一个引用计数,相同,freelibrary也只是减少引用计数,如果引用计数为0时,DLL才从内存中移除。
显式和隐式只是对于代码编写时来说的,最后产生的可执行程序,不管是显式和隐式,都是用loadlibrary载入的。显式与隐式不是用在这些方面的,显式加载适合需要动态的选 用DLL的情况。
2.DLL与exe的依赖关系:
3.显示加载过程(动态加载)
调用API的方式来加载dll:
//宏定义: typedef int (*PFN_ADD)(int, int); typedef int(*PFN_SUB)(int, int); typedef void(*PFN_TEST)();
1) LoadLibrary - 将dll加载到进程的内存
//加载dll到进程 HMODULE hMod = LoadLibrary("dll.dll");
2) GetProcAddress - 获取dll的导出函数的地址,通过函数指针的方式调用
//获取出导出函数的地址 PFN_ADD pfnAdd = (PFN_ADD)GetProcAddress(hMod, "Add"); int nVal = pfnAdd(1, 5); //获取出导出变量的地址 int* pnTest = (int*)GetProcAddress(hMod, "g_nTest"); //通过序号获取函数地址 PFN_SUB pfnSub = (PFN_SUB)GetProcAddress(hMod, (LPCTSTR)0x0006); nVal = pfnSub(1, 5);
3)FreeLibrary - 卸载dll
//卸载dll FreeLibrary(hMod);
4.GetProcAddres API是如何区分序号和字符串?
低64K(0x10000)的地址是不可用的,如果是字符串的地址必高于0x10000,一定在用户区,所以小于0x10000的值是序号,大于的值是字符串地址。
5.如果一个函数没有导出,能不能调用?
可以,但我们需要拿到函数的地址
通过计算:模块基址 + 函数与模块基址的偏移 = 函数的地址
//调用没有导出的函数 PFN_TEST pfnTest = (PFN_TEST)((int)hMod + 0x11620); pfnTest();
6.def导出
添加 __declspec (dllexport) 测试导出函数,
depend打开可以看到导出的函数:
导出后是粉碎后的名称。
如果想知道完整明确的导出函数函数名,可以采用def导出 - 给其他编译器或语言使用
指定函数导出的名字
用法:
添加.def 文件
DEF语法:
=internalname:内部函数的名称
@ordinal:序号
NONAME:此函数不以名字导出,只以序号导出
PRIVATE:只能显示加载,不能隐式加载
DATA :导出的是数据。
注意,使用 .def 文件从 DLL 中导出变量时,不需要在变量上指定 __declspec(dllexport)。但是,在任何使用 DLL 的文件中,仍必须在数据声明上使用 __declspec(dllimport)。
7.测试def导出:
可以看到函数已被导出: