Skip to content

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

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;
}

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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