gircore / gir.core

A C# binding generator for GObject based libraries providing a C# friendly API surface
https://gircore.github.io/
MIT License
307 stars 28 forks source link

Support dealing with unhandled exceptions from signal callbacks #845

Open cameronwhite opened 1 year ago

cameronwhite commented 1 year ago

In GtkSharp, the bindings caught any unhandled exceptions from callbacks (e.g. https://github.com/GtkSharp/GtkSharp/blob/c6a2696d2288375c43486ba7ae2018ed027b16c5/Source/Libs/GLibSharp/SignalClosure.cs#L181 and https://github.com/GtkSharp/GtkSharp/blob/develop/Source/Libs/GLibSharp/ExceptionManager.cs) and raised an event that allowed the application to log or display errors (Example from Pinta: https://github.com/PintaProject/Pinta/blob/5428fa04d3ca4562980e702b1bc568f9ec0b1bb7/Pinta/Main.cs#L137)

Otherwise, it seems like these unhandled exceptions will just terminate the program once they hit the native code boundary that invoked the delegate, so an outer try/catch block around application.Run() doesn't seem to work

Example program:

using System;

void HandleClick(Gtk.Button sender, System.EventArgs args)
{
    throw new System.NotImplementedException();
}

var application = Gtk.Application.New("org.gir.core", Gio.ApplicationFlags.FlagsNone);
application.OnActivate += (sender, args) =>
{
    var window = Gtk.ApplicationWindow.New((Gtk.Application) sender);
    window.Title = "Gtk4 Window";
    window.SetDefaultSize(300, 300);

    var button = Gtk.Button.NewWithLabel("Press me");
    button.OnClicked += HandleClick;
    window.SetChild(button);

    window.Show();
};

try
{
    return application.Run();
}
catch (Exception e)
{
    System.Console.WriteLine("unhandled exception {0}", e);
}

return 1;
badcel commented 1 year ago

This probably affects regular callbacks, too.

cameronwhite commented 1 year ago

yep, I think it probably applies for any delegates that get passed into and then called by native code

badcel commented 1 year ago

Fixed for signal callbacks in #861

cameronwhite commented 1 year ago

One thing that I noticed in #862 is that return types (or out parameters) add some complexity for exception handling in callbacks. Since the exception is swallowed and then control returns to the native function, the native code needs to have a valid return value since it's proceeding as usual. So, we probably need to set the return value to some default value if the managed callback has an unhandled exception

In GtkSharp, it looks like they just did this for bool return types (and void, of course), but if there was any other return type, or an out parameter, they just gave up and treated the unhandled exception as a fatal error and terminated.