Some reasons not to do anything scary in your DllMain - The Old New Thing (microsoft.com)https://devblogs.microsoft.com/oldnewthing/20040127-00/?p=40873
Raymond Chen 2004年01月27日
简介
这篇文章讨论了为什么不应该在DLL的
DllMain
函数中执行复杂的操作
正文
众所周知,我们不应该在DLL的DllMain
函数中进行任何复杂的操作。Oleg Lvovitch就此写过两篇精彩的文章,一篇解释了事情的运作方式,另一篇则讲述了当事情不按预期进行时可能遇到的问题。这里再补充一个简单的理由:在许多情况下,加载一个库并不意味着要全面利用它的功能。例如,某人可能会以以下方式加载你的库,仅仅是为了获取一个图标:
// 为了说明目的,省略了错误检查
hinst = LoadLibrary("you");
hicon = LoadIcon(hinst, MAKEINTRESOURCE(5));
FreeLibrary(hinst);
如果这时你的DLL突然执行了复杂的操作,比如启动一个计时器或线程,这可能会让对方感到意外,甚至不快。
虽然可以通过使用
LoadLibraryEx
和LOAD_LIBRARY_AS_DATAFILE
标志来避免这种情况,但这并不是我要讨论的重点。
你的库还可能在没有运行任何代码的情况下被加载,尤其是当它作为其他DLL的依赖项时。假设“middle.dll”是一个连接到你的DLL的中间库。代码可能会这样加载它:
hinst = LoadLibrary("middle");
pfn = GetProcAddress(hinst, "SomeFunction");
pfn(...);
FreeLibrary(hinst);
当“middle.dll”被加载时,你的DLL也会被加载和初始化。这意味着即使“SomeFunction”函数没有使用你的DLL,你的初始化代码也会执行。
这种“中间DLL短暂加载”的情况实际上非常普遍。例如,当执行“Regsvr32 middle.dll”命令注册DLL时,中间DLL会被加载以调用其DllRegisterServer
函数,这个函数通常只是简单地添加一些注册表键,并不会调用你的辅助DLL。
另一个例子是打开控制面板文件夹。控制面板文件夹会加载每个*.cpl文件,以调用其CplApplet
函数来确定显示哪个图标,这通常也不会涉及到你的辅助DLL。
在你的DLL的DLL_PROCESS_ATTACH
处理程序中,绝不应该创建任何具有线程亲和性的对象。因为你无法控制是哪个线程发送DLL_PROCESS_ATTACH
消息,也无法预知是哪个线程发送DLL_PROCESS_DETACH
消息。如果发送DLL_PROCESS_ATTACH
消息的线程在加载你的DLL后立即结束,那么任何依赖该线程的对象都会停止工作。即使该线程没有立即结束,也没有保证说调用FreeLibrary
的线程和调用LoadLibrary
的是同一个,因此你无法在DLL_PROCESS_DETACH
中正确清理这些对象。
此外,绝不应该在你的DLL的DLL_PROCESS_ATTACH
中创建窗口。除了线程亲和性的问题,全局钩子如果在加载器锁内部运行,也可能导致灾难性的后果,比如系统死锁。
明天我将提供更多相关的例子。