18

问题:

从非托管代码进入CLR的线程中的未处理异常不会触发“正常”未处理异常 CLR 处理。

CSSimpleObject.GetstringLength()在下面从 C++调用的代码中

  • "1" 在调用线程(非 CLR 创建的线程)中引发异常,
  • “2”在 new Thread()(CLR 创建的线程)中引发异常。

在“1”的情况下

  • CurrentDomain_UnhandledException() 永远不会被调用。
  • 应用程序域和进程将保持加载和运行,您只会得到一个 FAILED。

情况“2”(预期行为)

  • CurrentDomain_UnhandledException() 被调用。
  • 进程被杀死。

问题:

必须做什么才能获得“正常”行为?

示例代码:

下面的代码基于“所有互操作和融合示例”中的 Visual Studio 2010“ CppHostCLR ”代码示例。

运行时主机(C++):

PCWSTR pszStaticMethodName = L"GetStringLength";
PCWSTR pszStringArg = L"1";
//PCWSTR pszStringArg = L"2";

hr = pClrRuntimeHost->ExecuteInDefaultAppDomain(pszAssemblyPath,
    pszClassName, pszStaticMethodName, pszStringArg, &dwLengthRet);
if (FAILED(hr))
{
    wprintf(L"Failed to call GetStringLength w/hr 0x%08lx\n", hr);
    goto Cleanup;
}

托管代码(C#):

public class CSSimpleObject
{
    public CSSimpleObject()
    {
    }
    //------8<----------
    public static int GetStringLength(string str)
    {
        AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(CurrentDomain_UnhandledException);

        switch (str)
        {
            case "1":
                throw new Exception("exception in non-CLR-created thread");
            case "2":
                Thread thread = new Thread(new ThreadStart(WorkThreadFunction));
                thread.Start();
                break;
        }
        return str.Length;

    }
    static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
    {
        Console.WriteLine("CurrentDomain_UnhandledException:" + e.ToString());
        Console.ReadKey();
    }
    public static void WorkThreadFunction()
    {
        throw new Exception(""exception in CLR-created thread"");
    }

迄今为止的研究:

MSDN 最初暗示非 CLR 创建的线程中未处理的异常应该或多或少“自然”地表现 - 请参阅“托管线程中的异常

公共语言运行时允许线程中大多数未处理的异常自然地进行。在大多数情况下,这意味着未处理的异常会导致应用程序终止。”

“大多数”意味着在 CLR 创建的内部线程中,线程中止和应用程序域卸载异常的处理方式不同。在非 CLR 线程中

“他们正常进行,导致申请终止。”

进一步的研究使我找到了“ CLR 中未处理的异常处理”,在那里我发现了以下内容:

“如果未处理异常......在托管方法中,异常将退出 CLR 但继续作为本机 SEH 异常向上传播(托管异常表示为本机 SEH 异常)......操作系统未处理的异常过滤器(UEF)机制可能并不总是触发CLR的未处理异常处理。正常情况下,这会按预期工作,并且会触发CLR的未处理异常处理。但是,在某些情况下,这可能不会发生。

上面的代码有什么问题或者如何更改它以触发 CLR 的未处理异常处理?

更新(2011-05-31):

我刚刚发现了一个旧的错误报告,“当从非托管调用托管代码并引发异常时,未调用 UnhandledExceptionEventHandler - http://tinyurl.com/44j6gvu ”,Microsoft 确认这是一个“错误”行为:

感谢您花时间报告此问题。该行为确实是由 CLR 执行引擎和 CRT 竞争 UnhandledExceptionFilter 引起的错误。CLR 的架构在 4.0 版本中进行了修改,支持这种场景。

更新(2011-06-06):

为什么正确地做到这一点很重要?

  • 如果您正在创建一个托管环境,您的开发人员希望在异常处理中具有一致的行为
  • 除非有办法在本机线程中触发“正常 CLR 异常处理”,否则这意味着您始终必须将执行转移到托管线程(例如在线程池中排队)
  • 仍然有一小部分代码将执行从本机线程转移到托管线程......必须捕获所有异常并以不同的方式处理这种情况

注意:通过更改 CLR 行为SetActionOnFailure()会使事情变得更糟,因为它最终会掩盖原始异常(即,您最终会看到 threadAborts 而不是内存不足 - 不知道原始错误来自何处)!

4

1 回答 1

2

该更新意味着您可能在这里有解决方案。但是,它的解决方案并非在所有情况下都有效,所以这里有更多信息。

如果您更喜欢 CLR 未处理异常行为,则将其设为最外层程序并仅调用本机代码以执行特定功能。这将确保 CLR 控制未处理的异常过滤器。

如果你想保留你当前的结构并且你的 C++ 代码很小,你可以完全停止使用 CRT(这会拒绝你一堆有用的东西,包括静态构造函数和 C++ 异常处理)。这将确保 CLR 获得未处理的异常过滤器。

而且,当然,您可以简单地自己调用 SetUnhandledExceptionFilter 并获得您想要的行为。

但是,我认为在这种情况下,最好的建议是在任何线程的调用堆栈上放置一个带有 catch 块的实际函数,当异常发生时你想在其中做某事,而不是依赖 UEF 机制——因为在上下文中作为一个组件系统,它总是很脆弱,因为多个用户竞争它。

马丁

于 2011-06-04T18:21:29.943 回答