Can a .Net exception be thrown from unmanaged code using a delegate function?

I searched around SO and found various related questions, some answered essentially “don't do this”.

I want to call some unmanaged C ++ code that accesses various existing C ++ codes. Existing code can have a lot of errors that I want to display in C # exceptions. Due to the fact that something like this in Java and JNI seemed possible, the delegate function might raise certain exceptions that could then be called directly from unmanaged code. After that, the calls look like (csharp) -> (unmanaged) -> (csharp delegate, exception for exception / delayed wait), and then come back.

The code seems to be working fine (vs2010, mono). My question is if there is any problem with this approach - for example. the specification says that the exception is not guaranteed to be "pending" after calling unmanaged code or thread problems, etc.

// unmanaged.cpp #include <cstdio> #define EXPORT __declspec(dllexport) #define STDCALL __stdcall typedef void (STDCALL* raiseExcpFn_t)(const char *); extern "C" { // STRUCT ADDED TO TEST CLEANUP struct Allocated { int x; Allocated(int a): x(a) {} ~Allocated() { printf("--- Deleted allocated stack '%d' ---\n", x); fflush(stdout); } }; static raiseExcpFn_t exceptionRaiser = 0; EXPORT void STDCALL registerRaiseExcpFn(raiseExcpFn_t fun) { exceptionRaiser = fun; } EXPORT void STDCALL hello(const char * x) { Allocated a0(0); try { Allocated a1(1); printf("1 --- '%s' ---\n", x); fflush(stdout); (*exceptionRaiser)("Something bad happened!"); printf("2 --- '%s' ---\n", x); fflush(stdout); } catch (...) { printf("3 --- '%s' ---\n", x); fflush(stdout); throw; } printf("4 --- '%s' ---\n", x); fflush(stdout); } } // Program.cs using System; using System.Runtime.InteropServices; class Program { [DllImport("unmanaged.dll")] public static extern void registerRaiseExcpFn(RaiseException method); [DllImport("unmanaged.dll")] public static extern void hello([MarshalAs(UnmanagedType.LPStr)] string m); public delegate void RaiseException(string s); public static RaiseException excpfnDelegate = new RaiseException(RaiseExceptionMessage); // Static constructor (initializer) static Program() { registerRaiseExcpFn(excpfnDelegate); } static void RaiseExceptionMessage(String msg) { throw new ApplicationException(msg); } public static void Main(string[] args) { try { hello("Hello World!"); } catch (Exception e) { Console.WriteLine("Exception: " + e.GetType() + ":" + e.Message); } } } 

Update: Fixed test and output showing mono and Windows leaks (with / EHsc)

 // Observed output // with Release builds /EHa, VS2010, .Net 3.5 target //cstest.exe // --- Deleted allocated stack '0' --- // --- Deleted allocated stack '1' --- // 1 --- 'Hello World!' --- // 3 --- 'Hello World!' --- // Exception: System.ApplicationException:Something bad happened! // Observed LEAKING output // with Release builds /EHsc, VS2010, .Net 3.5 target // cstest.exe // 1 --- 'Hello World!' --- // Exception: System.ApplicationException:Something bad happened! // LEAKING output DYLD_LIBRARY_PATH=`pwd` mono program.exe // 1 --- 'Hello World!' --- // Exception: System.ApplicationException:Something bad happened! 
+7
c # exception mono unmanaged
source share
3 answers

Yes, you can do this work as long as you run the code on Windows. Both C ++ and .NET exceptions are based on the native SEH support provided by Windows. You will not have such a guarantee on Linux or Apple operating systems, however, the problem is when using Mono.

It is important that you build your C ++ code with the correct settings, the MSVC ++ compiler uses optimization to avoid registering exception filters when it can see that the code can never throw a C ++ exception. This may not work in your case, your delegation goal of RaiseException is about to throw one, and the compiler has no chance to guess about it. You must compile with / EHa to ensure that your C ++ destructors are called when the stack is unwound. You will find more details in this answer .

+4
source share

If you plan to work in Mono, the answer is simple:

Do not do this

Native methods will no longer execute code, and the exception will be deployed. No cleanup, no C ++ destructors, nothing.

On the other hand, this means that if you are sure that none of the native frames on the stack has any cleanup (if you write code in C ++, it may be more complicated than it seems), then you are free to quit managed exceptions at will.

The reason I so categorically advise doing this is because I once spent two days tracking a memory leak due to handling exceptions that unwind, although native frames. It’s hard to track, and I was stunned for a while (breakpoints weren’t affected, printfs don’t print ... but with the right tools it could take 5 minutes).

If you still decided to throw managed exceptions from your own code, I would do this before returning to managed code:

 void native_function_called_by_managed_code () { bool result; /* your code */ if (!result) throw_managed_exception (); } 

And I would limit myself to C with these methods, since it is too easy to get into automatic memory management in C ++, which will still leak:

 void native_function_called_by_managed_code () { bool result; MyCustomObject obj; /* your code */ if (!result) throw_managed_exception (); } 

This can lead to a leak due to the MyCustomObject destructor not being called.

+3
source share

You may have a problem with improper release of your own resources.

When an exception is thrown, the stack is unpacked until it finds the appropriate try-catch block.

This is all good and pleasant, but there are some side effects associated with the fact that they are in the middle of the native and managed.

In regular C #, all objects created in blocks on the path to exception will eventually be freed by the garbage collector. But Dispose () is not called unless you use a block.

In C ++, on the other hand, if you have your own exception, all objects created with new () are likely to hang around and you will have a memory leak, and objects on the stack will be destroyed correctly if the stack was unwound .

BUT, if you don't have / EHa, and you have a managed exception, it will only deploy the managed code. Thus, the built-in destructors of native objects created on the stack may not be called, and you may have memory leaks or, even worse, locks that cannot be unlocked ...

0
source share

All Articles