動態連結庫(轉)

heying1229發表於2007-07-28
動態連結庫:

  本課中,我們將學習DLLs,它們到底是什麼和如何建立它們。

理論:
如果您程式設計的時間非常長,就會發現很多的程式之間其實有相當多的重複程式碼。每編一個程式就重寫一遍這些程式碼既沒必要又浪費時間。在DOS時代,一般的做法是把這些重複的程式碼寫成一個個的函式,然後把它們按類別放到不同的庫檔案中去。當要使用這些函式時,只要把您的目標檔案(.obj)檔案和先前存放在庫檔案中的函式進行連結,連結時連結器會從庫檔案中抽取相關的資訊並把它們插入到可執行檔案中去。這個過程叫做靜態連結。C執行時庫就是一個好例子。這樣的庫的缺點是您在每一個呼叫庫函式的程式中都必須嵌入同一函式的複製,這顯然很浪費磁碟。在DOS時代畢竟每一時刻僅有一個程式在執行,所以浪費的還只是磁碟而已,在多工的WINDOWS時代就不僅浪費磁碟,還要浪費寶貴的記憶體了。


在WINDOWS中,由於有多個程式同時執行,如果您的程式非常大的話,那將消耗相當多的記憶體。WINDOWS的解決辦法是:使用動態連結庫。動態連結庫從表面上看也是一大堆的通用函式,不過即使有多個程式呼叫了它,在記憶體中也僅僅只有動態連結庫的唯一一份複製。WINDOWS是透過分頁機制來作到這一點的。當然,庫的程式碼只有一份,但是每一個應用程式要有自己單獨的資料段,要麼就會亂掉。
不象舊時的靜態連結庫,它並不會把這些函式的可執行程式碼放入到應用程式中去,而是當程式已經在記憶體中執行時,如果需要呼叫該函式時才調入記憶體也即連結。這也就是為什麼把它叫做“動態”的原因所在。另外您還可以動態地解除安裝動態連結庫,當然要求這時沒有其它的應用程式在使用它,否則就要一直等到最後一個使用它的函式也不再使用該動態連結庫時才能去解除安裝它。
為了正確的呼叫庫和給庫函式分配記憶體空間,在編譯和連結應用程式時,必須把重定位等一些訊息插入到執行程式碼中去,以便載入正確的庫,並給庫函式分配正確的地址。
那麼這些資訊從哪裡得到呢?引入庫。引入庫包含足夠的資訊,連結器從中抽取足夠的資訊(注意區別:靜態連結庫放入的是可執行程式碼)把它們放入到可執行檔案中去。當WINDOWS的載入器裝入應用程式檢視到有DLL時,它會查詢該庫檔案,如果沒有查到,就報錯退出,否則就把它對映進程式的地址空間,並修正函式呼叫語句的地址。
如果沒有引入庫呢?當然我們也可以呼叫動態連結庫中的任意函式。只不過您必須知道呼叫的函式是否在庫中而且是否在庫的引出名字表中,另外還需要知道該函式的引數個數和引數的型別。

(譯者加:說到這裡,讓我想起了一件很有名的事。<>一書的作者Angel Schudleman 曾經利用此方法來跟蹤微軟Win3x系統動態連結庫中未公開的函式,因為在微軟給程式設計師提供的系統動態連結庫的引入庫中沒有提供這些函式的原型,所以您無法在連結時把這些函式的資訊連結到可執行檔案中去,而為了某種目的您又要使用這些函式,您就可以在執行時載入動態連結庫並得到這些函式的地址,從而和呼叫其它的庫函式一樣使用這些未公開的函式。由於這本書的巨大影響,當時許多程式設計師紛紛在它們的程式中呼叫未公開函式,甚至在寫商業程式時也這麼做。這種走偏峰的做法引起了微軟的反感,後來微軟在它Win3x的改進版中不再把那些未公開函式列入系統動態連結庫的引出名字表,這樣也就無法再利用這種方法來呼叫未公開的函式了。)

當您讓系統的載入器為您載入動態庫時,如果不能找到庫檔案,它就會提示一條“A required .DLL file, xxxxx.dll is missing”,這樣您的應用程式就無法執行,即使該庫對您的應用程式來說並不重要。
如果您選擇在程式執行時自己載入該庫,就沒有這種問題了。
如果您知道足夠的資訊,就可以呼叫系統未公開的函式。
如果您呼叫LoadLibrary函式載入庫,就必須再呼叫GetProcAddress函式來得到每一個您想呼叫的函式的地址,GetProcAddress會在動態連結庫中查詢函式的入口地址。由於多餘的步驟,這樣您的程式執行起來會慢一點,但是並不明顯。
明白了LoadLibrary函式的優缺點,下面我們就來看看如何產生一個動態連結庫。下面的程式碼是一個動態連結庫的框架:
;--------------------------------------------------------------------------------------
; DLLSkeleton.asm
;--------------------------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include masm32includewindows.inc
include masm32includeuser32.inc
include masm32includekernel32.inc
includelib masm32libuser32.lib
includelib masm32libkernel32.lib

.data
.code
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
mov eax,TRUE
ret
DllEntry Endp
;---------------------------------------------------------------------------------------------------
;下面是一個空函式,您可以象下面一樣插入您的函式。
;----------------------------------------------------------------------------------------------------
TestFunction proc
ret
TestFunction endp

End DllEntry

;-------------------------------------------------------------------------------------
; DLLSkeleton.def
;-------------------------------------------------------------------------------------
LIBRARY DLLSkeleton
EXPORTS TestFunction

上面是一個動態連結庫的框架,每一個DLL必須有一個入口點函式,WINDOWS每一次在做下面的動作時會呼叫該入口點函式:

當動態連結庫被載入時
當動態連結庫解除安裝時
同一程式的執行緒生成時
同一程式的執行緒退出時
DllEntry proc hInstDLL:HINSTANCE, reason:DWORD, reserved1:DWORD
mov eax,TRUE
ret
DllEntry Endp

入口點函式的名稱無所謂只要您讓語句“END”中的函式名和前面的相同就可以了。該函式共有三個引數,只有前面兩個是重要的。
hInstDLL是該動態連結庫模組的控制程式碼。它和程式的例項控制程式碼不一樣。如果您以後要用,可以儲存它,因為以後再要獲得它不容易。
根據不同的時機,reason傳入的值可能是下面的四個值中的一個:

DLL_PROCESS_ATTACH 動態連結庫第一次插入程式的地址空間時。當傳入的引數是該值時,您可以做一些初始化的工作。
DLL_PROCESS_DETACH 動態連結庫從程式的地址空間卸出時。您可以在此做一些清理的工作。譬如:釋放記憶體等。
DLL_THREAD_ATTACH 新執行緒生成。
DLL_THREAD_DETACH 執行緒銷燬。
如果想要庫中的程式碼繼續執行,返回TRUE,否則返回FALSE,那樣動態連結庫就不會載入了。譬如:您想分配一塊記憶體,如果不成功的話就退出,這時您就可以返回FALSE。那樣動態連結庫就不會載入了。
您可以加入的函式,它們的位置並不重要,把它們放在入口點函式的前面或後面都可以。只是如果您想要它們能被其它的程式呼叫的話,就必須把它們的名字放到模組定義檔案(.def)中去。
動態連結庫在它們自己的編譯過程就需要,而不只是提供給其它要引用它的程式參考。他們如下:

LIBRARY DLLSkeleton
EXPORTS TestFunction

第一行是必須的。LIBRARY 定義了DLL的模組名稱。它必須和動態連結庫的名稱相同。
EXPORTS關鍵字告訴連結器該DLL的引出函式,也就是其它程式可以呼叫的函式。舉個例子:其它的程式想要呼叫函式TestFunction ,我們就把它放到EXPORTS中。
還有就是,連結器的選項中必須放入開關項:/DLL 和/DEF,就像下面這樣:

link /DLL /SUBSYSTEM:WINDOWS /DEF:DLLSkeleton.def /LIBPATH:c:masm32lib DLLSkeleton.obj

編譯器的開關選項是一樣的,即:/c /coff /Cp。在您連結好後,連結器會生成.lib 和.dll檔案。前者是引入庫,當其它的程式要呼叫您的動態連結庫中的函式時就需要該引入庫,以便把必要的資訊加入到其可執行檔案中去。
接下來我們來看看如何使用LoadLibrary函式來載入一個DLL。

;---------------------------------------------------------------------------------------------
; UseDLL.asm
;----------------------------------------------------------------------------------------------
.386
.model flat,stdcall
option casemap:none
include masm32includewindows.inc
include masm32includeuser32.inc
include masm32includekernel32.inc
includelib masm32libkernel32.lib
includelib masm32libuser32.lib

.data
LibName db "DLLSkeleton.dll",0
FunctionName db "TestHello",0
DllNotFound db "Cannot load library",0
AppName db "Load Library",0
FunctionNotFound db "TestHello function not found",0

.data?
hLib dd ? ; 動態連結庫的控制程式碼 (DLL)
TestHelloAddr dd ? ; TestHello 函式的地址

.code
start:
invoke LoadLibrary,addr LibName
;---------------------------------------------------------------------------------------------------------
; 呼叫LoadLibrary,其引數是欲載入的動態連結庫的名稱。如果呼叫成功,將返回該DLL的控制程式碼。 否則返回NULL。該控制程式碼可以傳給 :library函式和其它需要動態連結庫控制程式碼的函式。
;-----------------------------------------------------------------------------------------------------------
.if eax==NULL
invoke MessageBox,NULL,addr DllNotFound,addr AppName,MB_OK
.else
mov hLib,eax
invoke GetProcAddress,hLib,addr FunctionName
;-----------------------------------------------------------------------------------------------------------
; 當您得到了動態連結庫的控制程式碼後,把它傳給GetProcAddress函式,再把您要呼叫的函式的名稱 也傳給該函式。如果成功的話,它:會返回想要的函式的地址,失敗的話返回NULL。除非解除安裝該 動態連結庫否則函式的地址是不會改變的,所以您可以把它儲存到一個:全域性變數中以備後用。
;-----------------------------------------------------------------------------------------------------------
.if eax==NULL
invoke MessageBox,NULL,addr FunctionNotFound,addr AppName,MB_OK
.else
mov TestHelloAddr,eax
call [TestHelloAddr]
;-----------------------------------------------------------------------------------------------------------
; 以後您就可以和呼叫其它函式一樣呼叫該函式了。其中要把包含函式地址資訊的變數用方括號括起來。
;-----------------------------------------------------------------------------------------------------------
.endif
invoke FreeLibrary,hLib
;-----------------------------------------------------------------------------------------------------------
;呼叫FreeLibrary解除安裝動態連結庫。
;-----------------------------------------------------------------------------------------------------------
.endif
invoke ExitProcess,NULL
end start

使用LoadLibrary函式載入動態連結庫,可能要自己多做一些工作,但是這種方法確實是提供了許多的靈活性。

[@more@]

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10172717/viewspace-928731/,如需轉載,請註明出處,否則將追究法律責任。

相關文章