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
_______________________________________________
Devel mailing list
Devel@lists.geany.org
https://lists.geany.org/cgi-bin/mailman/listinfo/devel