動态連結庫一些基礎的概念:
動态連結庫縮寫為DLL。動态連結庫提供了許多的通用函數,可以被多個程式調用。動态連結庫,顧名思義,它隻有在程式執行的時候才會裝入到程式的位址空間中,程式不執行時就隻保留一些庫的資訊在檔案中。了解過PE檔案結構的應該知道,PE檔案不執行時,動态連結庫的資訊在PE檔案的導入表中,導入表裡面包含了這個PE檔案要使用的所有的動态連結庫的名稱以及要從庫中導入的函數的一些資訊。
了解過Windows的分頁記憶體機制應該知道,如果有多個程式用到同一個動态連結庫,Windows在實體空間中隻保留一份庫的代碼,系統通過分頁機制将這份代碼映射到不同的程序的位址空間中。因為Windows是一個分時系統,是以對于使用者DLL,當時間片切換到了要用這個DLL的程序時,該DLL在記憶體中的代碼就會被映射到這個程序的線性位址空間中。而對于系統DLL,因為所有程式都要用到,是以它會在所有時間片中都被映射。是以說,不管有多少程式正在使用DLL,庫代碼在實體記憶體中永遠隻有一份。但是庫使用的資料段還是會被映射到不同的實體記憶體中,多少給程式在使用動态連結庫就會有多少份資料段。
注意:動态連結庫是被映射到其他應用程式的線性位址空間中執行的,它和應用程式可以看成是一體的,DLL可以使用應用程式的資源,它所擁有的資源也可以被應用程式使用,它所做的任何動作都是代表應用程式進行的,如果動态連結庫進行打開檔案,配置設定資源等操作,這些檔案,資源都是歸應用程式所擁有。
編寫動态連結庫:
我們先看一段動态連結庫的源碼:
.386
.model flat,stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
dwCounter dd ?
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;dll的入口函數
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
DllEntry proc _hInstance,_dwReason,_dwReserved
mov eax,TRUE
ret
DllEntry endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;私有函數。将數值限制在0到10之間
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_CheckCounter proc
mov eax,dwCounter
cmp eax,0
jge @F
xor eax,eax
@@:
cmp eax,100
jle @F
mov eax,100
@@:
mov dwCounter,eax
ret
_CheckCounter endp ;如果大于等于0,則保持原來的數字不變,如果大于十則将十填入,小于0則将0填入
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;dll的導入函數之一,資料自加一,且範圍在0到10以内
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_IncCounter proc
inc dwCounter
call _CheckCounter
ret
_IncCounter endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;dll的導入函數之二,資料自減一,且範圍在0到10以内
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_DecCounter proc
dec dwCounter
call _CheckCounter
ret
_DecCounter endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;dll的導入函數之三,取模運算
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Mod proc uses ecx edx _dwNumber1,_dwNumber2
xor edx,edx
mov eax,_dwNumber1
mov ecx,_dwNumber2
.if ecx
div ecx
mov eax,edx
.endif
ret
_Mod endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
End DllEntry
乍一看好像和平常的彙編源代碼沒有差別,但是細看就會發現,它沒有了start和end start 。而是換成了一個 End DllEntry,并且還加了一個入口函數。其他三個函數就是DLL的導出函數,導出函數就和我們平常自定義的函數一樣寫就行。
和可執行檔案一樣,動态連結庫需要一個入口點,但是它的入口點是一個函數,函數的名字可以随意取,隻要合法就行,但是函數的格式有規定。
入口函數的結構一般如下所示:
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
函數的第一個參數是該動态連結庫的句柄,因為這個入口函數對調用該DLL的程式時不可見的,入口函數是供系統調用的,系統在裝載,解除安裝動态連結庫,以及程序中線程的建立和結束時都會調用這個入口函數。是以要在動态連結庫裡面獲得它自己的DLL的話,就隻能在這裡獲得了。
第二個參數是調用的原因,從上面代碼可以看到原因有4種。
- DLL_PROCESS_ATTACH:這個原因表示動态連結庫剛剛被映射到程序的位址空間,這裡可以做一些初始化的工作。傳回TRUE表示初始化成功,傳回FALSE表示初始化失敗,這樣庫的裝入也會失敗。
- DLL_THREAD_ATTACH:這個原因表示程序建立了一個新的線程,這樣的話,就要為那個線程配置設定資源。
- DLL_THREAD_DETACH:這個原因表示要結束一個線程,是以要釋放資源
- DLL_PROCESS_DETACH:這個原因表示動态連結庫要被解除安裝了。
最後一個參數是系統保留參數,可以不加理會。
但是在我們的例子程式中,隻傳回應該一個true表示裝入成功就可以了,因為我們也沒有什麼好初始化的。
編寫動态連結庫光有這個源檔案還不夠,我們還需要一個指出導出函數的檔案,這個就是定義檔案。比如,如果我們上面編寫的那個DLL裡面最後三個函數都導出的話,定義檔案可以這樣寫:
EXPORTS _IncCounter
_DecCounter
_Mod
沒錯,就是這麼一點,需要導出什麼函數就把函數名寫在EXPORTS後面就行了。而我們在庫的代碼中有寫的函數但是沒有在這裡定義的的話,那麼這個函數就是DLL的私有函數,對應用程式是不可見的。
生成應該動态連結庫需要就是這兩個檔案,然後和之前編譯連結彙編源代碼成EXE檔案不同的是連接配接選項,編譯選項還是一樣的。連結DLL的完整的連結選項:
Link /DLL /subsystem:windows /Def:filename.def filename.obj filename.res
DLL檔案中也可以包含資源。
然後為了使用NMAKE,這裡還是給出完整的makefile檔案:
DLL = Sample
LINK_FLAG =/DLL /subsystem:windows
ML_FLAG = /c /coff
$(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
好,有了這三個檔案,我們就可以直接用NAMKE編譯連結了。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICM38FdsYkRGZkRG9lcvx2bjxiNx8VZ6l2cs0TPR9UMnRlTzUleNBDOsJGcohVYsR2MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL0IjM0ETN1gTMzIzMwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
編譯連結完了之後會發現檔案夾下多了好幾個檔案:
原來檔案中就隻有Makefile,Sample.asm,Sample.def這三個檔案。那麼多出來的那幾個檔案是什麼呢?
首先多出來的DLL檔案就不用說什麼了,這個就是我們最後要得到的動态連結庫檔案,然後下面還有個EXP檔案是什麼?這個其實就是一個連接配接過程中的副産品,叫輸出庫檔案,沒什麼用,可以删掉。再下面是一個LIB檔案,這個檔案就很熟悉了,這個是一個導入庫檔案,我們編寫程式的時候要導入什麼DLL的話一般都是 :includelib xxxx.lib 。隻有當我們指定了這個導入庫檔案,程式才知道到哪個庫中尋找特定的函數。這個導入庫檔案是連結過程中自動幫我們生成的。最下面那個就不用多說了,就是編譯後産生的OBJ檔案。
DLL檔案已經生成了,但是我們要在程式裡面去調用的話這還不夠。我們還需要編寫一個INC檔案,這個就是供程式使用的頭檔案,在程式中包含這個檔案後我們就不需要再寫函數聲明了。
;***********************************************************************************
;作者: XXX
;E-mail: [email protected]
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;Version 1.0
; Data: 2020.01.31
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;Sample.dll 導出函數
;
;1、invoke _IncCounter
; 增加 DLL 内部計數器的值(最大到10)并傳回計數
;2、invoke _DecCounter
; 減少DLL内部計數器的值(最小到0)并傳回計數
;3、invoke _Mod,dwNumber1,dwNumber2
; 輸入:dwNumber1 和 dwNumber2 為兩個整數
; 輸出: 兩個數的模 dwNumber1 % dwNumber2
;**********************************************************************************
_IncCounter proto
_DecCounter proto
_Mod proto _dwNumber1:dword,_dwNumber2:dword
裡面有用的部分也就是最後面三行,就是聲明一下函數。如果隻是自己用的話,前面那些東西就不用寫了。
最後我們寫個程式來調用這個DLL看看:
下面是資源檔案:
#include <resource.h>
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#define ICO_MAIN 0x100
#define DLG_MAIN 0x1000
#define IDC_COUNT 0x1001
#define IDC_INC 0x1002
#define IDC_DEC 0x1003
#define IDC_NUM1 0x1004
#define IDC_NUM2 0x1005
#define IDC_MOD 0x1006
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN ICON "Main.ico"
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
DLG_MAIN DIALOG 186,132,173,79
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "自己寫的DLL"
FONT 9,"宋體"
BEGIN
GROUPBOX "Dll内部計數器",-1,4,2,164,32,BS_GROUPBOX
EDITTEXT IDC_COUNT,10,15,73,12,ES_READONLY
PUSHBUTTON "增加(&A)",IDC_INC,86,13,36,14
PUSHBUTTON "減少(&D)",IDC_DEC,123,13,37,14
GROUPBOX "取模函數測試",-1,4,37,164,32,BS_GROUPBOX
EDITTEXT IDC_NUM1,11,50,43,12,ES_NUMBER
LTEXT "%",-1,57,52,8,8
EDITTEXT IDC_NUM2,64,50,43,12,ES_NUMBER
LTEXT "=",-1,110,52,8,8
EDITTEXT IDC_MOD,117,50,43,12,ES_READONLY
END
下面是彙編源檔案:
.386
.model flat,stdcall
option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;Include檔案
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
include Sample.inc
includelib Sample.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;Equ
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN equ 100h
DLG_MAIN equ 1000h
IDC_COUNT equ 1001h
IDC_INC equ 1002h
IDC_DEC equ 1003h
IDC_NUM1 equ 1004h
IDC_NUM2 equ 1005h
IDC_MOD equ 1006h
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;代碼段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
hInstance dd ?
.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_INITDIALOG
invoke LoadIcon,hInstance,ICO_MAIN
invoke SendMessage,hWnd,WM_SETICON,ICON_BIG,eax
.elseif eax == WM_COMMAND
mov eax,wParam
.if ax == IDC_INC
invoke _IncCounter
invoke SetDlgItemInt,hWnd,IDC_COUNT,eax,FALSE
.elseif ax == IDC_DEC
invoke _DecCounter
invoke SetDlgItemInt,hWnd,IDC_COUNT,eax,FALSE
.elseif ax == IDC_NUM1 || ax == IDC_NUM2
invoke GetDlgItemInt,hWnd,IDC_NUM1,NULL,FALSE
push eax
invoke GetDlgItemInt,hWnd,IDC_NUM2,NULL,FALSE
pop ecx
invoke _Mod,ecx,eax
invoke SetDlgItemInt,hWnd,IDC_MOD,eax,FALSE
.endif
.else
mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
_ProcDlgMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke DialogBoxParam,eax,DLG_MAIN,\
NULL,offset _ProcDlgMain,NULL
invoke ExitProcess,NULL
end start
程式很簡單,就是分别調用了DLL裡面的三個函數,然後把結果顯示再對話框中。來看看程式運作的結果:
當然要注意要把DLL檔案與我們的程式放在一個目錄下面,不然就會像這樣:
系統查找DLL的順序:程式所在目錄,Windows系統目錄,PATH環境變量指定目錄。