Plugin Programming Interface
Plugins are all about interfaces. The basic notion of plugin-based system is that there is some central system that loads plugins it knows nothing about and communicates with them through well-defined interfaces and protocols.
The naive approach is to define a set of functions as the interface that the plugin exports (either dynamic or static library). This approach is technically possible but conceptually it is flawed. The reason is that there are two kinds of interfaces a plugin should support and can be only a single set of functions exported from a plugin. This means that both kinds of interface will be mixed together.
The first interface (and protocol) is the generic plugin interface. It lets the central system initialize the plugin, and lets the plugin register with the central system various functions for creating and destroying objects as well as global cleanup function. The generic plugin interface is not domain-specific and can be specified and implemented as a reusable library. The second interface is the functional interface implemented by the plugin objects. This interface is domain-specific and must be carefully designed and implemented by the actual plugins. The central system should be aware of this interface and interact with the plugin objects through it.
Listing One is the header file that specifies the generic plugin interface. Without delving into the details and explaining anything just yet let's just see what it offers.
#ifndef PF_PLUGIN_H #define PF_PLUGIN_H #include <apr-1/apr_general.h> #ifdef __cplusplus extern "C" { #endif typedef enum PF_ProgrammingLanguage { PF_ProgrammingLanguage_C, PF_ProgrammingLanguage_CPP } PF_ProgrammingLanguage; struct PF_PlatformServices_; typedef struct PF_ObjectParams { const apr_byte_t * objectType; const struct PF_PlatformServices_ * platformServices; } PF_ObjectParams; typedef struct PF_PluginAPI_Version { apr_int32_t major; apr_int32_t minor; } PF_PluginAPI_Version; typedef void * (*PF_CreateFunc)(PF_ObjectParams *); typedef apr_int32_t (*PF_DestroyFunc)(void *); typedef struct PF_RegisterParams { PF_PluginAPI_Version version; PF_CreateFunc createFunc; PF_DestroyFunc destroyFunc; PF_ProgrammingLanguage programmingLanguage; } PF_RegisterParams; typedef apr_int32_t (*PF_RegisterFunc)(const apr_byte_t * nodeType, const PF_RegisterParams * params); typedef apr_int32_t (*PF_InvokeServiceFunc)(const apr_byte_t * serviceName, void * serviceParams); typedef struct PF_PlatformServices { PF_PluginAPI_Version version; PF_RegisterFunc registerObject; PF_InvokeServiceFunc invokeService; } PF_PlatformServices; typedef apr_int32_t (*PF_ExitFunc)(); typedef PF_ExitFunc (*PF_InitFunc)(const PF_PlatformServices *); #ifndef PLUGIN_API #ifdef WIN32 #define PLUGIN_API __declspec(dllimport) #else #define PLUGIN_API #endif #endif extern #ifdef __cplusplus "C" #endif PLUGIN_API PF_ExitFunc PF_initPlugin(const PF_PlatformServices * params); #ifdef __cplusplus } #endif #endif /* PF_PLUGIN_H */
The first thing you should notice is that it is a C file. This allows the plugin framework to be compiled and used by pure C systems and to write pure C plugins. But, it is not limited to C and is actually designed to be used mostly from C++.
The PF_ProgrammingLanguage
enum allows plugins to declare to the plugin manager if they are implemented in C or C++.
The PF_ObjectParams
is an abstract struct that is passed to created plugin objects.
The PF_PluginAPI_Version
is used to negotiate versioning and make sure that the plugin manager loads only plugins with compatible version.
The functions pointer definitions PF_CreateFunc
and PF_DestroyFunc
(implemented by the plugin) allow the plugin manager to create and destroy plugin objects (each plugin registers such functions with the plugin manager)
The PF_RegisterParams
struct contains all the information that a plugin must provide to the plugin manager upon initialization (version, create/destroy functions, and programming language).
The PF_RegisterFunc
(implemented by the plugin manager) allows each plugin to register a PF_RegisterParams
struct for each object type it supports. Note that this scheme allows a plugin to register different versions of an object and multiple object types.
The PF_InvokeService
function pointer definition is a generic function that plugins can use to invoke services of the main system like logging, event notification and error reporting. The signature includes the service name and an opaque pointer to a parameters struct. The plugins should know about available services and how to invoke them (or you can implement service discovery if you wish using PF_InvokeService
).
The PF_PlatformServices
struct aggregates all the services I just mentioned that the platform provides to plugin (version, registering objects and the invoke service function). This struct is passed to each plugin at initialization time.
The PF_ExitFunc
is the definition of the plugin exit function pointer (implemented by the plugin).
The PF_InitFunc
is the definition of the plugin initialization function pointer.
The PF_initPlugin
is the actual signature of the plugin initialization function of dynamic plugins (plugins deployed in dynamically linked libraries/shared libraries). It is exported by name from dynamic plugins, so the plugin manager will be able to call it when loading the plugin. It accepts a pointer to a PF_PlatformServices
struct, so all the services are immediately available upon initialization (this is the right time to register objects) and it returns a pointer to an exit function.
Note that static plugins (plugins implemented in static libraries and linked directly to the main executable) should implement an init
function with C linkage too, but MUST NOT name it PF_initPlugin
. The reason is that if there are multiple static plugins, they will all have a function with the same name and your compiler will hate it.
Static plugins initialization is different. They must be initialized explicitly by the main executable that will call their initialization function with the PF_InitFunc
signature. This is unfortunate because it means the main executable needs to be modified whenever a new static plugin is added/removed and also the names of the various init
functions must be coordinated.
There is a technique called "auto-registration" that attempts to solve the problem. Auto-registration is accomplished by a global object in the static library. This object is supposed to be constructed before the main()
even starts. This global object can request the plugin manager to initialize the static plugin (passing the plugin's init()
function pointer). Unfortunately, this scheme doesn't work in Visual C++.