本文可帮助你解决以下问题:如果 DLL 加载或卸载序列因未经处理的异常中断,C++ DLL 静态链接到 C 运行时库 (CRT) 会导致线程退出时出现严重错误。
原始产品版本:
Visual C++
原始 KB 编号:
2754614
如果 DLL 加载或卸载序列因未经处理的异常中断,则静态链接到 C 运行时库的 C++ DLL (CRT) 可能会在线程退出时导致严重错误。
进程可能会在线程退出时崩溃, (0xC0000005出现访问冲突异常,EXCEPTION_ACCESS_VIOLATION) 如果它已动态加载 (,例如通过调用
LoadLibraryA ()
) 与 C Runtime 静态链接的本机 C++ DLL,并且 DLL 在其初始化或关闭期间生成了未经处理的异常。
在 CRT 启动或关闭 ((例如,在
DLL_PROCESS_ATTACH
期间或
DLL_PROCESS_DETACH
中
DllMain()
),或者在全局/静态 C++ 对象的构造函数或析构函数) 期间,如果 DLL 生成未经处理的严重错误,则
LoadLibrary
调用将只处理异常并返回
NULL
。 当 DLL 加载或卸载失败时,你可能会观察到一些错误代码,包括:
ERROR_NOACCESS (998) 或 EXCEPTION_ACCESS_VIOLATION (0xC0000005、0n3221225477)
EXCEPTION_INT_DIVIDE_BY_ZERO (0xC0000094、0n3221225620)
ERROR_STACK_OVERFLOW (1001) 或 EXCEPTION_STACK_OVERFLOW (0xC00000FD,0n3221225725)
C++ 异常 (0xE06D7363,0n3765269347)
ERROR_DLL_INIT_FAILED (0x8007045A)
在调用线程即将退出之前,通常不会观察到此库启动或关闭失败,其形式为致命的访问冲突异常,其调用堆栈如下所示:
<Unloaded_TestDll.dll>+0x1642 ntdll!RtlProcessFlsData+0x57 ntdll!LdrShutdownProcess+0xbd
ntdll!RtlExitUserProcess+0x74 kernel32!ExitProcessStub+0x12 TestExe!__crtExitProcess+0x17
TestExe!doexit+0x12a TestExe!exit+0x11 TestExe!__tmainCRTStartup+0x11c
kernel32!BaseThreadInitThunk+0xe ntdll!__RtlUserThreadStart+0x70 ntdll!_RtlUserThreadStart+0x1b
可以在 Visual Studio 中使用以下代码片段重现此行为:
//TestDll.dll: Make sure to use STATIC CRT to compile this DLL (i.e., /MT or /MTd)
#include <Windows.h>
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
switch (ul_reason_for_call)
case DLL_PROCESS_ATTACH:
//About to generate an exception
int* pInt = NULL;
*pInt = 5;
break;
return TRUE;
//TestExe.exe:
#include <Windows.h>
#include <stdio.h>
int main(int argc, TCHAR* argv[])
HMODULE hModule = LoadLibrary(TEXT("TestDll.dll"));
printf("GetLastError = %d\n", GetLastError());
if (hModule != NULL)
FreeLibrary(hModule);
return 0;
//Access Violation will occur following the above return statement
当线程退出时,Windows 会调用光纤本地存储 (FLS) 回调函数,并且该函数的地址不再位于有效的进程内存中。 最常见的原因是在过早卸载的 DLL 中使用静态 CRT。
当 C 运行时在 DLL 加载时初始化时,它通过调用 FlsAlloc () 注册名为 _freefls () 的 FLS 回调函数;但是,如果在加载或卸载 DLL 时出现未经处理的异常,C 运行时不会注销此 FLS 回调。
由于 C 运行时在 DLL 中静态链接,因此其 FLS 回调在该 DLL 本身中实现。 如果由于未经处理的异常而无法加载或卸载此 DLL,不仅会自动卸载 DLL,而且 C 运行时的 FLS 回调也将在 OS 中保持注册状态,即使在卸载 DLL 之后也是如此。 例如,当线程退出 (时,当 EXE 的 main()
函数返回) 时,OS 会尝试调用注册的 FLS 回调函数, (在这种情况下 _freefls) 现在指向未映射的进程空间,最终导致访问冲突异常。
VS 2012) 中的 VC++ 11.0 CRT (中进行了更改,以更好地解决 DLL 启动期间未处理的异常的 FLS 回调清理问题。 因此,对于源代码可访问且可以重新编译的 DLL,可以尝试以下选项:
使用最新的 VC11 CRT 编译 DLL (例如,使用 VS2012 RTM) 生成 DLL。
在编译 DLL 时使用 CRT DLL,而不是静态链接到 C 运行时;使用 /MD 或 /MDd 而不是 /MT 或 /MTd。
如果可能,请更正未处理异常的原因,从 DllMain
中删除容易出现异常的代码段,并/或正确处理异常。
实现自定义 DLL 入口点函数,总结 CRT 的初始化和代码,以在 DLL 启动期间发生异常时取消注册 CRT 的 FLS 回调。 当在调试版本中使用 /GS (缓冲区安全检查) 时,围绕入口点的此异常处理可能会导致问题。 如果选择此选项,请在调试版本中排除异常处理 (或 #if
#ifdef
) 。 对于无法重新生成的 DLL,目前无法更正此行为。
此行为是由于未能将 FLS 回调注销到已卸载的模块中而导致的,因此,它不仅由 DLL CRT 启动或关闭期间未处理的异常引起,而且还是由于设置如下所示的 FLS 回调,并且未在卸载 DLL 之前取消注册它:
//TestDll.dll: To reproduce the problem, compile with static CRT (/MT or /MTd)
#include <Windows.h>
VOID WINAPI MyFlsCallback(PVOID lpFlsData)
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
switch (ul_reason_for_call)
case DLL_PROCESS_ATTACH:
//Leaking FLS callback and rather setting an invalid callback.
DWORD dwFlsIndex = FlsAlloc(MyFlsCallback);
FlsSetValue(dwFlsIndex, (PVOID)5);
break;
return TRUE;
//TestExe.exe:
#include <Windows.h>
#include <stdio.h>
int main(int argc, TCHAR* argv[])
HMODULE hModule = LoadLibrary(TEXT("TestDll.dll"));
printf("GetLastError = %d \n", GetLastError());
if (hModule != NULL)
FreeLibrary(hModule);
return 0;
//Access Violation will occur following the above return statement
由于 OS 应调用 FLS 回调函数来执行 FLS 清理,因此上述无效的函数指针将导致访问冲突异常。 因此,此问题的理想解决方法是更正代码本身,确保在卸载 DLL 之前取消注册 FLS 回调。
运行时计算机上可能注册了第三方产品,这些产品会在运行时将 DLL 注入大多数进程。 在这种情况下,在产品开发之外受影响的 DLL 可能会导致在线程退出期间出现此错误。 如果无法按照上述指导重新生成此类 DLL,则唯一的选择可能是联系产品的供应商并请求此类修复,或卸载第三方产品。