Tuesday 26 March 2013

C++ plugin loading from shared object library

Here is a very simple C++ plugin loading class for unix/linux platforms. It is essentially a wrapper for the dlopen/dlsym functions from dlfcn.h. The objective here is to retrieve a known entry point within the library object to construct a plugin object. The application containing the plugin loader does not need to know the contents of the library, just how to access a specific plugin when required. The benefit of this set up is that the application and the plugin modules are totally decoupled, plugins can be added without the application needing to be re-linked.

The class PluginLoader is constructed with the path to a shared object library containing the actual plugin(s):
PluginLoader loader("[path to object library]");

The template function
 getSymbol (string symbol, T &value)
does the work of retrieving the entry point from the library. The idea is to pass a function pointer type which can be used to construct the plugin. This is preferable to returning a pointer to a created object as the application has full control over construction/destruction.

In the simple example below a plugin base class is created from which two plugins (PluginA and PluginB) are derived and must implement the pure virtual
outputMessage();
The functions to construct the objects are declared "extern C" as this prevents C++ name mangling.

extern "C" PluginBase* LOAD_PLUGIN_A() {
    return new PluginA();
}

Below is a simple example which extracts two plugins, constructs them and runs the outputMessage() method:
typedef PluginBase* (*plugin_base_initfunc)(); //typedef to function pointer

int main(int argc, char** argv) {

    try
    {
        PluginLoader loader("libSimplePlugin.so");
        PluginBase* pPluginA = 0;
        PluginBase* pPluginB = 0;
        plugin_base_initfunc func;
        loader.getSymbolFunc("LOAD_PLUGIN_A", func); //extract function to construct a PluginA object
        pPluginA = (func)(); //construct plugin object
        cout << pPluginA->outputMessage() << endl;
        delete pPluginA;

        loader.getSymbolFunc("LOAD_PLUGIN_B", func); //extract function to construct a PluginB object
        pPluginB = (func)(); //construct plugin object
        cout << pPluginB->outputMessage() << endl;
    }
    catch (exception &e) {
        cout << e.what() << endl;
    }
    return 0;
}

This produces the following output:
./pluginapp 
PluginA: Hello world
PluginB: Hello world

See full source files below:

 Class definition for PluginLoader.h
 
/**
 * Description:  Class definition for loading shared object libraries at runtime.
*/
#ifndef PLUGIN_LOADER_H
#define PLUGIN_LOADER_H


#include "string.h"
#include "iostream"


using namespace std;

class PluginLoader
{
public:
    /**
     *
     * @param libname path to required shared library file
     */
    PluginLoader(string libname);
    virtual ~PluginLoader();
    /**
     *
     * @param symbol symbol (defined using extern "C") to extract from opened shared library.     *
     * @return address of symbol (either function or object)
     */
    void *getSymbol(string symbol) const;

    /**
     *
     * @return name of shared library
     */
    string getLibName() const {
        return(m_libname);
    };


    template  void getSymbol (string symbol, T &value) const {
        void *sym = getSymbol(symbol);
        value = (T)(sym);
    }

private:

    void *m_lib;
    string m_libname;

};
#endif

Implementaion PluginLoader.cpp:

#include "CpluginLoader.h"
#include "sstream"
#include "dlfcn.h"
#include "iostream"
#include "stdexcept"

using namespace std;

PluginLoader::PluginLoader(string libname) {
    m_libname = libname;
    m_lib = dlopen(m_libname.c_str(), RTLD_LAZY | RTLD_GLOBAL);
    if (m_lib == 0) {
        stringstream errortext;
        if (char * err = dlerror()) {
            errortext << " dlopen() error: " << err << endl;
            throw runtime_error(errortext.str());
        }
    }
}

PluginLoader::~PluginLoader() {
    dlclose(m_lib);
}

void *
PluginLoader::getSymbol(string symbol) const {
    void *sym = 0;

    if (m_lib) {
        sym = dlsym(m_lib, symbol.c_str());
        if (char * err = dlerror()) {
            stringstream errortext;
            errortext << "DynamicLibrary::getSymbol() - Error on " << symbol << "message: " << err;
            throw runtime_error(errortext.str());
        }

    } else {
        throw runtime_error("DynamicLibrary::getSymbol() - Error accessing library");
    }

    return (sym);
}


Class definition PluginBase:


class PluginBase 
{
    
public:
    PluginBase();
    virtual ~PluginBase();
    
    virtual string outputMessage()=0;
};

Class definition PluginA:


class PluginA : public PluginBase
{
public:
    PluginA();
    virtual ~PluginA();

    virtual string outputMessage();
};

extern "C" PluginBase* LOAD_PLUGIN_A() {
    return new PluginA();
}

Implementaion of pure virtual:


string PluginA::outputMessage()
{
    return "PluginA: Hello world";
}

No comments:

Post a Comment