.NET 中非托管线程上的异常
本文关键字:异常 线程 NET | 更新日期: 2023-09-27 17:56:59
我的应用终止的情况,在终止前使用回调?
.NET 处理程序在以下情况下不起作用,SetUnhandledExceptionHandler 是正确的选择吗? 它似乎具有下面讨论的缺点。
场景
我想通过向 .net 应用中的服务发送消息和错误报告来响应所有应用终止的情况。
但是,我有一个 WPF 应用程序,其中我们的两个测试人员会绕过未经处理的异常:
- AppDomain.UnhandledException (最重要的是)
- Application.ThreadException
- 调度程序。未处理异常
它们标记为 SecuirtyCritical 和 HandleProcessCorruptedStateExceptions。legacyCorruptedStateExceptionsPolicy 在 app.config 中设置为 true
我在野外的两个例子
- 运行 widows10 的 VirtualBox 在某处初始化 WPF 时.dll会抛入一些 vboxd3d(关闭 vbox 3d 加速"修复它")
- Win8 机器在系统上下文菜单中具有"在显卡 A/B 上运行"的可疑选项,在 WPF 启动期间的某处 (:/) 崩溃,但仅在应用防破解工具时崩溃。
无论哪种方式,在实时时,应用都必须在终止之前响应这些类型的故障。
我可以使用非托管异常重现此问题,该异常发生在 .net 中 PInvoked 方法的非托管线程中:
测试.dll
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
DWORD WINAPI myThread(LPVOID lpParameter)
{
long testfail = *(long*)(-9022);
return 1;
}
extern "C" __declspec(dllexport) void test()
{
DWORD tid;
HANDLE myHandle = CreateThread(0, 0, myThread, NULL, 0, &tid);
WaitForSingleObject(myHandle, INFINITE);
}
应用.exe
class TestApp
{
[DllImport("kernel32.dll")]
static extern FilterDelegate SetUnhandledExceptionFilter(FilterDelegate lpTopLevelExceptionFilter);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
delegate int FilterDelegate(IntPtr exception_pointers);
static int Win32Handler(IntPtr nope)
{
MessageBox.Show("Native uncaught SEH exception"); // show + report or whatever
Environment.Exit(-1); // exit and avoid WER etc
return 1; // thats EXCEPTION_EXECUTE_HANDLER, although this wont be called due to the previous line
}
[DllImport("test.dll")]
static extern void test();
[STAThread]
public static void Main(string[] args)
{
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);
SetUnhandledExceptionFilter(Win32Handler);
test(); // This is caught by Win32Handler, not CurrentDomain_UnhandledException
}
[SecurityCritical, HandleProcessCorruptedStateExceptions ]
static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
Exception ex = e.ExceptionObject as Exception;
MessageBox.Show(ex.ToString()); // show + report or whatever
Environment.Exit(-1); // exit and avoid WER etc
}
}
这将处理 vboxd3d 中的故障.dll在裸 WPF 测试应用程序中,当然,该应用程序还注册了 WCF 调度程序和 WinForms 应用程序(为什么不注册)异常处理程序。
更新
- 在我尝试使用它的生产代码中,处理程序似乎被其他调用者覆盖,我可以通过每 100 毫秒调用一次方法来解决这个问题,这当然是愚蠢的。
- 在存在 vbox3d.dll 问题的计算机上,执行上述操作会将异常替换为 clr.dll 中的异常。
- 在崩溃时,传递到 kernel32 中的托管函数指针不再有效。 使用本机帮助程序 dll(在内部调用本机函数)设置处理程序似乎正在工作。 托管函数是一种静态方法 - 我不确定固定是否适用于此处,也许 clr 正在终止......
- 事实上,被管理的代表正在被收集。 没有发生处理程序的"覆盖"。 我已添加为答案..不确定要接受什么或这里的 SO 约定是什么......
问题中代码的问题如下:
SetUnhandledExceptionFilter(Win32Handler);
由于委托是自动创建的,因此可以:
FilterDelegate del = new FilterDelegate(Win32Handler);
SetUnhandledExceptionFilter(del);
问题是,GC 可以在最终引用后的任何时候收集它,以及创建的本机>管理的 thunk。所以:
SetUnhandledExceptionFilter(Win32Handler);
GC.Collect();
native_crash_on_unmanaged_thread();
将始终导致令人讨厌的崩溃,其中传递到 kernel32 中的处理程序.dll不再是有效的函数指针。 这可以通过不允许 GC 收集以下内容来补救:
public class Program
{
static FilterDelegate mdel;
public static void Main(string[] args)
{
FilterDelegate del = new FilterDelegate(Win32Handler);
SetUnhandledExceptionFilter(del);
GC.KeepAlive(del); // do not collect "del" in this scope (main)
// You could also use mdel, which I dont believe is collected either
GC.Collect();
native_crash_on_unmanaged_thread();
}
}
其他答案也是一个很好的资源;不知道现在要标记什么作为答案。
我不得不处理不可预测的非托管库。
如果要对非托管代码进行 P/调用,则可能会遇到问题。我发现在非托管代码中使用 C++/CLI 包装器更容易,在某些情况下,我在进入 C++/CLI 之前在库中编写了另一组非托管C++包装器。
你可能会想,"你到底为什么要写两套包装纸?
首先,如果隔离非托管代码,则可以更轻松地捕获异常并使其更可口。
第二个是纯粹务实的 - 如果你有一个使用 stl 的库(不是 dll),你会发现链接会神奇地为所有代码,托管和非托管,提供 stl 函数的 CLI 实现。防止这种情况的最简单方法是完全隔离使用 stl 的代码,这意味着每次通过非托管代码中的 stl 访问数据结构时,最终都会在托管代码和非托管代码之间进行多次转换,并且性能会下降。你可能会想,"我是一个一丝不苟的程序员 - 我会非常小心地把#pragma managed
和/或#pragma unmanaged
包装器放在正确的位置,我就准备好了。不,不,不。这不仅困难且不可靠,而且当您(不是如果)未能正确执行此操作时,您将没有很好的方法来检测它。
与往常一样,您应该确保您编写的任何包装器都是厚实的而不是健谈的。
下面是处理不稳定库的典型非托管代码块:
try {
// a bunch of set up code that you don't need to
// see reduced to this:
SomeImageType *outImage = GetImage();
// I was having problems with the heap getting mangled
// so heapcheck() is conditional macro that calls [_heapchk()][1]
heapcheck();
return outImage;
}
catch (std::bad_alloc &) {
throw MyLib::MyLibNoMemory();
}
catch (MyLib::MyLibFailure &err)
{
throw err;
}
catch (const char* msg)
{
// seriously, some code throws a string.
throw msg;
}
catch (...) {
throw MyLib::MyLibFailure(MyKib::MyFailureReason::kUnknown2);
}
无法正确处理的异常总是会发生,无论您多么努力地从内部保护它,该过程都可能会意外死亡。但是,您可以从外部监视它。
有另一个进程来监视您的主进程。如果主进程突然消失而没有记录错误或正常报告内容,则第二个进程可以做到这一点。第二个过程可以简单得多,根本没有非托管调用,因此它突然消失的可能性要小得多。
作为最后的手段,当您的流程开始时,请检查它们是否已正确关闭。如果没有,您可以报告错误的关机。如果整个机器死亡,这将很有用。