標籤:c style class blog code java
關於DLL的消極式載入
消極式載入DLL,使用的是隱式載入方式,當為exe使用的DLL指定為消極式載入的時候,連接器會將exe的【匯入段】中去除該DLL的相關資訊,同時在exe中嵌入一個新的【消極式載入段】表示要從該DLL中匯入哪些函數。
通過讓對消極式載入函數的調用跳轉到delayimp.lib中的__delayLoadHelper2函數,來完成對消極式載入的DLL的解析。
當exe中第一次調用了一個消極式載入的DLL中的某個匯出函數時,載入器才會將該DLL載入到進程地址空間中。需要注意的是:雖然此時已經載入了DLL,但是未調用到的其他函數,還是需要在調用的時候才能被修複(修複即不再通過__delayLoadHelper2函數來解析)。
消極式載入的DLL也可以卸載,當再次調用該DLL中的函數時,再次載入DLL到進程地址空間。
消極式載入 及支援卸載DLL的步驟,以ADll.dll為例
①項目屬性--配置屬性--連結器--輸入--消極式載入的DLL
輸入DLL的名字,注意這裡:卸載的時候使用的DLL的名字不能包含路徑 並且 大小寫必須和這裡設定的一樣。
/DELAYLAOD:[DLLName]
②項目屬性--配置屬性--連結器--進階--消極式載入的DLL
選擇【支援卸載(/DELAY:UNLOAD)】
③載入DelayImp.lib靜態庫
__delayLoadHelper2函數和__FUnloadDelayLoadedDLL2函數都在這個靜態庫中。
#pragma comment(lib, "DelayImp.lib")
#include <delayimp.h>
④添加DLL的標頭檔 或者 添加DLL匯出函式宣告
extern "C" __declspec(dllimport) int __stdcall Add(int a, int b);
demo
void CUseADll2Dlg::OnBnClickedButton1(){ // TODO: 在此添加控制項通知處理常式代碼 __try { int sum = Add(10, 20); } __except(1) { AfxMessageBox(_T("異常")); }}void CUseADll2Dlg::OnBnClickedButton2(){ //檢測該DLL是否載入了 HINSTANCE hInst = GetModuleHandle(TEXT("ADll.dll"));}void CUseADll2Dlg::OnBnClickedButton3(){ //參數DLL的名字不能包含路徑,並且 大小寫要和/DELAYLOAD設定的一致 BOOL bRet = __FUnloadDelayLoadedDLL2("ADll.dll");}
函數轉寄站
函數轉寄站是DLL【輸出段】中的一個條目,用來將一個函數調用【轉寄】到另一個DLL中的另一個函數。
可用【dumpbin -exports XX.dll】查看
在DLL中設定函數轉寄站:
#pragma comment(linker, "/export:本DLL匯出函數=其他DLL.函數名")
e.g.
#pragma comment(linker, "/export:Add=SecondDll.Sum")
將本DLL中的匯出函數Add【轉寄】到另一個名為SecondDll的DLL中函數Sum。
經過實驗,發現這裡需要特別注意:#pragma來表示轉寄的函數名一定是經過name manling改變後的!
如下:
//DLL1中的匯出函數,Add#pragma comment(linker, "/export:[email protected][email protected]")extern "C" __declspec(dllexport) int __stdcall Add(int a, int b);
//DLL2中的匯出函數Sumextern "C" __declspec(dllexport) int __stdcall Sum(int a, int b){ return a + b;}
一開始,當我試圖這樣寫時:#pragma comment(lib, "exports:Add=Dll2.Sum)
看起來好像沒有問題,可實際上,GetProcAddress總是失敗的,經過測試,發現必須使用經過編譯器name manling改變過後的名字,從depends查看如下
所以必須使用改變過後的名字才可以,這一點也同樣體現在GetProcAddress中,如下
HINSTANCE hInst = LoadLibrary(TEXT("Dll1.dll")); if (NULL == hInst) { AfxMessageBox(_T("LoadLibrary 失敗")); } typedef int (__stdcall * PUNC)(int, int); //必須加上__stdcall PUNC pfnAdd = (PUNC)GetProcAddress(hInst, "[email protected]"); //這裡的名字也必須是改編後的 if (NULL == pfnAdd) { AfxMessageBox(_T("GetProcAddress失敗")); return; } int sum = pfnAdd(100, 400); char chBuffer[100] = {0}; sprintf_s(chBuffer, "result is %d", sum); AfxMessageBox(CString(chBuffer));
另外,如果想要使用未經改變的名字,那麼可以在VS編譯器下使用
extern "C" __declspec(dllexport) int __cdecl Add(int a, int b);
使用extern "C" 和 __cdecl呼叫慣例組合方式,在VS下的名字就是Add。
結論:
①DLL函數轉寄時,轉寄到的函數也必須是匯出函數,兩者的呼叫慣例以及名字改編方式最好一致
②在設定轉寄時,必須使用經過改編後的名字
③在定義匯出函數的函數指標時,要加上呼叫慣例,並且GetProcAddress()函數也要用改編後的名字
已知DLL
在註冊表HKLM\SYSTEM\CurrentControlSet\Control\Session Manager\KnownDlls中列出的那些
在使用LoadLibrary或LoadLibraryEx時,參數中的DLL是否帶.dll尾碼導致的不同情況:
①帶.dll尾碼
首先去掉尾碼,比如LoadLibrary("xx.dll"),首先去掉尾碼,然後去上述註冊表路徑下尋找【名稱】為"xx"的項目,然後找到其對應的【資料】,即帶.dll尾碼的dll名字(這個名字可能和LoadLibrary時使用的不一樣),如果在註冊表中找不到該【名稱】為"xx"的項目,則再去搜尋路徑依次尋找"xx.dll", 如果在註冊表中找到了該DLL名字,則去%systemroot%\sytstem32目錄中去尋找該xx.dll,系統會並且只會該目錄下尋找該xx.dll,如果找到了則載入進進程地址空間,如果找不到則失敗返回。
②不帶.dll尾碼
按照正常搜尋路徑搜尋DLL