CLR hosting exception handling in a non-CLR-create

2019-03-17 22:36发布

问题:

The issue:

An unhandled exception in a thread entering the CLR from unmanaged code does not trigger the "normal" unhandled exception CLR processing.

In the code below calling CSSimpleObject.GetstringLength() from C++ with

  • "1" throws an exception in the calling thread (non-CLR-created thread),
  • "2" throws an exception in a new Thread() (CLR-created thread).

In case "1"

  • CurrentDomain_UnhandledException() is never called.
  • The Application Domain and the process will stay loaded and running, you will only get a FAILED.

In case "2" (expected behavior)

  • CurrentDomain_UnhandledException() is called.
  • The proces gets killed.

The Question:

What has to be done to get the "normal" behavior?

Sample code:

The code below is based on the Visual Studio 2010 "CppHostCLR" code sample from the "all interop and fusion samples".

RuntimeHost (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;
}

Managed Code (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"");
    }

Research so far:

MSDN initially implies that unhandled exceptions in a non-CLR-created thread should behave more or less "naturally" - see "Exceptions in Managed Threads"

The common language runtime allows most unhandled exceptions in threads to proceed naturally. In most cases this means that the unhandled exception causes the application to terminate."

"Most" meaning that in CLR-created threads internal, thread abort and Application Domain unloaded exceptions are handled differently. In non-CLR threads

"they proceed normally, resulting in termination of the application."

Further research led me to "Unhandled Exception Processing In The CLR" where I found out the following:

"if the exception was not handled ... in the managed method, the exception would exit the CLR but continue to propagate up the stack as a native SEH exception (managed exceptions are represented as native SEH exceptions) ... The OS unhandled exception filter (UEF) mechanism may not always result in triggering the CLR's unhandled exception processing. Under normal circumstances, this will work as expected and the CLR's unhandled exception processing will be triggered. However, in certain instances this may not happen."

What is wrong with the code above or how can it be changed so that the CLR's unhandled exception processing is triggered?

Update (2011-05-31):

I just found an old bug report, "UnhandledExceptionEventHandler is not called when managed code is called from unmanaged and throws an exception - http://tinyurl.com/44j6gvu", where Microsoft confirms this is a "buggy" behavior:

Thank you for taking the time to report this problem. The behavior is indeed a bug caused by the CLR execution engine and the CRT competing for the UnhandledExceptionFilter. The architecture of the CLR has been revised in the 4.0 version supporting this scenario.

Update (2011-06-06):

Why is it important to get this right?

  • if you are creating a hosting-environment your developers expect a consistent behavior in the exception handling
  • unless there is a way to trigger the "normal CLR exception handling" in a native thread it means you always have to transfer execution to a managed thread (enqueueing in a thread pool for instance)
  • there is still that tiny bit of code transfering excecution from native to managed thread ... that has to catch all exceptions and somehow handle that situation differently

Note: changing the CLR behavior through SetActionOnFailure() makes matters worse, in the sence, that it ends up masking the original exception (i.e. instead of an out of memory you endup seeing threadAborts - without a clue where the original error cam from)!

回答1:

The update implies that you perhaps have a solution here. However, its solution won't work in all cases, so here is some more info.

If you prefer CLR Unhandled Exception behaviour, then make that the outer-most program and call native code only to perform specific functions. That will ensure that the CLR gets control of the unhandled exception filter.

If you want to retain your current structure and the C++ code you have is small, you could stop using the CRT at all (which will deny you a bunch of useful stuff including static constructors and C++ exception handling). That will ensure that the CLR gets the Unhandled Exception Filter.

And, of course, you could simply call SetUnhandledExceptionFilter youself and get the behaviour you want.

However, I think the best recommendation in this situation is to put an actual function with a catch block on the call stack of any thread where you want to do something when an exception happens and not rely on the UEF mechanism -- because in the context of a component system, it is always fragile as multiple users compete for it.

Martyn