diff --git a/src/lib/plugin.c b/src/lib/plugin.c new file mode 100644 index 000000000..18c4b7630 --- /dev/null +++ b/src/lib/plugin.c @@ -0,0 +1,159 @@ +#include +#include +#include +#include + +#include "plugin.h" + +NOBUG_DEFINE_FLAG (cinelerra_plugin); + + +struct cinelerra_plugin +{ + const char* name; /*TODO store full and short name, sort by shortname?*/ + + /* use count for all interfaces of this plugin */ + unsigned use_count; + + time_t last; + + /* dlopen handle */ + void* handle; +}; + +/* global plugin registry */ +void* cinelerra_plugin_registry = NULL; + + +static int +cmp_plugin_name (const void* a, const void* b) +{ + return strcmp (((struct cinelerra_plugin*) a)->name, ((struct cinelerra_plugin*) b)->name); +} + +struct cinelerra_interface* +cinelerra_interface_open (const char* name, const char* interface, size_t min_revision) +{ + REQUIRE (min_revision > sizeof(cinelerra_interface), "try to use an empty interface eh?"); + REQUIRE (interface, "interface name must be defined"); + + struct cinelerra_plugin plugin; + struct cinelerra_plugin** found; + + // TODO cook somewhere (pluginpath+name+.so) + plugin.name = name; + + found = tsearch (&plugin, &cinelerra_plugin_registry, cmp_plugin_name); + if (!found) + return NULL; + + if (*found == &plugin) + { + NOTICE (cinelerra_plugin, "new plugin"); + + /* create new item */ + *found = malloc (sizeof (struct cinelerra_plugin)); + if (!*found) + goto ealloc1; + + (*found)->name = strdup (name); + if (!(*found)->name) + goto ealloc2; + + (*found)->use_count = 0; + + (*found)->handle = dlopen (name, RTLD_NOW|RTLD_LOCAL); + if (!(*found)->handle) + { + ERROR (cinelerra_plugin, "dlopen failed: %s", dlerror()); + goto edlopen; + } + + /* if the plugin defines a 'cinelerra_plugin_init' function, we call it, must return !0 on success */ + int (*init)(void) = dlsym((*found)->handle, "cinelerra_plugin_init"); + if (init && init()) + { + ERROR (cinelerra_plugin, "cinelerra_plugin_init failed: %s: %s", name, interface); + goto einit; + } + } + /* we have the plugin, now get the interface descriptor */ + struct cinelerra_interface* ret; + + ret = dlsym ((*found)->handle, interface); + TODO ("need some way to tell 'interface not provided by plugin', maybe cinelerra_plugin_error()?"); + if (!ret) + { + ERROR (cinelerra_plugin, "plugin %s doesnt provide interface %s", name, interface); + goto edlsym; + } + + /* is the interface older than required? */ + if (ret->size < min_revision) + { + ERROR (cinelerra_plugin, "plugin %s provides older interface %s revision than required", name, interface); + goto erevision; + } + + ret->plugin = *found; + + /* if the interface provides a 'open' function, call it now, must return !0 on success */ + if (ret->open && ret->open()) + { + ERROR (cinelerra_plugin, "open hook indicated an error"); + goto eopen; + } + + (*found)->use_count++; + ret->use_count++; + + return ret; + + /* Error cleanup */ + einit: + dlclose((*found)->handle); + eopen: + erevision: + edlsym: + edlopen: + free ((char*)(*found)->name); + ealloc2: + free (*found); + ealloc1: + *found = &plugin; + tdelete (&plugin, &cinelerra_plugin_registry, cmp_plugin_name); + return NULL; +} + +void +cinelerra_interface_close (struct cinelerra_interface* self) +{ + if(!self) + return; + + struct cinelerra_plugin* plugin = self->plugin; + + plugin->use_count--; + self->use_count--; + + /* if the interface provides a 'close' function, call it now */ + if (self->close) + self->close(); + + /* plugin not longer in use, unload it */ + if (!plugin->use_count) + { + TODO ("we dont want to close here, instead store time of recent use and make a expire run, already planned in my head"); + + /* if the plugin defines a 'cinelerra_plugin_destroy' function, we call it */ + int (*destroy)(void) = dlsym(plugin->handle, "cinelerra_plugin_destroy"); + if (destroy) + destroy(); + + /* and now cleanup */ + tdelete (plugin, &cinelerra_plugin_registry, cmp_plugin_name); + free ((char*)plugin->name); + dlclose(plugin->handle); + free (plugin); + } +} diff --git a/src/lib/plugin.h b/src/lib/plugin.h new file mode 100644 index 000000000..0cb7426a7 --- /dev/null +++ b/src/lib/plugin.h @@ -0,0 +1,112 @@ +#ifndef CINELERRA_PLUGIN_H +#define CINELERRA_PLUGIN_H + +#ifdef __cplusplus +extern "C" { +#elif 0 +} /*eek, fixes emacs indenting for now*/ +#endif + +#include +#include + +NOBUG_DECLARE_FLAG (cinelerra_plugin); + +/* tool macros*/ +#define CINELERRA_INTERFACE_TYPE(name, version) struct cinelerra_interface_##name##_##version +#define CINELERRA_INTERFACE_CAST(name, version) (CINELERRA_INTERFACE_TYPE(name, version)*) + +/* Interface definition */ +#define CINELERRA_INTERFACE(name, version, ...) \ +CINELERRA_INTERFACE_TYPE(name, version) \ +{ \ + struct cinelerra_interface interface_header_; \ + __VA_ARGS__ \ +} + +#define CINELERRA_INTERFACE_PROTO(ret, name, params) ret (*name) params; + +/* Interface instantiation */ +#define CINELERRA_INTERFACE_IMPLEMENT(iname, version, name, open, close, ...) \ +CINELERRA_INTERFACE_TYPE(iname, version) name##_##version = \ +{ \ + { \ + version, \ + sizeof(CINELERRA_INTERFACE_TYPE(iname, version)), \ + NULL, \ + 0, \ + open, \ + close \ + }, \ + __VA_ARGS__ \ +} + +#define CINELERRA_INTERFACE_FUNC(protoname, funcname) funcname, + + + +/* internally used */ +struct cinelerra_plugin; + +/** + * This is the header for any interface exported by plugins. + * Real interfaces append their functions at the end. There are few standard functions on each Interface + * Every function is required to be implemnted. + */ +struct cinelerra_interface +{ + /// interface version number + unsigned version; + /// size of the full structure is used to determine the revision (adding a new function increments the size) + size_t size; + /// back reference to plugin + struct cinelerra_plugin* plugin; + /// incremented for each user of this interface and decremented when closed + unsigned use_count; + + /// called for each open of this interface + int (*open)(void); + /// called for each close of this interface + int (*close)(void); +}; + +/** + * Make an interface available. + * To use an interface provided by a plugin it must be opened first. It is allowed to open an interface more than once. + * Each open must be paired with a close. + * @param plugin name of the plugin to use. + * @param name name of the interface to open. + * @param min_revision the size of the interface structure is used as measure of a minimal required revision (new functions are appended at the end) + * @return handle to the interface or NULL in case of a error. The application shall cast this handle to the actual interface type. + */ +struct cinelerra_interface* +cinelerra_interface_open (const char* plugin, const char* name, size_t min_revision); + + +/** + * Close an interface. Does not free associated resources + * Calling this function with self==NULL is legal. Every interface handle must be closed only once. + * @param self interface to be closed + */ +void +cinelerra_interface_close (struct cinelerra_interface* self); + +/** + * Tries to unload a plugin. + * When the Plugin is unused, then all resources associated with it are freed and it will be removed from memory + * @param plugin name of the plugin to be unloaded. + * @return 0 on success, else the number if users which keeping the plugin loaded + */ +int +cinelerra_plugin_unload (const char* plugin); + +void +cinelerra_plugin_expire (time_t age); + +const char* +cinelerra_plugin_error (); + +#ifdef __cplusplus +} /* extern "C" */ +#endif +#endif /* CINELERRA_PLUGIN_H */