和可執行文件同樣,動態連接庫也有本身的入口地址,若是系統或者當前進程的某個線程調用LoadLibrary函數加載或者使用FreeLibrary卸載該動態連接庫的時候,會自動使用3個特定的堆棧參數跳轉到該地址來運行。入口函數是爲了完成動態連接庫代碼的初始化和藹後工做,好比卸載後的資源釋放。ios
這三個參數具備特殊的含義。數據庫
BOOL APIENTRY DllMain(api
HMODULE hModule,函數
DWORD ul_reason_for_call,性能
LPVOID lpReserved
)spa
第一個參數是實例句柄,這個所謂的句柄實際上就是該加載進程在內存中的景象地址,注意進程句柄和動態連接庫的模塊句柄是不同的。好比,若是用戶想使用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分支每每用於實現系統初始化,好比創建數據庫連接,建立鉤子函數,分配系統資源,保存入口進程實例句柄等。code
一樣,當進程再也不使用該動態連接庫,好比調用ExitProcess函數或者顯式調用FreeLibrary函數時,系統又會使用DLL_PROCESS_DETACH參數執行相關分支。用於釋放系統資源、斷開數據庫連接、卸載鉤子函數、關閉文件、釋放內存等。blog
注意: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(); #endif typedef 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。