Basics Of Plugins
English 简体中文
By Girish Ramakrishnan, ForwardBias Technologies
Plugin overview
Plugins are a mechanism of extending existing programs. As an example, a Calculator program can extend it's list of supported operations by loading additional operators from plugins. Plugins allow third party developers to extend the calculator program without requiring access to the calculator program source code.
Creating plugins
In C, plugins are created using the following steps:
- The application defines an interface for plugins. These are a list of functions that the application expects to be implemented by a plugin.
- A plugin implements the interface and the code is compiled as a shared object.
- The application discovers the plugin, dynamically loads the plugin, resolves the symbols/functions in the plugin and calls the methods defined in the interface.
Let's take the example of a Calculator program that can be extended using plugins.
- The Calculator program defines the interface as:
// operatorinterface.h
double operation(double arg1, double arg2);
typedef double (*operation_pointer)(double, double);
The 'addition' plugin implements the interface as
// addition.c
#include "operatorinterface.h"
double operation(double arg1, double arg2)
{
return arg1 + arg2;
}
The plugin is compiled as a shared object using 'cc -shared -fPIC addition.c -o addition.so'.
- The Application discovers plugins at runtime by searching preconfigured paths for shared objects.
// application.c
#include <dlfcn.h>
#include "operatorinterface.h"
int main()
{
const char *plugin_path = "/path/to/plugin.so";
void *plugin = dlopen(plugin_path, RTLD_LAZY); // plugin_path is a pointer to any arbitrary plugin that supports the operatorinterface.h
operation_pointer ptr = (operation_pointer) dlsym(plugin, "operation"); // get pointer to the 'operation' function
ptr(10, 20); // invoke the plugin function with some arguments
}
Exporting symbols in C
A shared object may contain many functions but only some of them are meant to be exposed to external programs. Compilers provide mechanisms to mark the visibility of functions. In gcc, this is achieved by prefixing __attribute__((visibility("default"))). On MSVC, __declspec(dllexport) is prefixed to a function or a class.
The addition plugin should be written as:
// addition.c
#include "operatorinterface.h"
__attribute__((visibility("default"))) double operation(double arg1, double arg2)
{
return arg1 + arg2;
}
Exporting symbols in C++
In C, the mapping of names to symbols in standardized. C+, however, has no such standard and each compiler generates different symbol names for the same function name. This name mangling is required to support C+ function overloading, wherein many functions can have the same name but different signature.
Thus, in C+,
- The resolving of the symbol "operation" using dlsym, as in the example above, will fail if the plugin was compiled using a C+ compiler. The programmer will need to resolve the mangled name instead.
Resolving on mangled names is compiler dependent. If the plugin was compiled using a different compiler then even resolving using the mangled name will not work.
The trick to solve the above problems is to define the interface in C++ but have a single C function that returns a pointer to the interface. This C function is compiled using C-linkage (specified using extern "C"). For example, the above plugin can be implemented as C++ as follows:
// operatorinterface.h
class OperatorInterface {
public:
virtual double operation(double arg1, double arg2) = 0;
};
extern "C" __attribute__((visibility("default"))) OperatorInterface *getInterface();
typedef OperatorInterface *(*GetInterfacePointer)();
// addition.cpp
#include "operatorinterface.h"
class AdditionOperator : public OperatorInterface
{
public:
double operation(double arg1, double arg2) { return arg1 + arg2; }
};
extern "C" OperatorInterface *getInterface() { return new AdditionOperator; }
// application.cpp
#include "operatorinterface.h"
#include <dlfcn.h>
int main() {
void *plugin = dlopen(plugin_path, RTLD_LAZY); // plugin_path is a pointer to any arbitrary plugin that supports the operatorinterface.h
GetInterfacePointer getInterfacePointer = (GetInterfacePointer) dlsym(plugin, "getInterface"); // resolve the 'C' function
OperatorInterface *interface = getInterfacePointer(); // Use the 'C' function to get the C++ interface
interface->operation(10, 20); // invoke the plugin function with some arguments
}