天天看點

自己寫動态連結庫

動态連結庫一些基礎的概念:

動态連結庫縮寫為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編譯連結了。

自己寫動态連結庫

 編譯連結完了之後會發現檔案夾下多了好幾個檔案:

自己寫動态連結庫

 原來檔案中就隻有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環境變量指定目錄。