📄 011.txt
字号:
1. 方法一:常规方法
先看UseDll1.asm程序,这个程序用常规的方法实现了对Counter.dll动态链接库中函数的调用:
.386
.model flat, stdcall
option casemap :none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
include Counter.inc
includelib Counter.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Equ 等值定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN equ 1000
DLG_MAIN equ 1000
IDC_COUNTER equ 1001
IDC_INC equ 1002
IDC_DEC equ 1003
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_ProcDlgMain proc uses ebx edi esi hWnd,wMsg,wParam,lParam
mov eax,wMsg
;********************************************************************
.if eax == WM_CLOSE
invoke EndDialog,hWnd,NULL
;********************************************************************
.elseif eax == WM_COMMAND
mov eax,wParam
.if ax == IDC_INC
invoke _IncCount,hWnd,IDC_COUNTER
.elseif ax == IDC_DEC
invoke _DecCount,hWnd,IDC_COUNTER
.endif
.else
mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
_ProcDlgMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
invoke GetModuleHandle,NULL
invoke DialogBoxParam,eax,DLG_MAIN,NULL,\
offset _ProcDlgMain,NULL
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
这段代码是再简单不过的了,读者只需要注意两点。首先是程序开头的包含语句:
include Counter.inc
includelib Counter.lib
第二点就是WM_COMMAND消息中调用_IncCount和_DecCount函数的方法,毋需多言,方法和使用Windows提供的DLL时是一模一样的。
那么,这种方法有什么缺点呢?先来做几个实验。首先,将Counter.dll文件删除,再执行UseDll1.exe文件,这时对话框并没有被显示,系统弹出如图11.2所示的错误提示框。
图11.2 丢失Dll文件时的错误信息
这说明不管程序要用到哪几个dll文件,如果丢失任何一个dll文件的话(UseDll1.exe文件中还用到了Kernel32.dll文件和User32.dll文件,它们并没有丢失),可执行文件将无法被装入执行。
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.1 动态链接库(5)
invoke GetDlgItem,hWnd,IDC_INC
invoke EnableWindow,eax,FALSE
invoke GetDlgItem,hWnd,IDC_DEC
invoke EnableWindow,eax,FALSE
.endif
;********************************************************************
.elseif eax == WM_COMMAND
mov eax,wParam
.if ax == IDC_INC
.if lpIncCount
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -