close

來源: Mars

 

介紹一點重要的背景知識:

所有的Win32API函數都包含在DLL中。三個最重要的DLL是:KERNEL32.DLL(它由管理內存、進程和線程的函數組成),USER32.DLL(它由執行用戶界面任務(如創建窗口和發送消息)的函數組成),GDI32.DLL(它由繪圖和顯示文本的函數組成)。另外還有一些執行專門功能的DLL,例如:ADVAPI32.DLL(包含有關對象安全、註冊表管理和事件記錄的函數);COMDLG32.DLL(包含了通用對話框,FileOpen、FileSave等);LZ32.DLL(包含文件解壓縮函數)

13.1 創建動態鏈接庫

DLL中通常沒有處理消息循環的代碼和創建窗口的代碼。

應用程序要呼叫DLL中的函數,必須先把DLL的文件映射到進程的地址空間裡。

由 DLL中代碼創建的對象都歸呼叫線程或進程所有,DLL在Win32中什麼也不擁有。例如:DLL中的函數呼叫了VirtualAlloc,保留的地址空 間區域在呼叫的線程的進程的地址空間中。如果後來DLL從程序中釋放了,可是由DLL保留的這塊物理存儲並不釋放,只有當程序某個線程呼叫了 VirtualFree或是進程終結時,該保留區域才從內存中釋放。


13.1.1將DLL映射到進程的地址空間:可以使用兩種方法將DLL映射到進程的地址空間,一種是隱式連接,另一種是顯式裝入

1.隱式鏈接:

採用這種方法時,系統尋找DLL文件時,查找以下這幾處:

(1)包含EXE文件的目錄

(2)進程的當前目錄

(3)Windows系統目錄

(4)Windows目錄

(5)列在PATH環境變量中的目錄

如果這五處都找不到DLL,那麼系統會顯示一個消息框,之後終止整個進程。

採用這種方法映射的DLL,DLL在進程終結時才會被解除映射,也就是說在進程終結時才會被釋放。

2.顯式鏈接:

當進程中的線程呼叫LoadLibrary或LoadLibraryEx時可以顯示的映射DLL文件到進程的地址空間。顯式加載DLL後,可以在任意時刻用 FreeLibrary來釋放DLL在進程地址空間中的映射。如果同一進程中的線程使用LoadLibrary或LoadLibraryEx加載了相同的 DLL兩次以上,那麼在第一次加載DLL到進程的地址空間後,系統並不會再重複加載DLL,而只是增加對DLL的使用計數,同理在使用 FreeLibrary時,只是減少對DLL的使用計數,並一定要從進程的地址空間中釋放DLL。當系統看到一個DLL的使用計數為0時,就自動把該 DLL從進程的地址空間中釋放。如果是兩個不同的進程(進程A和進程B)顯示載入了相同的DLL,那麼進程A在載入DLL時,系統同時為該DLL的使用計 數設置為1,同理B的也是1,在進程A釋放DLL時,進程B中的DLL並不受任何影響,進程B中對DLL的使用計數還是1,而進程A中對該DLL的使用計 數變成0。註:顯示鏈接時,系統尋找DLL文件同隱式鏈接,不過函數LoadLibraryEx可以改變尋找的方式。

線程可以呼叫GetModuleHandle函數來判斷一個DLL是否被載入了進程的地址空間,

HINSTANCE GetModuleHandle(LPCTSTR lpszModuleName);

例子:

HINSTANCE hinstDLL;

hinstDLL = GetModuleHandle(「SomeDLL.dll」);

If (hinstDLL == NULL){

hinstDLL = LoadLibrary(「SomeDLL.dll」);

}

如果有了DLL的HINSTANCE值就可以使用GetModuleFileName來得到DLL的全路徑名,

DWORD GetModuleFileName(HINSTANCE hinstModule, LPTSTR lpszPath,DWORD cchPath);

參數說明:

hinstModule是DLL或EXE的HINSTANCE值,lpszPath是返回的DLL的全路徑名,cchPath指定緩衝區字符大小。


13.2 DLL的進入/退出函數

這些函數常常被DLL用來執行線程級或進程級的初始化或清理工作。

如果DLL中有這個進入/退出函數,那麼這個函數必須是下面這個形式的:

BOOL WINAPI DLLMain(HINSTANCE hinstDLL,DWORD fdwReason,LPVOID fImpLoad){

Switch(fdwReason){

Case DLL_PROCESS_ATTACH:

//當這個DLL被映射到了進程的地址空間時

break;

case DLL_THREAD_ATTACH:

//一個線程正在被創建

break;

case DLL_THREAD_DETACH:

//線程終結

break;

case DLL_PROCESS_DETACH:

//這個DLL從進程的地址空間中解除映射

break;

}

return(TRUE);

}

參數說明:

hinstDLL包含DLL句柄。該值表示DLL被映射到進程地址空間內的虛擬地址。

fImpLoad當隱式加載時該參數非零,當DLL被顯示加載時為零。

fdwReason:該參數指出系統為什麼呼叫該函數。該參數有四個可能值:DLL_PROCESS_ATTACH、DLL_PROCESS_DETACH、DLL_THREAD_ATTACH、DLL_THREAD_DETACH。下面分別說明這四個可能值的作用:

DLL_PROCESS_ATTACH:

當一個DLL被首次載入進程地址空間時,系統會呼叫該DLL的DLLMain函數,傳遞的參數fdwReason為 DLL_PROCESS_ATTACH。這種情況只有在首次映射DLL時才發生。當DLLMain處理DLL_PROCESS_ATTACH 時,DLLMain函數的返回值表示DLL的初始化是否成功。成功返回TRUE,否則返回FALSE。舉一個在DLL_PROCESS_ATTACH通知 中簡單的初始化例子:使用HeapCreate來創建一個DLL要使用的堆,當然這個堆是在進程的地址空間上的。(現在描述一下隱式載入DLL時都發生了什麼: 當新建一個進程時,系統為該進程分配了地址空間,之後將EXE文件和所需要的DLL文件都映射到進程的地址空間。之後系統創建了進程的主線程,並使用這個 主線程來呼叫每一個DLL中的DLLMain函數,傳遞給DLLMain函數的參數fdwReason的值是DLL_PROCESS_ATTACH。在所 有的DLL都響應了DLL_PROCESS_ATTACH通知後,系統讓進程的主線程執行EXE的C運行時啟動代碼,而後呼叫EXE文件的WinMain 函數。如果有一個DLL的DLLMain函數的返回值為FALSE,說明DLL的初始化沒有成功,系統就會終結整個進程,去掉所有文件映像,之後顯示一個 對話框告訴用戶進程不能啟動。再說明一下顯示載入DLL時都發生了什麼:首先線程(A)呼叫LoadLibrary或 LoadLibraryEx來載入一個DLL,之後系統讓線程A來呼叫DLL中的DLLMain函數,並傳遞參數fdwReason值為 DLL_PROCESS_ATTACH,當DLL中的DLLMain處理完DLL_PROCESS_ATTACH通知後,線程就會從 LoadLibrary返回,繼續執行線程中LoadLibrary下面的代碼。如果DLL中的DLLMain返回FALSE,說明初始化不成功,系統將 DLL自動解除映射,使用對LoadLibrary或LoadLibraryEx的呼叫返回NULL。)

DLL_PROCESS_DETACH:

當DLL從進程的地址空間解除映射時,參數fdwReason被傳遞的值為DLL_PROCESS_DETACH。當DLL處理 DLL_PROCESS_DETACH時,DLL應該處理與進程相關的清理操作。舉個例子:可以在DLL_PROCESS_DETACH階段使用 HeapDestroy來釋放在DLL_PROCESS_DETACH階段創建的堆。

如 果進程的終結是因為系統中有某個線程呼叫了TerminateProcess來終結的,那麼系統就不會用DLL_PROCESS_DETACH來呼叫 DLL中的DLLMain函數來執行進程的清理工作。這樣就會造成數據丟失。所以萬不得以不要使用TerminateProcess來終結進程。

DLL_THREAD_ATTACH:

該通知告訴所有的DLL執行線程的初始化。當進程創建一個新線程時,系統會查看進程地址空間中所有的DLL文件映射,之後用 DLL_THREAD_ATTACH來呼叫DLL中的DLLMain函數。要注意:系統不會為進程的主線程使用值DLL_THREAD_ATTACH來呼 叫DLL中的DLLMain函數。

DLL_THREAD_DETACH:

該通知告訴所有的DLL執行線程的清理工作。注意如果線程的終結是使用TerminateThread來完成的,那麼系統將不會使用值 DLL_THREAD_DETACH來執行線程的清理工作,這也就是說可能會造成數據丟失,所以萬不得已不要使用TerminateThread來終結線 程。

註:在編寫DLL時不必一定實現DLLMain函數,當DLL源碼中沒有DLLMain函數時,C運行時庫有它自己的一個DLLMain函數。


13.3 從DLL中輸出函數和變量

在C中輸出函數和變量的方法:

舉例子說明:如何輸出函數add和變量g_nUsageCount,

_declspec(dllexport) int Add(){

//函數Add的函數體

}

_declspec(dllexport) int g_nUsageCount = 0;

下 面說明一下從DLL中輸出函數和變量時鏈接器所要做的一些事:首先在鏈接器鏈接DLL時,鏈接器檢查到了關於輸出函數和變量的信息,之後鏈接器就自動產生 了一個包含DLL輸出符號的LIB文件,同時還在生成的DLL文件中嵌入了一個輸出符號表(這個表中包含DLL中要輸出的函數和變量的名字和在DLL中對應的地址)。


13.4 在EXE中使用DLL輸出的函數和變量

 

下面舉例子說明如何在EXE中呼叫從DLL中輸出的函數Add和變量g_nUsageCount,

_declspec(dllimport) int Add();

_declspec(dllimport) int g_nUsageCount;

線程可以使用GetProcAddress函數來得到從DLL中輸出的函數或變量的地址,

FARPROC GetProcAddress(HINSTANCE hinstDLL,LPCSTR lpszProc);

參數說明:hinstDLL是DLL的句柄,lpszProc可以是DLL中函數或變量的名字,也可以是對應DLL中函數或變量的序號。

舉例子:

GetProcAddress(hinst,」SomeDLLName」);//直接使用函數名

GetProcAddress(hinst,MakeIntResource(2));//使用函數的序號


13.5 在EXE或DLL的多個實例中共享數據

13.5.1 首先說明一下EXE或DLL中的節:


每一個EXE或DLL文件都是由節的集合組成的,每個節由「.」開始,例如:編譯器把所有代碼都放在.text節中。如下表所示為EXE或DLL中的常用節:

節名

內容

.text

應用程序或DLL的代碼

.bss

未初始化的數據

.rdata

只讀的運行時數據

.rsrc

資源

.edata

輸出名字表

.data

初始化的數據

.xdata

異常處理表

.idata

引入名字表

.CRT

只讀的C運行時數據

.reloc

修正表信息

.debug

調試信息

.tls

線程局部存儲

每個節都有如下訪問權限:

屬性

含義

READ

該節中的字節可讀取

WRITE

該節中的字節可寫入

EXECUTE

該節中的字節可執行

SHARED

該節中的字節可被多個實例共享

在代碼中使用節的方法舉例:

#pragma comment(linker,」/SECTION:SHARED,RWS」);//使節SHARED可以讀、寫、可共享。

arrow
arrow
    全站熱搜
    創作者介紹
    創作者 huenlil 的頭像
    huenlil

    H's 手札

    huenlil 發表在 痞客邦 留言(1) 人氣()