標籤:
和可執行檔一樣,動態連結程式庫也有自己的入口地址,如果系統或者當前進程的某個線程調用LoadLibrary函數載入或者使用FreeLibrary卸載該動態連結程式庫的時候,會自動使用3個特定的堆棧參數跳轉到該地址來運行。入口函數是為了完成動態連結程式庫代碼的初始化和善後工作,比如卸載後的資源釋放。
這三個參數具有特殊的含義。
BOOL APIENTRY DllMain(
HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
第一個參數是執行個體控制代碼,這個所謂的控制代碼實際上就是該載入進程在記憶體中的景象地址,注意進程控制代碼和動態連結程式庫的模組控制代碼是不一樣的。比如,如果使用者想使用hModule去載入該動態連結程式庫中的一個位元影像資源,那將永遠得不到結果。動態連結程式庫的模組控制代碼依附於載入進程,脫離進程獨立於系統空間中的動態連結程式庫是不存在的,也沒有意義。動態連結程式庫總是要載入到進程中的某個地址,該地址就是動態連結程式庫的模組控制代碼,這個控制代碼不同於進程執行個體控制代碼,它是相對於進程入口地址的一個位移量,只有通過模組控制代碼才能定址到該動態連結程式庫中嵌套的各種視窗或者其他資源。
ul_reason_for_call 參數表示該動態連結程式庫是在什麼條件下被載入的,即載入的原因。當使用者顯式採用LoadLibrary(Ex)函數載入一個動態連結程式庫或者由進程本身隱式載入該庫時,該入口函數就會被調用,入口參數 ul_reason_for_call 這時就等於DLL_PROCESS_ATTACH,入口函數的傳回值就是LoadLibrary函數的傳回值,它們都是通過EAX寄存器來傳遞的。根據LoadLibrary(Ex)函數的說明,這個函數如果返回一個NULL值,表示載入失敗。實際上在DllMain 函數中,只要返回FALSE,LoadLibrary(Ex)函數就會返回一個NULL值。如果動態連結程式庫需要排他調用,可以在進入點函數中判斷載入進程的檔案名稱,或者履行其他合法性檢查;如果不希望被某個進程調用,可以直接在DllMain函數中返回FALSE。DLL_PROCESS_ATTACH分支往往用於實現系統初始化,比如建立資料庫連結,建立鉤子函數,分配系統資源,儲存入口進程執行個體控制代碼等。
同樣,當進程不再使用該動態連結程式庫,比如調用ExitProcess函數或者顯式調用FreeLibrary函數時,系統又會使用DLL_PROCESS_DETACH參數執行相關分支。用於釋放系統資源、斷開資料庫連結、卸載鉤子函數、關閉檔案、釋放記憶體等。
注意:DLL_PROCESS_DETACH分支的執行是有條件的,進程的意外終止,並不會執行DLL_PROCESS_DETACH分支語句,這樣分支中的關閉資源、取消連結、關閉檔案等善後語句就無法執行,這將會造成一些資料的潛在丟失。因此除非萬不得已,不要輕易使用TerminateProcess函數終止進程執行。
如果線程建立時DLL完成到進程空間的映射,這樣系統會使用DLL_THREAD_ATTACH進入進入點。而系統執行ExitThread函數時會執行DLL_THREAD_DETACH分支。同樣的規則適用於線程,使用者不要輕易使用TerminateThread函數,這也會導致一些不可預知的記憶體流失或者資源沒有釋放和資料丟失。
如果使用者不在乎DLL_THREAD_DETACH和DLL_THREAD_ATTACH通知,又希望提高建立和撤銷線程的效能,可以在收到DLL_THREAD_ATTACH通知時,調用DisableThreadLibraryCalls函數。
注意:關於lpReserved參數----在靜態(隱式)載入和調用LoadLibray函數實現動態(顯式)載入動態庫時,這兩種情況是不一樣的,動態載入時這個值為0。如果使用者希望自己編寫的的動態連結程式庫只能被動態載入或者只能被靜態載入,可以通過判斷這個參數來實現。
程式碼範例:
dllmain.cpp(產生mydll.dll):
// dllmain.cpp : 定義 DLL 應用程式的進入點。#include "stdafx.h"#include<stdio.h>#include<Psapi.h>#include<Windows.h>typedef void(*pFnPtr)(char*);BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ){ char szName[MAX_PATH]; if (lpReserved != 0) { //只允許動態載入,靜態載入將提示錯誤並退出 MessageBox(NULL, "只允許動態載入", "Sorry", MB_OK); return FALSE; } GetModuleBaseName(GetCurrentProcess(),NULL ,szName,MAX_PATH);//擷取當前主進程的base name switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: //故意將調用進程的名字改為test.exe,該動態連結程式庫將無法載入。 if (strcmp(szName, "test.exe") == 0) return FALSE; case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE;}extern "C"{ //主調進程將調用該函數,該函數再調用主調進程的ExeFn函數 _declspec(dllexport) int fnImportingDLL() { MessageBox(NULL, "Dll Function called!", "mydll", MB_OK); pFnPtr fn = (pFnPtr)::GetProcAddress(GetModuleHandle(NULL), "ExeFn"); if (fn) fn("夢回吹角連營"); else { MessageBox(NULL, "It Did not work:", "From DLL", MB_OK); return -1; } }}
main.cpp(產生console.exe):
#include<iostream>#include<Windows.h>#define _DYNAMIC_#ifndef _DYNAMIC_#pragma comment(lib,"mydll.lib") //靜態載入extern "C" _declspec(dllexport) int fnImportingDLL();#endiftypedef int(*pfnImportingDLL)();using namespace std;int main(int argc, char* argv[]){#ifdef _DYNAMIC_ //動態載入 pfnImportingDLL fnImportingDLL = NULL; HMODULE hModule=::LoadLibrary("mydll.dll"); if (hModule == NULL) { cout << "無法載入mydll.dll" << endl; return -1; } fnImportingDLL =(pfnImportingDLL) GetProcAddress(hModule, "fnImportingDLL"); if (fnImportingDLL == NULL) { cout << "找不到fnImportingDLL函數" << endl; return -2; }#endif cout << "I‘m going...\n"; fnImportingDLL(); cout << "Game Over\n";#ifdef _DYNAMIC_ ::FreeLibrary(hModule);#endif return 0;}extern "C"{ //該函數將有動態連結程式庫中的函數來調用 _declspec(dllexport) void ExeFn(char* lpszMessage) { MessageBox(NULL, lpszMessage, "From Exe", MB_OK); }}
將產生的 console.exe檔案和mydll.dll檔案放在同一個檔案夾下
注釋掉 _DYNAMIC_ 宏將靜態載入mydll.dll,這樣會彈出對話方塊提示只能動態載入,因為在DllMain中檢查了lpReserved的值並做了判斷。
將console.exe的名字改為test.exe,然後在cmd中運行test.exe,將顯示無法載入mydll.dll。
Windows API 編程-----DLL編程之禁止載入自己