[Geany-Devel] New plugin loader mechanisms

Thomas Martitz kugel at xxxxx
Wed Mar 18 16:42:38 UTC 2015


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 

tl;dr <-
Key functions

gboolean geany_load_module(GeanyPlugin *, GModule *)
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);

These are analogous and semantically equivalent to the current 
functions. The only difference is the parameters that are passed to 
them, and of course how they are registed. Additionally the existing 
PluginCallbacks pointer registered here for all plugins that want 
statically defined signal handlers.

There is no version_check() hook because that's now done inside geany, 
and the long deprecated configure_single is phased out. Please also note 
that each hook receives the GeanyPlugin pointer and the userdata 
pointer, these are the same passed to geany_plugin_register.

With this new mechanism, proxy plugins are more than doable. geanypy 
should benifit as well.

I have implemented this so far and adapted the demo plugin. The code is 
at[1]. Please have a look and/or comment on my proposal or contribute 
your ideas. Thanks!

[1] https://github.com/kugel-/geany/tree/new-plugins

Best regards

More information about the Devel mailing list