七、动态地加载和调用动态库中的函数
在VC中new一个名为dllCall的Win32 Console Application工程,并将上面生成的dllTest.dll文件拷贝到dllCall的工程子目录下。dllCall工程仅包含一个main.cpp文件,其源代码如下::
#include <stdio.h>
#include <windows.h>
typedef int(*lpAddFun)(int, int); //定义函数指针类型
int main(int argc, char *argv[])
{
HINSTANCE hDll; //DLL句柄
lpAddFun addFun; //函数指针变量
hDll = LoadLibrary("dllTest.dll");
if (hDll != NULL)
{
addFun = (lpAddFun)GetProcAddress(hDll, "add");
if (addFun != NULL)
{
int result = addFun(2, 3);
printf("%d", result);
}
FreeLibrary(hDll);
}
return 0;
}
下面我们来逐一分析上面的程序。
首先,语句typedef int ( * lpAddFun)(int,int)定义了一个与add函数接受参数类型和返回值均相同的函数指针类型。随后,在main函数中定义了lpAddFun的实例addFun;
其次,在函数main中定义了一个DLL HINSTANCE句柄实例hDll,通过Win32 API函数LoadLibrary动态加载了DLL模块并将DLL模块句柄赋给了hDll;
再次,在函数main中通过Win32 API函数GetProcAddress得到了所加载DLL模块中函数add的地址并赋给了addFun。经由函数指针addFun进行了对DLL中add函数的调用;
最后,应用工程使用完DLL后,在函数main中通过Win32 API函数FreeLibrary释放了已经加载的DLL模块。
上面例子中我们看到了由“LoadLibrary-GetProcAddress-FreeLibrary”系统API提供的三位一体“DLL加载-DLL函数地址获取-DLL释放”方式,这种调用方式称为DLL的动态调用方式。动态调用方式的特点是完全由编程者用Windows API 函数加载和卸载 DLL,程序员可以在运行时决定 DLL 文件何时加载或不加载,决定加载哪个 DLL 文件。
八、静态地加载和和调用动态库中的函数
与动态调用方式相对应的就是静态调用方式。静态调用方式的特点是由编译系统完成对DLL的加载和应用程序结束时 DLL 的卸载。当调用某DLL的应用程序结束时,若系统中还有其它程序使用该 DLL,则Windows对DLL的应用记录减1,直到所有使用该DLL的程序都结束时才释放它。静态调用方式简单实用,但不如动态调用方式灵活。
下面我们来看看静态调用的例子,将编译dllTest工程所生成的dllTest.lib和dllTest.dll文件拷入dllCall工程所在的路径,dllCall执行下列代码:
// 导入库dllTest.lib文件中仅仅是关于其对应的DLL文件中函数的重定位信息
#pragma comment(lib,"dllTest.lib")
extern "C" __declspec(dllimport) add(int x,int y);
int main(int argc, char* argv[])
{
int result = add(2,3);
printf("%d",result);
return 0;
}
由上述代码可以看出,静态调用方式的顺利进行需要完成两个动作:
(1)告诉编译器与DLL相对应的.lib导入库文件所在的路径及文件名,#pragma comment(lib,"dllTest.lib")就是起这个作用。
程序员在建立一个DLL文件时,连接器会自动为其生成一个对应的.lib导入库文件,该文件包含了DLL 导出函数的符号名及序号(并不含有实际的代码)。在应用程序里,.lib文件将作为DLL的替代文件参与编译。
(2)声明导入函数,extern "C" __declspec(dllimport) add(int x,int y)语句中的__declspec(dllimport)发挥这个作用。
静态调用方式不再需要使用系统API来加载、卸载DLL以及获取DLL中导出函数的地址。这是因为,当程序员通过静态链接方式编译生成应用程序时,应用程序中调用的与.lib文件中导出符号相匹配的函数符号将进入到生成的EXE 文件中,.lib文件中所包含的与之对应的DLL文件的文件名也被编译器存储在 EXE文件内部。当应用程序运行过程中需要加载DLL文件时,Windows将根据这些信息发现并加载DLL,然后通过符号名实现对DLL 函数的动态链接。这样,EXE将能直接通过函数名调用DLL的输出函数,就象调用程序内部的其他函数一样。