Commented Code ...blog

Nov 20, 2012

Simple way to load code at runtime via Plug-ins

In an attempt to apply the Waste Hierarchy to my development cycle, I started looking back at some old code while working on my new project. I needed a way of loading code at runtime in the form of plug-ins so I thought I'd dig up my old implementation and clean it up a bit.

Using a simple base class for all plug-ins, and another to load them, we can get code loaded at runtime in a few easy steps.


The idea

At some point we've all used an application that implements function enhancement via runtime plug-ins. Being able to add functionality to our applications at a future date without the need to build new versions is a great thing. Depending on your design, starting with a base application that just loads plugins might just be the way to go.

The basic idea is to create a simple template for a Plug-in class that will compile into Shared Library. The main application will contain an easy to use interface that loads the Shared Library at runtime and enable the Plug-in as defined by the template.

Plug-in Template

The first step is to create a base class that we will derive all our Plug-ins. This class will contain all the required function calls that every plug-in will need. In our example we will just have a StartPlugin and StopPlugin but in theory you'll want to control when a plug-in is loaded, unloaded, check if it is currently running, get names and version for dependencies, etc.

In this example we will also be using the Factory software design patter. The Factory pattern is used to instantiate an object while hiding the actual logic. Since our main application has no idea what the plug-in classes are, we use a Factory base class to instantiate a plug-in object. But more on that later.

Plugin.h

#ifndef PLUGIN_H
#define PLUGIN_H

// Base class for all your plugins.
class Plugin
{
  public:
    virtual ~Plugin (void) { }

    // Insert the functions all plugins must support here.
    virtual int StartPlugin (void) = 0;
    virtual int StopPlugin (void) = 0;
};

// Base class used for instantiating your plugin classes.
class PluginFactory
{
  public:
    virtual ~PluginFactory (void) { }
    virtual Plugin *CreatePlugin (void) = 0;
};

// Required function our loader will look for in our shared library.
extern "C" void* GetFactory (void);

#endif  // PLUGIN_H

libFoo: The example plug-in

We will use the Plugin base class to create an example plug-in that. We'll create a short service routine for our plug-in that prints out a 1000ms tick. The service routine starts when StartPlugin is called and stops once StopPlugin is called.

Foo.cpp

#include "Plugin.h"
#include <iostream>
#include <thread>
#include <chrono>

class Foo : public Plugin
{
  public:
    // Required by Plugin class, starts our Plugin object's main task.
    int StartPlugin (void)
    {
      std::cout << "Starting Foo" << std::endl;
      running = true;

      // Start a therad that prints ticks while this->running is true
      // [this] keeps the instance of Foo in the closure of the anonymous
      // function.
      th = std::thread(
        [this] () {
          std::chrono::milliseconds dura(1000);
          while (this->running)
          {
            std::cout << "Tick\n";
            std::this_thread::sleep_for(dura);
          }
        });

      return 0;
    }

    int StopPlugin (void)
    {
      std::cout << "Stopping Foo" << std::endl;
      // Allows anonymous function to finish
      running = false;

      // Waits for thread to complete
      th.join();
      return 0;
    }

  private:
    bool running;
    std::thread th;
};

class FooFactory : public PluginFactory
{
  public:
    FooFactory () { }
    ~FooFactory () { }

    // Returns generic "Plugin" class, so our main app doesn't need
    // to know anythign about Foo
    virtual Plugin *CreatePlugin(void)
    {
      Plugin *plugin = new Foo;

      return plugin;
    }
};

// Required symbol to load the FooFactory
void *GetFactory (void)
{
  return new FooFactory;
}

Our service routine is the anonymous function:

[this] () {
  std::chronos::milliseconds dura(1000);
  while (this->running)
  {
    std::cout << "Tick\n";
    std::this_thread::sleep_for(dura);
  }
}

Keeping the Foo object in our closure, we loop through checking our running flag, printing and sleeping. If you call StopPlugin the flag is changed and we join our thread.

The FooFactory class creates instances of the Foo class returning as the base class Plugin. This allows our main application to instantiate and run the plug-in code without having any real knowledge of the actual class. All this code will hide behind our PluginLoader class eventually.

The last function GetFactory is a C call that returns the factory object. We use this in our plug-in loading process.

To build the shared library:

g++ -std=c++11 -Wall -fPIC -c Foo.cpp
g++ -std=c++11 -shared -Wl,-soname,libFoo.so.1 -lpthread -o libFoo.so.1 Foo.o

Loading Plug-ins

Without using a 3rd party library, not much has changed in dynamic loading of libraries has not changed since the C days (please update me if you know something). For now we will use dlopen to load a shared library, and dlsym to load a symbol from said library.

Our main application will create an instance of PluginLoader for every plug-in you want to load. Once loaded, PluginLoader can instantiate as many copies of the plug-in as needed.

PluginLoader.cpp

#include <stdexcept>
#include <memory>
#include <dlfcn.h>

#include "Plugin.h"

class PluginLoader
{
  public:
    PluginLoader (std::string const& filename)
    {
      // Load shared library file
      handle = dlopen(filename.c_str(), RTLD_LAZY);

      // If loading fails, throw exception
      if (!handle)
        throw std::logic_error("Loading Library: " + filename);

      // Old, C style Function Pointer
      void* (*oldStyle) ();

      // Load "GetFactory" symbol from shared library
      *(void **)(&oldStyle) = dlsym(handle, "GetFactory");

      // If symbol is missing, throw exception
      if (!oldStyle)
        throw std::logic_error("In " +
                                filename +
                                " Missing Symbol: GetFactory");

      // Cast Function Pointer to std::function, call it and cast the return
      // value to PluginFactory*. Store it in our class for later use.
      factory =  std::unique_ptr<PluginFactory>(
        (PluginFactory*)(reinterpret_cast<void* (*)(void)>(oldStyle))());
    }

    ~PluginLoader (void)
    {
      // Need to destroy our factory object before closing shared library.
      delete factory.release();
      dlclose(handle);
    }

    // Returns pointer to a Plugin object.
    std::unique_ptr<Plugin> createPlugin (void)
    {
      return std::unique_ptr<Plugin>(factory->CreatePlugin());
    }

  private:
    void* handle;
    std::unique_ptr<PluginFactory> factory;
};

PluginLoader takes the path of the shared library file, opens it, loads the GetFactory symbol and instantiates a factory. Calling PluginLoader::createPlugin will instantiate the plug-in object.

Putting it all together

Last step is to put everything in our main application.

#include <functional>
#include <stdexcept>
#include <memory>
#include <chrono>
#include <thread>

#include "Plugin.h"
#include "PluginLoader.h"

int main ()
{
  std::chrono::milliseconds dura(1000);

  // Load Shared Library
  PluginLoader loader("./libFoo.so.1");

  // Create an instance of the Foo plug-in
  std::unique_ptr<Plugin> plugin(loader.createPlugin());

  // Start Plug-in
  plugin->StartPlugin();

  // Sleep 1000ms x 3
  for (int i = 0; i < 3; i++)
  {
    std::this_thread::sleep_for(dura);
  }

  // Stop plug-in thread
  plugin->StopPlugin();

  return 0;
}

To build our app:

g++ -std=c++11 -rdynamic -c PluginLoader.cpp 
g++ -std=c++11 -rdynamic -c main.cpp 
g++ -std=c++11 -rdynamic -ldl -lpthread -o app PluginLoader.o main.o

Our output:

[jeff@blue plugins]$ ls
app libFoo.so.1
[jeff@blue plugins]$ ./app
Starting Foo
Tick
Tick
Tick
Stopping Foo
[jeff@blue plugins]$

<perm> | /coding/c++ | 2012.11.20-15:32.00 | <comments> | <reddit> | <digg> | <stumbleupon> | <tweet>