Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I searched around SO and found various related questions, some answered with essentially "don't do that."

I want to call some unmanaged C++ code that accesses various existing C++ code. The existing code may have a variety of error conditions that I want to map into C# exceptions. From doing something similar in Java and JNI, it seemed that it might be possible to have a delegate function to raise defined exceptions, which could then be called directly from unmanaged code. The calls then look like (csharp)->(unmanaged)->(csharp delegate,throw/set pending exception) and then return back up.

The code below seems to work fine (vs2010, mono). My question is is there any problem with this approach - e.g. the spec says that the exception is not guaranteed to still be "pending" after unmanaged code is called, or threading issues, 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);
    } 
  }
}

Updated: Corrected test and output, showing mono and Windows (with /EHsc) leaks

// 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!
like image 612
Marvin Avatar asked Oct 28 '13 15:10

Marvin


People also ask

How to catch an exception in C#?

To catch an exception that an async task throws, place the await expression in a try block, and catch the exception in a catch block. Uncomment the throw new Exception line in the example to demonstrate exception handling. The task's IsFaulted property is set to True , the task's Exception.

How does CLR handle exception?

Exception, the CLR execution engine can raise exceptions, and unmanaged code can raise exceptions as well. Exceptions raised on a thread of execution follow the thread through native and managed code, across AppDomains, and, if not handled by the program, are treated as unhandled exceptions by the operating system.

How in C# we can ▪ handle exceptions by using the try catch and finally statements?

C# provides three keywords try, catch and finally to implement exception handling. The try encloses the statements that might throw an exception whereas catch handles an exception if one exists. The finally can be used for any cleanup work that needs to be done.

What happens if finally block throws an exception in C#?

A finally block always executes, regardless of whether an exception is thrown. The following code example uses a try / catch block to catch an ArgumentOutOfRangeException.


2 Answers

Yes, you can make this work as long as you run the code on Windows. Both C++ exceptions and .NET exceptions are built on top of the native SEH support provided by Windows. You will not have such guarantee on Linux or Apple operating systems however, a concern when you use Mono.

It is important that you build your C++ code with the correct settings, the MSVC++ compiler uses an optimization to avoid registering exception filters when it can see that code can never throw a C++ exception. That cannot work in your case, your RaiseException delegate target is going to throw one and the compiler has no chance at guessing at that. You must compile with /EHa to ensure your C++ destructors will be called when the stack is unwound. You'll find more details in this answer.

like image 169
Hans Passant Avatar answered Oct 16 '22 04:10

Hans Passant


If you're ever planning on running on Mono, the answer is simple:

Don't do it

No more code will be executed in the native methods the exception would unwind over. No cleanup, no C++ destructors, nothing.

On the other hand this means that if you're sure none of the native frames on the stack have any cleanup to do (if you're writing C++ code this can be harder than it looks), then you're free to throw managed exceptions at will.

The reason I so adamantly advice against doing this is because I once spent two days tracking down a memory leak because of exception handling unwinding though native frames. It is quite hard to track down and I was quite boggled for a while (breakpoints aren't hit, printfs don't print... but with the right tools it could have taken 5 minutes).

If you're still decided on throwing managed exceptions from native code, I'd do it just 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 in those methods, since it's too easy to get into automatic memory management in C++ that would still leak:

void native_function_called_by_managed_code ()
{
    bool result;
    MyCustomObject obj;

    /* your code */

    if (!result)
        throw_managed_exception ();
}

This could leak because MyCustomObject's destructor isn't called.

like image 36
Rolf Bjarne Kvinge Avatar answered Oct 16 '22 04:10

Rolf Bjarne Kvinge