I am testing the following code.
using System;
using System.Runtime.InteropServices;
class Program
{
[DllImport("libupcall.so")]
public static extern void register_callback(Callback cb);
[DllImport("libupcall.so")]
public static extern void upcall();
public static void MyStaticCallback(int val)
{
throw new Exception("error");
}
public delegate void Callback(int var);
static Callback cbMyStaticCallback = MyStaticCallback;
static void Main(string[] args)
{
try {
register_callback(MyStaticCallback);
upcall();
} catch (Exception e) {
Console.WriteLine("Catch clause caught : {0} \n", e.Message);
}
}
}
libupcall.so is based on the following C source code
#include <stdio.h>
typedef void (*callback)(int val);
callback gCB;
extern void register_callback(callback cb)
{
printf("register_callback:%p ----\n", (void *)cb);
gCB = cb;
}
extern void upcall()
{
static int c = 0;
printf("upcall ----\n");
gCB(c++);
}
dotnet run exits with unhandled exception as follows.
twoflower@js2-desktop:~/dev/complexexception$ sudo dotnet run
register_callback:0x7f3a041f407c ----
upcall ----
Unhandled Exception: System.Exception: error
at Program.MyStaticCallback(Int32 val) in /home/twoflower/dev/complexexception/Program.cs:line 14
at Program.upcall()
at Program.Main(String[] args) in /home/twoflower/dev/complexexception/Program.cs:line 24
twoflower@js2-desktop:~/dev/complexexception$
here is corefile bt.
* thread dotnet/coreclr#1: tid = 0, 0x00007fda93b26428 libc.so.6`__GI_raise(sig=6) + 56 at raise.c:54, name = 'dotnet', stop reason = signal SIGABRT
* frame #0: 0x00007fda93b26428 libc.so.6`__GI_raise(sig=6) + 56 at raise.c:54
frame dotnet/coreclr#1: 0x00007fda93b2802a libc.so.6`__GI_abort + 362 at abort.c:89
frame dotnet/coreclr#2: 0x00007fda9326258c libcoreclr.so`??? + 124
frame dotnet/coreclr#3: 0x00007fda9326148b libcoreclr.so`??? + 235
frame dotnet/coreclr#4: 0x00007fda92f0b893 libcoreclr.so`??? + 531
frame dotnet/coreclr#5: 0x00007fda92f0cd71 libcoreclr.so`??? + 593
frame dotnet/coreclr#6: 0x00007fda92fcaae3 libcoreclr.so`??? + 51
frame dotnet/coreclr#7: 0x00007fda93ecb263 libgcc_s.so.1`_Unwind_RaiseException(exc=0x0000000002467530) + 115 at unwind.inc:113
frame dotnet/coreclr#8: 0x00007fda9446790c libstdc++.so.6`__cxa_throw + 92
frame dotnet/coreclr#9: 0x00007fda9322749d libcoreclr.so`??? + 77
frame dotnet/coreclr#10: 0x00007fda92fd781e libcoreclr.so`??? + 273
frame dotnet/coreclr#11: 0x00007fda88e4b7b2 libupcall.so`upcall + 45 at upcall.c:16
frame dotnet/coreclr#12: 0x00007fda19d85ae6
frame dotnet/coreclr#13: 0x00007fda19d8588e
frame dotnet/coreclr#14: 0x00007fda92fd67b7 libcoreclr.so`??? + 124
frame dotnet/coreclr#15: 0x00007fda92eec630 libcoreclr.so`??? + 1264
Is this normal behavior that catch block on Main function not catch exception ?
Exception crossing managed to native boundary (like in your case when a callback is called from native code and it throws an exception) are intentionally not supported on dotnet core. You'll need to catch all exceptions in your callbacks that you call from native code.
One of the reasons is that we don't really know how to propagate exception through the native code that called the managed callback since we have no idea what kind of code was it. It could have been C code, C++ code, ASM code or in fact any other language code. If we just skipped those frames and it was e.g. C++, we would not be calling destructors of stack objects, thus possibly leaking memory or abandoning locked locks etc. If we attempted to throw some predefined C++ exception through that, the native code could swallow it unintentionally. Or it can even break some runtimes that are not ready to process exceptions.
Thank you very much.
Most helpful comment
One of the reasons is that we don't really know how to propagate exception through the native code that called the managed callback since we have no idea what kind of code was it. It could have been C code, C++ code, ASM code or in fact any other language code. If we just skipped those frames and it was e.g. C++, we would not be calling destructors of stack objects, thus possibly leaking memory or abandoning locked locks etc. If we attempted to throw some predefined C++ exception through that, the native code could swallow it unintentionally. Or it can even break some runtimes that are not ready to process exceptions.