《Windows Via C/C++》读书笔记之dll入口函数
一、DllMain()的一些知识与注意事项
*** DllMain()是大小写敏感的。
*** DllMain()里面不要处理太复杂的操作,且由于加载顺序不定,只有调用Kernel32的某些函数是安全的。
参考文档:http://msdn.microsoft.com/en-us/windows/hardware/gg487379.aspx
The following tasks are safe to perform within DllMain:
• Initialize static data structures and members at compile time.
• Create and initialize synchronization objects.
• Allocate memory and initialize dynamic data structures (avoiding the functions listed above.)
• Set up thread local storage (TLS).
• Open, read from, and write to files.
• Call functions in Kernel32.dll (except the functions that are listed above).
• Set global pointers to NULL, putting off the initialization of dynamic members. In Microsoft Windows Vista™, you can use the one-time initialization functions to ensure that a block of code is executed only once in a multithreaded environment.
*** DLL_PROCESS_ATTACH: 进程第一次加载时通知
DLL_PROCESS_ATTACH仅在dll第一次被加载时通知。
隐式链接中,系统将exe和其所需的dll加载进地址空间后,创建主线程(Primary Thread),由主线程调用各个dll的DllMain(),然后调用exe的main()。
动态加载时,由调用LoadLibrary()的线程执行DllMain()。
*** DLL_THREAD_ATTACH: 新线程创建时通知
DLL_THREAD_ATTACH的几点注意事项:
1、进程中有新线程创建时,所有已经加载的DLL可以处理DLL_THREAD_ATTACH通知,这些处理代码由新线程执行,处理完毕后,新线程才能继续执行代码
2、当新的dll被加载时,系统不会让进程中已有的线程去通知DLL_THREAD_ATTACH
3、主线程不通知DLL_THREAD_ATTCH,主线程通知的是DLL_PROCESS_ATTACH
*** DLL_THREAD_DETACH: 线程消亡时通知
DLL_THREAD_DETACH的注意事项:
1、ExitThread()的线程调用已加载dll的DLL_THREAD_DETACH的处理,该通知处理完毕后,线程才能退出
2、该通知不一定和DLL_THREAD_ATTACH配对,有可能是和DLL_PROCESS_ATTACH
*** DLL_PROCESS_DETACH: 进程卸载dll时通知
DLL_PROCESS_DETACH的几点注意事项:
1、如果DLL_PROCESS_ATTACH的处理返回FALSE,则不会调用DllMain(),传递DLL_PROCESS_DETACH
2、调用ExitProcess()的线程负责调用DLL_PROCESS_DETACH
3、对于显示链接的dll,调用FreeLibrary()的线程负责调用DLL_PROCESS_DETACH
4、DLL所在进程被TerminateProcess()后,不会收到DLL_PROCESS_DETACH通知。可能会导致数据丢失
注意:DLL_PROCESS_DETACH处理完成后,dll才会被卸载,进程才能正常退出。所以如果存在死循环,则进程不会被正常退出。
二、DllMain()的执行对于系统来说是个同步操作
*** 进程对于DllMain会做同步操作,需要注意避免死锁
MSDN有草稿文档说,LoadLibrary会获取LoadLock,如果在DllMain()里面调用了其他DLL或者Kernel.dll的LoadLibrary等函数,会构成死锁或者崩溃。因为其他dll不一定已初始化过
*** DisableThreadLibraryCalls()
《Windows Via C/C++》作者在Chapter20中Serialized Calls to DllMain小节,曾尝试调用DisableThreadLibraryCalls()来预期达到解除死锁的目的。代码片段如下:
case DLL_PROCESS_ATTACH: // The DLL is being mapped into the process' address space. // Prevent the system from calling DllMain // when threads are created or destroyed. DisableThreadLibraryCalls(hInstDll); // Create a thread to do some stuff. hThread = CreateThread(NULL, 0, SomeFunction, NULL, 0, &dwThreadId); // Suspend our thread until the new thread terminates. WaitForSingleObject(hThread, INFINITE); // We no longer need access to the new thread. CloseHandle(hThread); break;
这块代码在响应进程加载该DLL的时候创建一个线程,然后等待线程消亡,而线程消亡时会向该进程中的DLL通知DLL_THREAD_DETACH,执行DllMain(),构成死锁。DisableThreadLibraryCalls()未奏效的原因是该函数只是告诉系统这个dll不接收DLL_THREAD_DETACH通知,但如果进程中有其他dll需要接收,进程还是需要去获取DllMain()的锁,还是会构成死锁。
三、DllMain()与C运行时库的关系
*** 使用linker选项/ENTRY可以告诉链接器DLL的入口函数
当使用微软的链接器时,如果指定了/DLL开关,则链接器会将__DllMainCRTStartup()作为入口函数。__DllMainCRTStartup会在转发DLL_PROCESS_ATTACH到DllMain()之前,初始化CRT,保证全局C++对象初始化,在DLL_PROCESS_DETACH到了之后,先先转发给DllMain()然后销毁全局C++对象。
CRT提供的入口函数会被编译进DLL的代码段(.text section),exe文件也是如此。CRT其他的函数会有msvcr**.dll导出。**为编译器版本号。
*** CRT提供的缺省DllMain()
__DllMainCRTStartup()不关注DLL_THREAD_ATTACH和DLL_THREAD_DETACH的通知。
如果用户没有提供DllMain(),CRT会认为用户也不关心这两个通知,会合成一个DllMain(),在里面调用DisableThreadLibraryCalls()。
参考文档:
1、Best Practices for Creating DLLs
2、DllMain entry point