Windows API编程之动态链接库(DLL)
2020-12-28 12:30
/* * testMain.c */ #include#include typedef int (* PGetMax)(int, int); int main() { int a = 2; int b = 3; HINSTANCE hDll; // DLL句柄 PGetMax pGetMax; // 函数指针 hDll = LoadLibrary(".\\Debug\\DLL_lib.dll"); if (hDll == NULL) { printf("Can‘t find library file \"dll_lib.dll\"\n"); exit(1); } pGetMax = (PGetMax)GetProcAddress(hDll, "GetMax"); if (pGetMax == NULL) { printf("Can‘t find function \"GetMax\"\n"); exit(1); } printf(" max(2, 3) = %d\n", pGetMax(2, 3)); FreeLibrary(hDll); return 0; }
此时,不再需要动态的.h文件和.lib文件,只需要提供.dll文件即可。在具体使用时,先用LoadLibrary加载Dll文件,然后用GetProcAddress寻找函数的地址,此时必须提供该函数的在Dll中的名字(不一定与函数名相同)。
然后编译链接、运行,结果与前面的运行结果相同。
下面将解释,为什么前面要去掉WINAPI调用约定(即采用默认的__cdecl方式)。我们可以先看看DLL_Lib.dll里面的链接符号。在cmd中运行命令:
dumpbin /exports DLL_Lib.dll
得到如下结果:
Dump of file f:\code\DLLTest\Debug\Dll_lib.dll File Type: DLL Section contains the following exports for DLL_Lib.dll 0 characteristics 4652C3B1 time date stamp Tue May 22 18:19:29 2007 0.00 version 1 ordinal base 1 number of functions 1 number of names ordinal hint RVA name 1 0 0000100A GetMax Summary 4000 .data 1000 .idata 3000 .rdata 2000 .reloc 28000 .text
可以看到GetMax函数在编译后在Dll中的名字仍为GetMax,所以在前面的程序中使用的是:
pGetMax = (PGetMax)GetProcAddress(hDll, "GetMax");
然后,我们把WINAPI添加回去,重新编译DLL_Lib工程。运行刚才的DLL_Test程序,运行出错,结果如下:
> process attach of dll
Can‘t find function "GetMax"
> process detach of dll
Press any key to continue
显然,运行失败原因是因为没有找到GetMax函数。再次运行命令:dumpbin /exports DLL_Lib.dll,结果如下(部分结果):
1 ordinal base 1 number of functions 1 number of names ordinal hint RVA name 1 0 0000100A _GetMax@8
从上面dumpbin的输出看,GetMax函数在WINAPI调用约定方式下在DLL里的名字与源码中的函数定义时的名字不再相同,其导出名是"_GetMax@8"。此时,你把testMain.c中的函数指针类型声明和函数查找语句作如下修改:
typedef int (WINAPI* PGetMax)(int, int);
pGetMax = (PGetMax)GetProcAddress(hDll, "_GetMax@8");
再次编译链接,然后运行,发现结果又正确了。
现在找到了问题所在。很显然,这种修改方式并不适用,而默认生成的名字又不是我们所想要的。那么该怎么解决这个问题呢?这就需要用到.def文件来解决。
模块定义文件(.def)
模块定义文件(.def文件)是一个描述DLL的各种属性的文件,可以包含一个或多个模块定义语句。如果你不使用关键字__declspec(dllexport)关键字导出DLL中的函数,那么DLL就需要一个.def文件。
一个最小的.def文件必须包含下面的模块定义语句:
(1)文件中第一个语句必须是LIBRARY语句。该语句标记该.def文件属于哪个DLL。语法形式为:LIBRARY
(2)EXPORTS语句列表。第一个导出语句的形式为:entryname[=internalname] [@ordinal],列出DLL中要导出的函数的名字和可选的序号(ordinal value)。要导出的函数名可以是程序源码中的函数名,也可以定义新的函数别名(但后面必须紧跟[=]);序号必须在范围1到N之间且不能重复,其中N是DLL中导出的函数个数。因此,EXPORTS语句语法形式为:
EXPORTS
[=
[=
;...
(3)虽然不是必须的,一个.def文件也常常包含DESCRIPTION语句,用来描述该DLL的用途之类,语法形式为:
DESCRIPTION ""
(4)在任意位置,可以包含注释语句,以分号(;)开始。
例如,在本文中后面将用到的.def文件为:
; DLL_Lib.def LIBRARY DLL_Lib ; the dll name DESCRIPTION "Learn how to use the dll." EXPORTS GetMax @1 Max=GetMax @2 ; alias name of GetMax ; Ok, over
现在,让我们回到DLL_Lib工程,修改GetMax函数的声明,把EXPORT去掉,重新编译该工程。然后,运行dumpbin命令,我们发现此时没有导出函数。再将上面的DLL_Lib.def文件添加进DLL_Lib工程,再次编译,并运行dumpbin命令,得到如下结果(引用部分结果):
1 ordinal base 2 number of functions 2 number of names ordinal hint RVA name 1 0 0000100A GetMax 2 1 0000100A Max
正如我们所预期的,有两个导出函数GetMax和Max。注意,此时源码中的GetMax函数的导出名不再是默认的“_GetMax@8”。另外,需要注意的是,两个导出函数有相同的相对虚拟地址(RVA),也说明了两个导出名实质是同一个函数的不同名字而已,都是源码中GetMax函数的导出名。
现在,回到DLL_Test工程,修改testMain.c文件内容如下:
/* * testMain.c */ #include#include typedef int (WINAPI* PGetMax)(int, int); int main() { int a = 2; int b = 3; HINSTANCE hDll; // DLL句柄 PGetMax pGetMax; // 函数指针 hDll = LoadLibrary(".\\Debug\\DLL_lib.dll"); if (hDll == NULL) { printf("Can‘t find library file \"dll_lib.dll\"\n"); exit(1); } pGetMax = (PGetMax)GetProcAddress(hDll, "GetMax"); if (pGetMax == NULL) { printf("Can‘t find function \"GetMax\"\n"); exit(1); } printf(" GetMax(2, 3) = %d\n", pGetMax(2, 3)); pGetMax = (PGetMax)GetProcAddress(hDll, "Max"); if (pGetMax == NULL) { printf("Can‘t find function \"GetMax\"\n"); exit(1); } printf(" Max(2, 3) = %d\n", pGetMax(2, 3)); FreeLibrary(hDll); return 0; }
编译链接、运行,结果如下:
> process attach of dll
GetMax(2, 3) = 3
Max(2, 3) = 3
> process detach of dll
Press any key to continue
运行结果正如前面分析的那样,GetMax和Max都得到了相同的结果。
到这里,我们解决了DLL导出函数名在各种调用约定下的默认名可能不同于源码中函数名的问题。此时,你就可以制作跟Windows的自带API函数库相同的库了:使用__stdcall调用约定以满足Windows下的任何语言都可以调用DLL库,同时使用函数名作为导出名,以方便用户使用DLL里的函数。
#pragma data_seg ("shared")
int g_oneNumber = 0;
#pragma data_seg ()
文章标题:Windows API编程之动态链接库(DLL)
文章链接:http://soscw.com/index.php/essay/38828.html