介紹一點重要的背景知識:
所有的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可以讀、寫、可共享。
2 comments:
受教了~有些中譯還真的很難理解
順便幫我介紹一下 opengl32.dll吧
哈
他被我搞爛了
快救我
請找google大神,這篇也是google到的,看書都不知道要去哪邊找=-=
Post a Comment