WIN32汇编语言教程:第11章 动态链接库和钩子 · 11.1 动态链接库(2)

DllEntry       proc hInstDLL,dwReason,dwReserved
 
             mov    eax,dwReason
             .if    eax == DLL_PROCESS_ATTACH
                    ;保存hInstDll
                    ;初始化库需要的各种资源
                    .if    初始化成功
                        mov    eax,TRUE
                    .else
                        mov eax,FALSE
                    .endif
             .elseif eax == DLL_THREAD_ATTACH
                 ;释放库使用的资源
             .elseif eax == DLL_THREAD_DETACH
                 ;为新的线程分配资源
             .elseif eax == DLL_PROCESS_DETACH
                 ;为线程释放资源
             .endif
             ret
 
DllEntry       Endp

Windows会传给入口函数3个参数,dwReason参数的值表示本次调用的原因,它可能是下面的四种情况之一。

当dwReason的值是DLL_PROCESS_ATTACH的时候,表示动态链接库刚被映射到某个进程的地址空间,程序可以在这里进行一些初始化的工作,并返回TRUE表示初始化成功,返回FALSE表示初始化出错,这样库的装入就会失败。这给了动态链接库一个机会来阻止自己被装入。比如库程序可以在这里申请并保留一些内存,如果申请失败的话就可以返回FALSE告诉Windows,库无法正常工作。

当dwReason的值是DLL_PROCESS_DETACH的时候则相反,表示动态链接库将被卸载,库程序可以在这里进行一些资源的释放工作,如将初始化时申请的内存释放,将打开的文件关闭等。 以DLL_PROCESS_ATTACH和DLL_PROCESS_DETACH值进行的调用在库的生命周期中只可能出现一次。

当dwReason的值是DLL_THREAD_ATTACH的时候,表示应用程序创建了一个新的线程。当某个线程正常终止的时候,dwReason的值是DLL_PROCESS_DETACH。如果应用程序不是以多线程方式工作的话,就不会有这两种原因的调用;反之,如果应用程序频繁地创建和结束线程,那么入口函数将不断被调用。

hInstDll是动态链接库的模块实例句柄。当使用这个句柄来装入资源的时候,表示资源是定义在库文件中的,对于动态链接库来说,获取这个句柄的惟一途径就是保留入口函数的这个参数,如果在DLL_PROCESS_ATTACH时不将这个句柄保存下来的话,运行时可能就没有其他方法可以获取了。dwReserved参数是系统保留的参数,可以不必理会。

读者可能会问:不是可以通过GetModuleHandle(NULL)函数来获取模块实例句柄吗?是的,但是动态链接库是代表应用程序运行的,所以,如果在库中调用这个函数,得到的仍然是“宿主”程序的实例句柄,而不是库程序的实例句柄。

在例子程序中,不需要初始化工作,所以仅返回一个TRUE,表示任何情况下,Windows都可以装入这个库文件。动态链接库有一种很“极端”的应用:纯资源库,这些库仅包含资源而没有任何的功能函数,如字体文件等,对于这些库来说,库中的全部代码仅是入口函数中用来返回TRUE的那几句,这是库能被正常装入所必须的代码。

2. 导出函数

与写普通的可执行文件相比,动态链接库的设计流程中多了一个文件,那就是定义文件 *.def,源代码目录中还包括一个Counter.def文件,它的内容是:

EXPORTS _IncCount
       _DecCount

文件内容总共只有两行:一个EXPORTS关键字加上两个库中函数的名称,这是用来告诉链接器这两个函数需要导出,也就是说这两个函数可以被其他程序调用。动态链接库的文件格式是PE格式,每个PE格式文件的文件头中都可以有一个导出表,只有导出表中列出的函数才可以被其他程序调用,链接器根据def文件的内容在导出表中加入由EXPORTS关键字指定的函数名。

如果库文件中的函数没有在def文件中指定(如例子中的_SetText函数),那么这个函数就仅能被库文件中的代码调用,而无法在其他应用程序中使用,这是因为库文件的导出表中没有列出它的名称,这样其他程序根本不会知道它的存在。对于这些函数,可以把它们叫做私有函数。


3. 链接选项

为了生成动态链接库文件,在链接的时候必须使用合适的选项,来看看Counter库文件例子使用的Makefile文件:

DLL = Counter
ML_FLAG = /c /coff
LINK_FLAG = /subsystem:windows /Dll
 
$(DLL).dll: $(DLL).obj $(DLL).def
       Link $(LINK_FLAG) /Def:$(DLL).def $(DLL).obj
.asm.obj:
      ml $(ML_FLAG) $<
.rc.res:
       rc $<
clean:
       del *.obj
       del *.exp
       del *.lib

编译的时候,使用Ml.exe编译器的方法并没有什么不同,但是使用Link.exe链接程序的时候,必须使用/Dll和/Def选项,/Dll选项告诉链接器输出文件的格式是动态链接库,/Def:filename.def选项用来指定定义了导出函数名称的def文件名,在这个例子中,库文件中没有包含资源,如果包含资源的话,链接时还可以指定资源文件名,一个完整的链接参数如下所示:

Link /DLL /subsystem:windows /Def:filename.def filename.obj filename.res

4. 生成库文件

当使用Link.exe链接器完成链接工作后,链接器生成3个文件,它们分别以dll,lib和exp为扩展名。dll文件就是动态链接库,而lib文件是供程序开发用的导入库。

回想一下:当在汇编源程序中用到某个动态链接库中的函数时,在源文件的一开始就要用includelib语句指定动态链接库的导入库,这样链接的时候链接器才知道到哪个库中寻找指定的函数,如果开发的时候没有动态链接库的导入库文件,使用起来就比较麻烦了。

为了在开发其他程序的时候使用自己编写的动态链接库,就必须提供这个动态链接库的导入库文件,Link.exe考虑了这一点,所以在生成dll文件的同时也生成了导入库文件。如果dll文件是当做最终应用程序发布的,可以仅发布dll文件;如果是当做插件供其他人做二次开发用的,那么就要为其他程序员同时提供dll文件和lib文件,并且根据情况提供不同语言使用的头文件(最后,还要为每个导出函数写一个说明,包括参数的个数、类型和定义等)。目录中还有一个Counter.inc文件,它的内容如下:

_IncCount  proto  :dword,:dword
_DecCount  proto  :dword,:dword

这是为了在汇编程序中使用Counter.dll库文件而书写的include文件。(这样读者就知道MASM32软件包中包含的lib文件和inc文件是怎么来的了!)

exp文件是输出库文件,这是链接时的一个副产品,一般没有什么用途,我们可以直接将它删掉。

上页:第11章 动态链接库和钩子 · 11.1 动态链接库(1) 下页:第11章 动态链接库和钩子 · 11.1 动态链接库(3)

第11章 动态链接库和钩子

版权所有 © 中山市飞娥软件工作室 证书:粤ICP备09170368号