查看單個文章
舊 2004-03-11, 11:40 AM   #1
psac
榮譽會員
 
psac 的頭像
榮譽勳章
UID - 3662
在線等級: 級別:30 | 在線時長:1048小時 | 升級還需:37小時級別:30 | 在線時長:1048小時 | 升級還需:37小時級別:30 | 在線時長:1048小時 | 升級還需:37小時級別:30 | 在線時長:1048小時 | 升級還需:37小時級別:30 | 在線時長:1048小時 | 升級還需:37小時
註冊日期: 2002-12-07
住址: 木柵市立動物園
文章: 17381
現金: 5253 金幣
資產: 33853 金幣
預設 Win32環境下動態鏈接庫(DLL)編程原理

作者:李欣

提交者:eastvc 發佈日期:2003-12-10 14:08:40
原文出處:http://www.swm.com.cn


比較大應用程序都由很多模塊組成,這些模塊分別完成相對獨立的功能,它們彼此協作來完成整個軟體系統的工作。其中可能存在一些模塊的功能較為通用,在構造其它軟體系統時仍會被使用。在構造軟體系統時,如果將所有模塊的源程式碼都靜態編譯到整個應用程序EXE檔案中,會產生一些問題:一個缺點是增加了應用程序的大小,它會佔用更多的磁碟空間,程序執行時也會消耗較大的記憶體空間,造成系統資源的浪費;另一個缺點是,在編寫大的EXE程序時,在每次修改重建時都必須調整編譯所有源程式碼,增加了編譯過程的複雜性,也不利於階段性的單元測試。

Windows系統平台上提供了一種完全不同的較有效的編程和執行環境,你可以將獨立的程序模塊新建為較小的DLL(Dynamic Linkable Library)檔案,並可對它們單獨編譯和測試。在執行時,只有當EXE程序確實要使用這些DLL模塊的情況下,系統才會將它們裝載到記憶體空間中。這種方式不僅減少了EXE檔案的大小和對記憶體空間的需求,而且使這些DLL模塊可以同時被多個應用程序使用。Microsoft Windows自己就將一些主要的系統功能以DLL模塊的形式實現。例如IE中的一些基本功能就是由DLL檔案實現的,它可以被其它應用程序使用和整合。

一般來說,DLL是一種磁碟檔案(通常帶有DLL擴展名),它由全局資料、服務函數和資源組成,在執行時被系統載入執行到行程的虛擬空間中,成為使用行程的一部分。如果與其它DLL之間沒有衝突,該檔案通常映射到行程虛擬空間的同一地址上。DLL模塊中包含各種匯出函數,用於向外界提供服務。Windows在載入執行DLL模塊時將行程函數使用與DLL檔案的匯出函數相匹配。
在Win32環境中,每個行程都複製了自己的讀/寫全局變量。如果想要與其它行程共享記憶體,必須使用記憶體映射檔案或者聲明一個共享資料段。DLL模塊需要的堆棧記憶體都是從執行行程的堆棧中分配出來的。
DLL現在越來越容易編寫。Win32已經大大簡化了其編程模式,並有許多來自AppWizard和MFC類庫的支持。

一、匯出和匯入函數的匹配

DLL檔案中包含一個匯出函數表。這些匯出函數由它們的符號名和稱為標誌號的整數與外界聯繫起來。函數表中還包含了DLL中函數的地址。當應用程序載入執行DLL模塊時時,它並不知道使用函數的實際地址,但它知道函數的符號名和標誌號。動態鏈接過程在載入執行的DLL模塊時動態建立一個函數使用與函數地址的對應表。如果重新編譯和重建DLL檔案,並不需要修改應用程序,除非你改變了匯出函數的符號名和參數序列。
簡單的DLL檔案只為應用程序提供匯出函數,比較複雜的DLL檔案除了提供匯出函數以外,還使用其它DLL檔案中的函數。這樣,一個特殊的DLL可以既有匯入函數,又有匯入函數。這並不是一個問題,因為動態鏈接過程可以處理交叉相關的情況。
在DLL代碼中,必須像下面這樣明確聲明匯出函數:
__declspec(dllexport) int MyFunction(int n);
但也可以在模塊定義(DEF)檔案中列出匯出函數,不過這樣做常常引起更多的麻煩。在應用程序方面,要求像下面這樣明確聲明相應的輸入函數:
__declspec(dllimport) int MyFuncition(int n);
僅有匯入和匯出聲明並不能使應用程序內部的函數使用鏈接到相應的DLL檔案上。應用程序的項目必須為鏈接程序指定所需的輸入庫(LIB檔案)。而且應用程序事實上必須至少包含一個對DLL函數的使用。

二、與DLL模塊建立鏈接

應用程序匯入函數與DLL檔案中的匯出函數進行鏈接有兩種方式:隱式鏈接和顯式鏈接。所謂的隱式鏈接是指在應用程序中不需指明DLL檔案的實際存儲路徑,程序員不需關心DLL檔案的實際裝載。而顯式鏈接與此相反。
採用隱式鏈接方式,程序員在建立一個DLL檔案時,鏈接程序會自動產生一個與之對應的LIB匯入檔案。該檔案包含了每一個DLL匯出函數的符號名和可選的標誌號,但是並不含有實際的代碼。LIB檔案作為DLL的替代檔案被編譯到應用程序項目中。當程序員通過靜態鏈接方式編譯產生應用程序時,應用程序中的使用函數與LIB檔案中匯出符號相匹配,這些符號或標誌號進入到產生的EXE檔案中。LIB檔案中也包含了對應的DLL檔案名(但不是完全的路徑名),鏈接程序將其存儲在EXE檔案內部。當應用程序執行過程中需要載入執行DLL檔案時,Windows根據這些訊息發現並載入執行DLL,然後通過符號名或標誌號實現對DLL函數的動態鏈接。
顯式鏈接方式對於整合化的開發語言(例如VB)比較適合。有了顯式鏈接,程序員就不必再使用匯入檔案,而是直接使用Win32 的LoadLibary函數,並指定DLL的路徑作為參數。LoadLibary返回HINSTANCE參數,應用程序在使用GetProcAddress函數時使用這一參數。GetProcAddress函數將符號名或標誌號轉換為DLL內部的地址。假設有一個匯出如下函數的DLL檔案:
extern "C" __declspec(dllexport) double SquareRoot(double d);
下面是應用程序對該匯出函數的顯式鏈接的例子:
typedef double(SQRTPROC)(double);
HINSTANCE hInstance;
SQRTPROC* pFunction;
VERIFY(hInstance=::LoadLibrary("c:\\winnt\\system32\\mydll.dll"));
VERIFY(pFunction=(SQRTPROC*)::GetProcAddress(hInstance,"SquareRoot"));
double d=(*pFunction)(81.0);//使用該DLL函數
在隱式鏈接方式中,所有被應用程序使用的DLL檔案都會在應用程序EXE檔案載入執行時被載入執行在到記憶體中;但如果採用顯式鏈接方式,程序員可以決定DLL檔案何時載入執行或不載入執行。顯式鏈接在執行時決定載入執行哪個DLL檔案。例如,可以將一個帶有字元串資源的DLL模塊以英語載入執行,而另一個以西班牙語載入執行。應用程序在用戶選擇了合適的語種後再載入執行與之對應的DLL檔案。

三、使用符號名鏈接與標誌號鏈接

在Win16環境中,符號名鏈接效率較低,所有那時標誌號鏈接是主要的鏈接方式。在Win32環境中,符號名鏈接的效率得到了改善。Microsoft現在推薦使用符號名鏈接。但在MFC庫中的DLL版本仍然採用的是標誌號鏈接。一個典型的MFC程序可能會鏈接到數百個MFC DLL函數上。採用標誌號鏈接的應用程序的EXE檔案體相對較小,因為它不必包含匯入函數的長字元串符號名。

四、編寫DllMain函數

DllMain函數是DLL模塊的預設值入口點。當Windows載入執行DLL模塊時使用這一函數。系統首先使用全局對象的構造函數,然後使用全局函數DLLMain。DLLMain函數不僅在將DLL鏈接載入執行到行程時被使用,在DLL模塊與行程分離時(以及其它時候)也被使用。下面是一個框架DLLMain函數的例子。
HINSTANCE g_hInstance;
extern "C" int APIENTRY DllMain(HINSTANCE hInstance,DWORD dwReason,LPVOID lpReserved)
{
if(dwReason==DLL_PROCESS_ATTACH)
{
TRACE0("EX22A.DLL Initializing!\n");
//在這裡進行初始化
}
else if(dwReason=DLL_PROCESS_DETACH)
{
TRACE0("EX22A.DLL Terminating!\n");
//在這裡進行清除工作
}
return 1;//成功
}
如果程序員沒有為DLL模塊編寫一個DLLMain函數,系統會從其它執行庫中引入一個不做任何操作的預設值DLLMain函數版本。在單個線程啟動和終止時,DLLMain函數也被使用。正如由dwReason參數所表明的那樣。

五、模塊句柄

行程中的每個DLL模塊被全局唯一的32字節的HINSTANCE句柄標誌。行程自己還有一個HINSTANCE句柄。所有這些模塊句柄都只有在特定的行程內部有效,它們代表了DLL或EXE模塊在行程虛擬空間中的起始地址。在Win32中,HINSTANCE和HMODULE的值是相同的,這個兩種類型可以替換使用。行程模塊句柄幾乎總是等於0x400000,而DLL模塊的載入執行地址的預設值句柄是0x10000000。如果程序同時使用了幾個DLL模塊,每一個都會有不同的HINSTANCE值。這是因為在新建DLL檔案時指定了不同的基地址,或者是因為載入執行程序對DLL代碼進行了重定位。
模塊句柄對於載入執行資源特別重要。Win32 的FindResource函數中帶有一個HINSTANCE參數。EXE和DLL都有其自己的資源。如果應用程序需要來自於DLL的資源,就將此參數指定為DLL的模塊句柄。如果需要EXE檔案中包含的資源,就指定EXE的模塊句柄。
但是在使用這些句柄之前存在一個問題,你怎樣得到它們呢?如果需要得到EXE模塊句柄,使用帶有Null參數的Win32函數GetModuleHandle;如果需要DLL模塊句柄,就使用以DLL檔案名為參數的Win32函數GetModuleHandle。

六、應用程序怎樣找到DLL檔案

如果應用程序使用LoadLibrary顯式鏈接,那麼在這個函數的參數中可以指定DLL檔案的完整路徑。如果不指定路徑,或是進行隱式鏈接,Windows將遵循下面的搜索順序來定位DLL:
1. 包含EXE檔案的目錄,
2. 行程的當前工作目錄,
3. Windows系統目錄,
4. Windows目錄,
5. 列在Path環境變量中的一系列目錄。
這裡有一個很容易發生錯誤的陷阱。如果你使用VC++進行項目開發,並且為DLL模塊專門新建了一個項目,然後將產生的DLL檔案拷貝到系統目錄下,從應用程序中使用DLL模塊。到目前為止,一切正常。接下來對DLL模塊做了一些修改後重新產生了新的DLL檔案,但你忘記將新的DLL檔案拷貝到系統目錄下。下一次當你執行應用程序時,它仍載入執行了老版本的DLL檔案,這可要當心!

七、調試DLL程序

Microsoft 的VC++是開發和測試DLL的有效工具,只需從DLL項目中執行調試程序即可。當你第一次這樣操作時,調試程序會向你詢問EXE檔案的路徑。此後每次在調試程序中執行DLL時,調試程序會自動載入執行該EXE檔案。然後該EXE檔案用上面的搜索序列發現DLL檔案,這意味著你必須設置Path環境變量讓其包含DLL檔案的磁碟路徑,或者也可以將DLL檔案拷貝到搜索序列中的目錄路徑下。




最新評論 [發表評論] [文章投稿] 檢視所有評論 推薦給好友 列印

全是vc技術內幕上抄來的~~~~~~ ( VCInSidE 發表於 2003-12-23 9:23:00)


我全是從VC知識庫抄來的,希望喜歡
http://www.vckbase.com/code/winsys/dll/CallDllFunc.zip
psac 目前離線  
送花文章: 3, 收花文章: 1631 篇, 收花: 3205 次