[Geany-Devel] New plugin loader mechanisms

Matthew Brush mbrush at xxxxx
Wed Mar 18 21:55:32 UTC 2015

On 15-03-18 09:42 AM, Thomas Martitz wrote:
> Hello,
> tl;dr -> scroll down
> I am working on a new plugin architecture that deals with some of the
> shortcomings of the current state. My primary motivation is to be able
> to use libpeas to load plugins, both C and non-C (Python!), as you might
> have learned from other threads I started. However the situation can be
> improved regardless of that goal.
> List of current shortcomings:
>   - (A separate change but nevertheless: ) Currently geany exports a
> pointer to a struct, that contains more structs, which contain function
> points to the API functions. Fortunately this is nicely hidden to
> developers via macros. But due to gtkbuilder all functions and nothing
> prevents plugins from accessing these. And the macros are awkward and
> strange anyway. There is currently the linkage-cleanup PR in the works
> which improves this by actually exporting the API functions, and _only_
> the API functions to plugins.
>   - Global symbols. Plugins binaries have to export a number of global
> symbols (geany_{functions,data,plugin}, plugin_{init,...,cleanup}). This
> kind of sucks, because they pollute the global namespace (in theory).
> Luckily on unix or win32 systems this is not a problem because they can
> restrict the symbol visibility of shared libraries. It's still bad
> practice. Ideally plugins should have zero global symbols, everything
> being static or hidden to the plugin binary.
>   - The plugin entry points / callbacks are inconsistent w.r.t to the
> parameters they receive, and none receive some kind of a plugin handle
> referencing to the plugin itself (there is only the geany_plugin global).
>   - The plugin entry points / callbacks do not allow for the plugin
> associate private/user data with the plugin handle, except hand-maintain
> hash tables. This is not a problem for the most part because it can be
> stored in some plugin-wide static variable, however it does become
> problematic when you attempt to have one plugin act as a proxy for other
> plugins (see geanypy or my pluxy effort)
>   - The plugin does the ABI/API verification. We currently trust the
> plugins to use PLUGIN_VERSION_CHECK() or otherwise implement
> plugin_version_check() correctly. Plugins decide whether they are
> api/abi compatible with geany. Pure crazyness!
>   - Plugins cannot register plugins recursively. It would be awesome if
> a plugin could register a plugin on behalf of others, in such a manner
> that they appear in the PM dialog and can be handled by the user like
> normal plugins (*proper* proxy plugins are not possible).
> To improve the situation I propose the following mechaism and new plugin
> hooks:
> tl;dr <-
> Key functions
> gboolean geany_load_module(GeanyPlugin *, GModule *)

What is the GModule* for? Is it a .dll that Geany opened on behalf of 
the plugin based on selection in Plugin Manager?

> gboolean geany_plugin_register(GeanyPlugin *, gint api, gint abi,
> PluginHooks *(see below), gpointer)
> The plugin defines a single global function,
> geany_load_module(GeanyPlugin *, GModule *). This is the only function
> that geany learns about using g_module_symbol(). And the only thing this
> function ought to do is to call geany_plugin_register(). This does 4 things
> 1) Provide the plugin handle to the plugin very early
> 2) Perform abi and abi checks, so this is finally done inside geany.
> Added bonus is that geany knows the requested api and can possibly apply
> backcompat workarounds on a per plugin basis (instead of globally), warn
> about very old plugins or even deny loading them.
> 3) Register the remaining hooks and callbacks (see below)
> 4) Associate a userdata pointer to the plugin, geany will pass this
> pointer back to future calls into the plugin (good for proxies)
> In the future geany_plugin_register should be able to be used to
> register plugins recursivly, by passing the appropriate GeanyPlugin
> pointer, i.e. plugin A should be able to call
> geany_plugin_register(plugin_B, ...) to realize pluxies.
> Now to the plugin hooks:
> typedef struct _PluginHooks
> {
>      PluginCallback *callbacks;
>      void        (*set_info)  (GeanyPlugin *plugin, gpointer pdata);
>      void        (*init)      (GeanyPlugin *plugin, gpointer pdata);
>      GtkWidget*  (*configure) (GeanyPlugin *plugin, GtkDialog *dialog,
> gpointer pdata);
>      void        (*help)      (GeanyPlugin *plugin, gpointer pdata);
>      void        (*cleanup)   (GeanyPlugin *plugin, gpointer pdata);
> }
> PluginHooks;

What if instead of PluginHooks it was called `Plugin` (GeanyPlugin is 
taken, so for this discussion I'll use `Plugin` :) and instead of just 
the callback function pointers it contained the (possibly sub-)plugin's 
info, like this:

typedef struct
   const char *name;
   const char *version;
   const char *author;
   const char *description;
   unsigned    api_min;
   unsigned    abi_ver;
   void       *plugin_data; // pdata/plugin context

   bool      (*init) (Plugin*);
   GtkWidget (*configure) (Plugin,GtkDialog*);
   gchar*    (*help) (Plugin*); // if not NULL ret, show URL in browser
   bool      (*deinit) (Plugin*); // could signal unloading problem

Then the "register" function could be like:

bool plugin_register (GeanyPlugin *module, // geany's plugin context
                       Plugin      *plugin); // real/sub-plugin ctx

Inside a normal plugin it could do:

static Plugin my_plugin = {
   .name        = "Foo",
   .version     = "0.1",
   .author      = "Me",
   .description = "Foo plugin",
   .api_min     = 200,
   .abi_ver     = GEANY_ABI_VERSION,
   .init        = my_plugin_init,
   .configure   = my_plugin_configure,
   .help        = my_plugin_help,
   .deinit      = my_plugin_cleanup,

bool plugin_load_module (GeanyPlugin *module_plugin)
   return plugin_register (module_plugin, &my_plugin);

Or for a proxying-type plugin (ex. GeanyPy/GeanyLua):

bool plugin_load_module (GeanyPlugin *module_plugin)
   Plugin *plug = g_new0 (Plugin, 1);
   *plug = my_plugin;
   plug->name = "Joe's plugin";
   plug->version = etc...
   plug->plugin_data = PyObject_New(...); // or lua_new() or whatever
   return plugin_register (module_plugin, plug);

Just some ideas based on yours and mine previous work around this. 
There's many ways to skin this cat :)

Matthew Brush

More information about the Devel mailing list