2

I'm currently working with code resembling sth like this:

[DllImport("<native-dlllibrary>.dll")]
internal unsafe static extern void SetCallback([...], <delegateType> callback);

// This method calls provided callback in case of error
// synchronously 
DllImport("<native-dlllibrary>.dll")]
internal unsafe static extern void SomeOperation([...]);

[...]

private void ActualCallback([...])
    => throw new Exception();

[...]

{
    SetCallback([...], callback);
    try
    {
        SomeOperation([...]);
    }
    catch(Exception e)
    {
        [...] // process exception
    }
}

The dll library is pure C/C++ (no C++/CLI), so the exception should ideally flow C# (callback) -> C/C++ (SomeOperation) -> C# (try catch block), however when it is thrown program pretty much freezes. Is it possible for that exception to reach the final C# try catch block (out of the box without workarounds)? I know that catching non-CLS compliant exceptions is possible (Can you catch a native exception in C# code?), but in this case I'm throwing a CLS compliant exception passing native boundary.

MichalK
  • 126
  • 3
  • If you break your application in a debugger, where is the thread stopped? If you turn on "break when thrown" in the exception settings, do you stop in your callback? I would kind of have expected this to just work, but since native code is involved, just about anything can happen if there is a mistake anywhere. – JonasH Sep 08 '22 at 08:50
  • After the exception is thrown the debugger pretty much goes as much as it can before the native boundary and and then (when passing this boundary) inside native debugger it just sits somewhere in CLR dlls (explicit thread pause) without moving out of it, it just looks like it is stuck in the marshalling process – MichalK Sep 08 '22 at 08:58
  • Having exceptions "leave" a .dll is a really bad thing to do, even if it works. – Eric Sep 08 '22 at 09:03
  • @Eric casting exceptions between code in different .Net assemblies is completely normal, but things might be different in c++. – JonasH Sep 08 '22 at 09:25
  • @JonasH I am talking about throwing exceptions in a native unmanaged dll and trying to catch them in the host application. C++ exceptions are not even compatible across different C++ compilers. And most programming languages are not able to catch something that is thrown by another language at all. .Net assemblies might use the .dll file extensions but they are something completely different. – Eric Sep 08 '22 at 10:42
  • @Eric it is true however to be specify situation a little bit I'm only using MSVC under windows environment so it can be narrowed down. – MichalK Sep 08 '22 at 11:35

1 Answers1

0

Throwing an exception across a managed-to-native boundary and expecting it to reach the native-to-managed boundary is a recipe for disaster. The C++ code would have to be have been written to take into account SEH exceptions and safely unwind, which a lot of C++ code isn't.

Instead, it's probably easier to have the callback save the excption and then rethrow it. You may want the callback to return a bool as to whether to continue the native function.

Exception _ex;

private bool ActualCallback([...])
{
    try
    {
        // do stuff
        if (someCondition)
            throw new Exception();
        // more stuff
        return true;
    }
    catch(Exception ex)
    {
        _ex = ex;
        return false;
    }
}

private void DoStuffNatively()
{
    _ex = null;
    SetCallback([...], callback);
    try
    {
        SomeOperation([...]);
        if(_ex != null)
            System.Runtime.ExceptionServices.ExceptionDispatchInfoCapture(_ex).Throw();
    }
    catch(Exception e)
    {
        [...] // process exception
    }
}
Charlieface
  • 52,284
  • 6
  • 19
  • 43
  • Yep, thanks for the answer I actually use this workaround I just was curious if it was even possible to even do it without workarounds. – MichalK Sep 09 '22 at 08:30
  • There's very little documentation on it (and I'm not a C/C++ programmer) but I believe you must use SEH exceptions. General best practice is for code at the native/managed boundary to just return an error code (possibly via an `out` parameter or using `SetLastError`) this is what all Win32 functions do. – Charlieface Sep 09 '22 at 08:38