本课中,我们将学习DLLs,它们到底是什么和如何创建它们。
理论:
如果您编程的时间非常长,就会发现很多的程序之间其实有相当多的重复代码。每编一个程序就重写一遍这些代码既没必要又浪费时间。在DOS时代,一般的做法是把这些重复的代码写成一个个的函数,然后把它们按类别放到不同的库文件中去。当要使用这些函数时,只要把您的目标文件(.obj)文件和先前存放在库文件中的函数进行链接,链接时链接器会从库文件中抽取相关的信息并把它们插入到可执行文件中去。这个过程叫做静态链接。C运行时库就是一个好例子。这样的库的缺点是您在每一个调用库函数的程序中都必须嵌入同一函数的拷贝,这显然很浪费磁盘。在DOS时代毕竟每一时刻仅有一个程序在运行,所以浪费的还只是磁盘而已,在多任务的WINDOWS时代就不仅浪费磁盘,还要浪费宝贵的内存了。
在WINDOWS中,由于有多个程序同时运行,如果您的程序非常大的话,那将消耗相当多的内存。WINDOWS的解决办法是:使用动态链接库。动态链接库从表面上看也是一大堆的通用函数,不过即使有多个程序调用了它,在内存中也仅仅只有动态链接库的唯一一份拷贝。WINDOWS是通过分页机制来作到这一点的。当然,库的代码只有一份,但是每一个应用程序要有自己单独的数据段,要么就会乱掉。
不象旧时的静态链接库,它并不会把这些函数的可执行代码放入到应用程序中去,而是当程序已经在内存中运行时,如果需要调用该函数时才调入内存也即链接。这也就是为什么把它叫做“动态”的原因所在。另外您还可以动态地卸载动态链接库,当然要求这时没有其它的应用程序在使用它,否则就要一直等到最后一个使用它的函数也不再使用该动态链接库时才能去卸载它。
为了正确的调用库和给库函数分配内存空间,在编译和链接应用程序时,必须把重定位等一些消息插入到执行代码中去,以便载入正确的库,并给库函数分配正确的地址。
那么这些信息从哪里得到呢?引入库。引入库包含足够的信息,链接器从中抽取足够的信息(注意区别:静态链接库放入的是可执行代码)把它们放入到可执行文件中去。当WINDOWS的加载器装入应用程序查看到有DLL时,它会查找该库文件,如果没有查到,就报错退出,否则就把它映射进进程的地址空间,并修正函数调用语句的地址。
如果没有引入库呢?当然我们也可以调用动态链接库中的任意函数。只不过您必须知道调用的函数是否在库中而且是否在库的引出名字表中,另外还需要知道该函数的参数个数和参数的类型。
(译者加:说到这里,让我想起了一件很有名的事。<<Undocumented Windows>>一书的作者Angel Schudleman 曾经利用此方法来跟踪微软Win3x系统动态链接库中未公开的函数,因为在微软给程序员提供的系统动态链接库的引入库中没有提供这些函数的原型,所以您无法在链接时把这些函数的信息链接到可执行文件中去,而为了某种目的您又要使用这些函数,您就可以在执行时加载动态链接库并得到这些函数的地址,从而和调用其它的库函数一样使用这些未公开的函数。由于这本书的巨大影响,当时许多程序员纷纷在它们的程序中调用未公开函数,甚至在写商业程序时也这么做。这种走偏峰的做法引起了微软的反感,后来微软在它Win3x的改进版中不再把那些未公开函数列入系统动态链接库的引出名字表,这样也就无法再利用这种方法来调用未公开的函数了。)
当您让系统的加载器为您加载动态库时,如果不能找到库文件,它就会提示一条“A required .DLL file, xxxxx.dll is missing”,这样您的应用程序就无法运行,即使该库对您的应用程序来说并不重要。
如果您选择在程序运行时自己加载该库,就没有这种问题了。
如果您知道足够的信息,就可以调用系统未公开的函数。
如果您调用LoadLibrary函数加载库,就必须再调用GetProcAddress函数来得到每一个您想调用的函数的地址,GetProcAddress会在动态链接库中查找函数的入口地址。由于多余的步骤,这样您的程序执行起来会慢一点,但是并不明显。
明白了LoadLibrary函数的优缺点,下面我们就来看看如何产生一个动态链接库。下面的代码是一个动态链接库的框架:
;--------------------------------------------------------------------------------------
; DLLSkeleton.asm
;--------------------------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
.data
.code
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
mov eax,TRUE
ret
DllEntry Endp
;---------------------------------------------------------------------------------------------------
;下面是一个空函数,您可以象下面一样插入您的函数。
;----------------------------------------------------------------------------------------------------
TestFunction proc
ret
TestFunction endp
End DllEntry
;-------------------------------------------------------------------------------------
; DLLSkeleton.def
;-------------------------------------------------------------------------------------
LIBRARY DLLSkeleton
EXPORTS TestFunction