WIN32汇编语言教程:第11章 动态链接库和钩子 · 11.1 动态链接库(4)
第二个实验:修改上一节中的Counter.asm,将入口函数中的返回值改为FALSE,也就是说模拟dll初始化失败的情况。修改完毕后重新编译链接生成新的Counter.dll文件,并将文件拷贝到UseDll1.exe所在目录后运行,系统将弹出如图11.3所示的错误提示。也就是说,任何一个dll文件因为初始化失败而无法装入时,可执行文件也是无法被装入执行的。
图11.3 Dll初始化失败时的错误信息
第三个实验:模拟软件升级或dll文件版本不对时的情况,这种情况在Windows系统中经常发生,因为当某些应用软件包被安装的时候,它可能会用自己附带的某个版本的dll文件替换掉Windows目录中已存在的dll文件,当程序卸载的时候,它有可能会根据备份恢复原来的版本,但更多的情况是根本没有恢复,经过多次安装和卸载不同的应用软件包后,最终的结果就是Windows系统目录中各个dll文件的版本参差不齐。不同版本的dll文件中可能增加了一些函数,也可能废弃了一些函数,有时其他使用这个dll文件的程序可能刚好用到不存在的函数,而这个函数在原来版本的dll文件中本来是存在的。
现在就修改程序来模拟这种情况,将Counter.def文件中的_DecCount一行去掉,这样dll文件的导出表中就不会有这个函数,相当于函数不存在了,然后重新编译dll文件并将它拷贝到UseDll1.exe所在目录,执行UseDll1.exe,这时系统显示的是如图11.4所示的错误提示,UseDll1.exe程序仍然不能被正常装入执行。
图11.4 找不到导出函数的错误信息
现在读者一定明白这个最熟悉不过的错误信息的由来了,通常对付这种莫名其妙的错误的最好方法就是重新安装Windows,这将使所有dll文件的版本被重新安装为统一的版本,错误也就自然消失了。读者也可能会说:把出错的dll替换掉不就行了吗,为什么要整个重装呢?问题是你知道原来的版本应该是多少吗?
如果使用这种标准的方法调用动态链接库中的函数,链接器会根据导入库中的信息将使用的dll文件名和函数名存放在可执行文件头的导入表中,这样Windows要执行文件的时候,在装入的过程中会根据导入表中的dll列表寻找每个dll文件,并根据函数名在每个dll中寻找导出函数,如果这中间出现任何错误,如上面演示的dll文件丢失、dll文件装入失败或dll中的函数名无法找到等情况,应用程序都无法被装载执行。
2. 方法二:动态装入
方法一的优点就是使用方便,应用程序可以像使用自己内部的函数一样使用DLL中的函数,缺点也显而易见,如果装入DLL的过程中有任何错误,应用程序没有任何机会完成应变的措施,因为它根本没有被装入执行。
编程中有时候会有下面的需求:
● 程序需要使用系统中的保留函数。这个函数确实存在于动态链接库的导出表中,可以被其他程序引用,但是软件开发包提供的lib文件中并不包含它。
● 不同版本Windows中的函数集不同(如Windows NT中的很多与安全有关的函数在Windows 9x中不存在),同一版本Windows中不同版本dll文件的函数集也可能不同,程序需要根据函数是否存在做不同的处理。
● 程序使用的某些库并不重要(如仅用来显示程序版本的库),如果丢失这个库,程序希望能继续运行,而不是像上面演示的那样出现根本无法装入的情况。
对于这些需求,解决的办法就是不能将动态链接库的导入信息保存在可执行文件的导入表中,也就是说不要让Windows系统来做动态链接库的装入工作,这些工作由应用程序自己的代码来完成。有3个函数可以用来完成这样的功能:LoadLibrary(装入动态链接库),FreeLibrary(释放动态链接库)和GetProcAddress(获取导出函数地址)。
例子UseDll2.asm程序使用这种动态装入的方法来实现UseDll1程序同样的功能,来看看UseDll2.asm的内容:
.386 .model flat, stdcall option casemap :none ;#################################################################### ; Include 文件定义 ;#################################################################### include windows.inc include user32.inc includelib user32.lib include kernel32.inc includelib kernel32.lib ;#################################################################### ; Equ 等值定义 ;#################################################################### ICO_MAIN equ 1000 DLG_MAIN equ 1000 IDC_COUNTER equ 1001 IDC_INC equ 1002 IDC_DEC equ 1003 _PROCVAR2 typedef proto :dword,:dword PROCVAR2 typedef ptr _PROCVAR2 ;#################################################################### ; 数据段 ;#################################################################### .data? hDllInstance dd ? lpIncCount PROCVAR2 ? lpDecCount PROCVAR2 ? .const szError db 'Counter.dll文件丢失或装载失败,计数功能无法实现',0 szDll db 'Counter.dll',0 szIncCount db '_IncCount',0 szDecCount db '_DecCount',0 ;#################################################################### ; 代码段 ;#################################################################### .code ;#################################################################### _ProcDlgMain proc uses ebx edi esi hWnd,wMsg,wParam,lParam mov eax,wMsg ;******************************************************************** .if eax == WM_CLOSE .if hDllInstance invoke FreeLibrary,hDllInstance .endif invoke EndDialog,hWnd,NULL ;******************************************************************** .elseif eax == WM_INITDIALOG invoke LoadLibrary,addr szDll .if eax mov hDllInstance,eax invoke GetProcAddress,hDllInstance,\ addr szIncCount mov lpIncCount,eax invoke GetProcAddress,hDllInstance,\ addr szDecCount mov lpDecCount,eax .else invoke MessageBox,hWnd,addr szError,NULL,\ MB_OK or MB_ICONWARNING
上页:第11章 动态链接库和钩子 · 11.1 动态链接库(3) 下页:第11章 动态链接库和钩子 · 11.1 动态链接库(5)