DLL(动态链接库)编程
dll是现在常见的文件,它集成了程序的很多功能在里面。一般情况下,它不能直接被执行,常见的使用方法是用其他的*.exe调用其执行,以使其内部功能表现出来。还有*.ocx文件也与之类似,也就是人们常说的com
1.简要
Windows API中所有的函数都包含在dll中,其中有3个最重要的DLL。
(1)
Kernel32.dll
它包含那些用于管理内存、进程和线程的函数,例如CreateThread函数;
(2)
User32.dll
它包含那些用于执行用户界面任务(如窗口的创建和消息的传送)的函数,例如CreateWindow函数;
(3)
GDI32.dll
它包含那些用于画图和显示文本的函数。
2.
静态库和动态库
(1)
静态库
函数和数据被编译进一个二进制文件(通常扩展名为.LIB)。在使用静态库的情况下,在编译链接可执行文件时,链接器从库中复制这些函数和数据并把
它们和应用程序的其他模块组合起来创建最终的可执行文件(.Exe文件).当发布产品时,只需要发布这个可执行文件,并不需要发布被使用的静态库。(2)
动态库
在使用动态库的时候,往往提供两个文件:一个引入库(.lib)文件和一个DLL(.dll)文件。虽然引入库的后缀名也是”lib”,但是动态库
的引入库文件和静态库文件有着本质上的区别,对一个DLL来说,其引入库文件(.lib)包含该DLL导出的函数和变量的符号名,而.dll文件包含该
DLL实际的函数和数据。在使用动态库的情况下,在编译链接可执行文件时,只需要链接该DLL的引入库文件,该DLL中的函数代码和数据并不复制到可执行
文件中,直到可执行程序运行时,才去加载所需的DLL,将该DLL映射到进程的地址空间外,然后访问DLL中导出的函数。这时,发布产品时,除了发布可执
行文件以外,同时还要发布该程序将要调用的动态链接库。
3.
在导出库头文件中的标准写法:
#ifdef LIBDAQ_EXPORTS
#define LIBDAQ_API __declspec(dllexport)
#else
#define LIBDAQ_API __declspec(dllimport)
#endif
将该头文件添加到某客户代码中时,会自动展开。如果客户代码没有定义LIBDAQ_EXPORTS,那么LIBDAQ_EXPORTS会被定义为__declspec(dllimport)表示有LIBDAQ_EXPORTS头的函数都是从该DLL中导入的。
4.
名字改编和”extern “C””
C++编译器在生成DLL时,会对导出的函数进行名字改编,并且不同的编译器使用的改变规则不一样,因此改编后的名字会不一样。这样,如果利用不同
的编译器分别生成DLL和访问该DLL的客户端代码程序的话,后者在访问该DLL的导出函数时会出现问题。为了实现通用性,需要加上限定符:extern
“C”。
但是利用限定符extern “C”可以解决C++和C之间相互调用时函数命名的问题,但是这种方法有一个缺陷,就是不能用于导出一个类的成员函数,只能用于导出全局函数。
5.
显示加载方式加载DLL
使用动态方式来加载动态链接库时,需要用到LoadLibrary函数。该函数的作用就是将指定的可执行模块映射到调用进程的地址空间。调用原型为:
HMODULE LoadLibrary(LPCTSTR lpFileName);
LoadLibrary函数不仅可以加载DLL,还可以加载可执行模块(Exe)。当加载可执行模块时,主要是为了访问该模块内的一些资源,例如对
话框资源、位图资源或图标资源等。LoadLibrary函数有一个字符串类型(LPCTSTR)的参数,该参数指定了可执行模块的名称,既可以是一个
dll文件,也可以是一个exe文件。如果调用成功,LoadLibrary函数将返回所加载的那个模块的句柄。返回类型HMODULE和
HINSTANCE可以通用。
当加载到动态链接库模块的句柄后,接下来就要想办法获取该动态链接库中导出函数的地址,这可以通过调用GetProcAddress函数来实现。该函数用来获取DLL导出函数的地址,其原型声明如下所示:
FARPROC GetProcAddress(HMODULE hModule, LPCSTR lpProcName);
参数hModule:指定动态链接库模块的句柄,即LoadLibrary函数的返回值。
参数lpProcName:一个指向常量的字符指针,指定DLL导出函数的名字或函数的序号。如果是序号,则序号必须在低位字节中,高位字节必须是0。
如果调用成功,GetProcAddress函数将返回指定导出函数的地址;否则返回NULL。
例如:
HINSTANCE hInst;
hInst = LoadLibrary(“DllTest.dll”);
typedef int (*ADDPROC)(int a, int b);
ADDPROC add = (ADDPROC)GetProcAddress(hInst, “add”);
if (!add)
print(“Failure”);
else
process next events
FreeLibrary(hInst);
调用语法:
BOOL FreeLibrary(HMODULE hModule);
6. 加载DLL的两种方式优缺点:
采用动态加载方式,那么可以在需要时才加载DLL,而隐式链接方式实现起来比较简单,在编写客户端代码时就可以把链接工作做好,在程序中可以随时调用
DLL导出的函数。但是如果程序需要访问十多个DLL时,如果都采用隐式链接方式加载它们的话,那么在该程序启动时,这些DLL都需要被加载到内存中,并
映射到调用进程的地址空间,这样将加大程序的启动时间。而且一般来说,在程序运行过程中只是在某个条件满足时才需要访问某个DLL中的某个函数,其它情况
下都不需要访问这些DLL中的函数。但是这时所有的DLL都已经被加载到内存中,资源浪费是比较严重的。这个时候就需要采用显示加载的方式来访问DLL,
在需要时才加载所需的DLL。也就是说在需要时才被加载到内存中,并被映射到调用进程的地址控件中。需要说明的是,隐式链接方式访问DLL时,在程序启动
时也是通过LoadLibrary函数加载该进程需要的动态链接库的。
7. DllMain函数
如果提供了DllMain函数(该函数是可以选择存在的),那么在此函数中不要进行太复杂的调用。因为在加载该动态链接库时,可能还有一些核心动态
链接库没有被加载。例如Use32.dll或GDI32.dll。我们自己编写的DLL会比较靠前地被加载。
阅读(1922) | 评论(1) | 转发(0) |