Skip to content

RFC: Re-loadable plugin callbacks and nested plugins. #39

@Neopallium

Description

@Neopallium

I have started a branch symbols in my repos:
https://github.com/Neopallium/cr/tree/symbols

It has added support for:

  1. Re-loadable custom plugin symbols cr_symbol. It is an opaque structure that holds a pointer to a symbol from the plugin. When the plugin reloads, all re-loadable symbols are updated.
  2. Support for safely calling plugin callbacks. cr_closure
  3. Nested setjmp protected calls (Allows nesting of plugins). This is only needed on linux/macOS, windows uses __try/__except which doesn't use a global and "should" be nesting safe?

Limits:

  1. Don't call into the same plugin more then once in a call chain (i.e. Plugin A -> Plugin B -> Plugin A "crash", would cause Plugin A to possible reload while it is still on the call stack.)

TODOS:

  • Doesn't free cr_symbol or cr_closure. I plan to add reference counting of cr_symbol
  • Always creates a new cr_symbol, should check if one already exists for the requested symbol.
  • Tests
  • Untested on windows/macOS.
  • Add cr_closure_free to release allocated cr_closure and release reference to cr_symbol
  • Add cr_closure_init to allow embedding cr_closure inside the callback's userdata object.
  • Try adding a "protected call" counter to cr_plugin to prevent reload/rollback while a protected call is active for that plugin. Or try to longjmp/throw back to the first protected call.

New public API:

struct cr_symbol *cr_plugin_get_symbol(struct cr_plugin *ctx, const char *name)

Returns a reference to a re-loadable plugin symbol.

struct cr_closure *cr_plugin_new_closure(struct cr_symbol *symbol, void *userdata)

Used to wrap the plugin's callback and userdata, so it can be safely unwrapped and called from host-side "trampoline" function.

Example (See working example in repos: samples/fake_gui.*, samples/cb_host.*, samples/cb_guest.c):

  • Fake GUI event callback API
typedef int (*cb_mouse_event_func)(void *userdata, int x, int y, int buttons);
int fake_register_mouse_events(cb_mouse_event_func cb, void *userdata);
int fake_send_mouse_event(int x, int y, int buttons);
  • Host-side support
// We need a "trampoline" function that will never be unloaded/reloaded.
// Only need one for each kind of callback function (typedef)
// This can be defined in a small common shared library that is dynamically linked into the host and plugins.
int host_trampoline_mouse_events(void *userdata, int x, int y, int buttons) {
  CR_CLOSURE_CALL_START(cb_mouse_event_func, userdata, true)
  if (cb_func) {
    return cb_func(closure->userdata, x, y, buttons);
  } else {
    fprintf(stdout, "No plugin callback.  Can happen when the plugin is closed `cr_plugin_close`\n");
  }
  CR_CLOSURE_CALL_END

  return 0;
}

///// Normal host-side code follows.....
  • Guest plugin callback
static struct cr_symbol * CR_STATE g_plugin_mouse_cb = NULL;
static void register_mouse_events(struct cr_plugin *ctx, void *userdata) {
  if (!g_plugin_mouse_cb) {
    // Only need to initialize `g_plugin_mouse_cb` on the first `CR_LOAD`
    g_plugin_mouse_cb = cr_plugin_get_symbol(ctx, "plugin_mouse_events");

    // We can create any number of `cr_closure` as needed and re-use `cr_symbol`
    struct cr_closure *closure = cr_symbol_new_closure(g_plugin_mouse_cb, userdata);
    fake_register_mouse_events(host_trampoline_mouse_events, closure);
  }
}

// Plugin callback for mouse events.
CR_EXPORT int plugin_mouse_events(void *userdata, int x, int y, int buttons) {
  struct cr_plugin *ctx = (struct cr_plugin *)userdata;
  fprintf(stdout, "Guest: mouse event: (%d, %d, 0x%x): ver=%d\n", x, y, buttons);
  return 0;
}

CR_EXPORT int cr_main(struct cr_plugin *ctx, enum cr_op operation) {
  if (operation == CR_LOAD) {
    // Reload/rollback unsafe register of mouse events callback
    //fake_register_mouse_events(plugin_mouse_events, ctx);
    // Reload/rollback safe.
    register_mouse_events(ctx, ctx);  // We are just using `ctx` as our plugin userdata.
  }
  return 0;
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions