DLL相关的东西
1、DLL的加载方式
隐式:
#pragma comment(lib, "XX.lib");
编译器去查找名为XX.dll的DLL,除了名字相同,该DLL和该LIB的GUID也相同。
显式:
HINSTANCE hInst = LoadLibrary(TEXT("XX.dll"));
if(NULL == hInst) retrun;
HINSTANCE hInst = LoadLibrary(TEXT("XX.dll"), NULL, FLAGS);
第三个参数为一些标志,详见核心编程。
2、DLL的入口函数
原型
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
);
其中hModule为该DLL在当前进程地址空间中的位置,ul_reason_for_call为调用此函数的原因,lpReserved如果为显式加载则为0,如果为隐式加载则为非0。
详细说一下ul_reason_for_call
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { #ifdef _DEBUG switch (ul_reason_for_call) { //只在该DLL第一次加载进地址空间的时候被调用一次 //此后当该DLL被加载进不同进程的地址空间时让然不调用 //当主调线程通过该reason调用DllMain函数时,主调线程不会收到DLL_THREAD_ATTACH通知
case DLL_PROCESS_ATTACH: OutputDebugString(TEXT("\r\n***********B Dll DLL_PROCESS_ATTACH ***********\r\n")); break; //当该DLL已经被加载到进程地址空间后,当有新【线程创建】的时候,新创建的线程先执行此段代码 //然后再去执行线程本身的代码 //不管有几个DLL,每个DLL的此处代码都会被新线程执行
case DLL_THREAD_ATTACH: OutputDebugString(TEXT("\r\n***********B Dll DLL_THREAD_ATTACH ***********\r\n")); break; //当该DLL已经被加载到进程地址空间后,当有【线程结束】的时候,该线程结束之前会调用此处代码 //然后才返回 //不管有几个DLL,每个DLL的此处代码都会被执行
case DLL_THREAD_DETACH: OutputDebugString(TEXT("\r\n***********B Dll DLL_THREAD_DETACH *********** \r\n")); break; //只有在该DLL从内存中卸载的时候才会被调用一次
case DLL_PROCESS_DETACH: OutputDebugString(TEXT("\r\n***********B Dll DLL_PROCESS_DETACH ***********\r\n")); break; } #endif
return TRUE; }
demo
有两个DLL, ADll.dll 和 BDll.dll
在程序中加载这个两个DLL, 然后创建一个新线程,看输出
//加载DLL
void CUseABDllDlg::OnBnClickedBtnLoadlib() { HINSTANCE hInstA = LoadLibrary(_T("ADll.dll")); HINSTANCE HIntB = LoadLibrary(_T("BDll.dll")); if (NULL == hInstA || NULL == HIntB) { AfxMessageBox(_T("加载DLL失败")); return; } } //线程函数
UINT WINAPI WorkThread(LPVOID lpParam) { #ifdef _DEBUG OutputDebugString(TEXT("\r\n&&&&&&&&&&&&&&&&&&线程被创建&&&&&&&&&&&&&&&&&\r\n")); #endif
return 0U; } //创建线程
void CUseABDllDlg::OnBnClickedButton2() { HANDLE hThread = (HANDLE)_beginthreadex(NULL, 0, WorkThread, NULL, 0, NULL); if (NULL == hThread || INVALID_HANDLE_VALUE == hThread) { AfxMessageBox(_T("创建线程失败")); } }
输出
结论:
每创建一个新线程,当前进程地址空间中的DLL的DllMain函数都会通过DLL_THREAD_ATTACH通知被调用一次
每结束一个线程,进程地址空间空的DLL的DllMain函数都会通过DLL_THREAD_DETACH通知被调用一次。