3F / DllExport

.NET DllExport with .NET Core support (aka 3F/DllExport aka DllExport.bat)
MIT License
961 stars 133 forks source link

Exception thrown in managed DLL is not handled properly #85

Closed Olby2000 closed 4 years ago

Olby2000 commented 6 years ago

Hi, This is not as much an issue with the dllexport it self as with how the produced libraries work within a C++ host.

Basically I wrote an extension DLL for an un-managed C++ application. Everything works great, however when an exception is thrown in the managed code (e.g. throw new Exception();) the calling thread is terminated before any of the catch block code is executed. VS debugger will say something like The thread 0x3d8 has exited with code 0 (0x0). and anything I do in the catch block like calling hosts's API commands, displaying a message box or log the error will not work because the host application has moved on and the debugger will display Exception thrown: 'System.NullReferenceException' in ManagedPlugin.dll

Any ideas how to fix this? Thank you.

3F commented 6 years ago

You have many ways. For example, for SEH handling more like:

unmanaged C++

int filter(unsigned int code, struct _EXCEPTION_POINTERS* ptr) { 
    ... 
    return EXCEPTION_EXECUTE_HANDLER; 
}
...

__try
{
    lib.call<int>("ThrowUnhandledCLRException");
}
__except(filter(GetExceptionCode(), GetExceptionInformation()))
{
    //... yey!
}

However, I recommend wrapping for CLR side. If pointwise is difficult to cover lot of procs, well, you can also try with ~GetLastError model through wrapping all unhandled exceptions via existing domains, for example:

~

C#

AppDomain.CurrentDomain.UnhandledException += (object sender, UnhandledExceptionEventArgs e) =>
{
    SetLastError(
        ...
    );
};

unmanaged C++

try
{
    ...
    throwIfError();
}
catch(const UnhandledCLRException& ex)
{
    ...
}

Or some other pre-processing via CLR side.

etc.

Olby2000 commented 6 years ago

My bad! It was a silly typo :) In my catch block I had this:

[DllExport(CallingConvention = CallingConvention.StdCall)]
public static uint Test()
{
    try
    {
        MessageBox.Show("test1"); // ok
        throw new Exception();
    }
    catch (Exception e)
    {
        MessageBox.Show($"{e.Message}" +
            $"\n{e.InnerException.Message}"); // fails here
        return 1;
    }
    return 0;
}

The $"\n{e.InnerException.Message}"); // fails here line was missing a null check so it should be $"\n{e.InnerException?.Message}"); // fails here. It was causing the NullReference error and crashing the CLR.

Thanks for the info. Here are a few clarifications, first of all I do not have access to the C++ source code - it's a third party application which can work with plugin DLLs. I wrote the DLL in C# and exported the functions using DllExport.

I would like to have a blanket approach where if I did not catch a specific error in my code I have a general catch all unhandled exceptions code which would at least log the error or show a message. I tried the following code but it does not seem to work. Could you please advise. Thanks.

[DllExport(CallingConvention = CallingConvention.StdCall)]
public static void TestFunc()
{
    AppDomain.CurrentDomain.UnhandledException +=
        (object sender, UnhandledExceptionEventArgs e)
        => MessageBox.Show($"UnhandledException");

    Application.ThreadException +=
        (object sender, ThreadExceptionEventArgs e)
        => MessageBox.Show($"ThreadException: {e?.Exception?.Message}");

    MessageBox.Show("exception incoming");
    throw new Exception();
    // The host kills the DLL thread and the message boxes do not pop up
}
3F commented 6 years ago

@Olby2000 Try to force route the exceptions via UnhandledExceptionMode.CatchException if you're using System.Windows.Forms.

Also note, MessageBox.Show can cause a deadlocks for different MTA/STA models. Here, I already mentioned about this:

  1. https://github.com/3F/DllExport/issues/69#issuecomment-375923269
  2. https://github.com/3F/DllExport/issues/69#issuecomment-376002474