[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
}
Plugin;
```
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,
};
G_MODULE_EXPORT
bool plugin_load_module (GeanyPlugin *module_plugin)
{
return plugin_register (module_plugin, &my_plugin);
}
```
Or for a proxying-type plugin (ex. GeanyPy/GeanyLua):
```
G_MODULE_EXPORT
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 :)
Cheers,
Matthew Brush
More information about the Devel
mailing list