Over the past few weeks, one of the concepts that I’ve been experimenting with is plugin architecture. The idea of having a core application which can be extended by shared objects without recompiling the core program. Or, possibly, a way of defining more services in an application based around plugins. What I’ve done so far hasn’t been much, but, I haven’t spent much time working on it. When I started out on researching it, one of the things I wanted was to be able to was define a C++ object in a plugin and be able to instantiate that object in the main program. So, this is what I have come up with. There are three parts of this solution: the plugin class definition in plugin.hpp, the plugin in someplugin.cpp, and the loader code in loader.cpp. I’ve also included a CMakeLists.txt file to compile it with cmake.
plugin.hpp
#ifndef _PLUGIN_HPP_ #define _PLUGIN_HPP_ #include <string> namespace plugins { class Plugin { public: virtual std::string toString() = 0; }; } #endif
awesomeplugin.cpp
#include "plugin.hpp" namespace plugins { class AwesomePlugin : public Plugin { public: // A function to do something, so we can demonstrate the plugin std::string toString() { return std::string("Coming from awesome plugin"); } }; } extern "C" { // Function to return an instance of a new AwesomePlugin object plugins::Plugin* construct() { return new plugins::AwesomePlugin(); } }
loader.cpp
#include <iostream> #include <vector> #include <dlfcn.h> #include <boost/function.hpp> #include "plugin.hpp" typedef std::vector<std::string> StringVector; typedef boost::function<plugins::Plugin* ()> pluginConstructor; int main (int argc, char** argv) { // Assemble the names of plugins to load StringVector plugins; for(int i = 1; i < argc; i++) { plugins.push_back(argv[i]); } // Iterate through all the plugins and call construct and use an instance for(StringVector::iterator it = plugins.begin(); it != plugins.end(); it++) { // Alert that we are attempting to load a plugin std::cout << "Loading plugin \"" << *it << "\"" << it->c_str() << std::endl; // Load the plugin's .so file void *handle = NULL; if(!(handle = dlopen(it->c_str(), RTLD_LAZY))) { std::cerr << "Plugin: " << dlerror() << std::endl; continue; } dlerror(); // Get the pluginConstructor function pluginConstructor construct = (plugins::Plugin* (*)(void)) dlsym(handle, "construct"); char *error = NULL; if((error = dlerror())) { std::cerr << "Plugin: " << dlerror() << std::endl; dlclose(handle); continue; } // Construct a plugin plugins::Plugin *plugin = construct(); std::cout << "[Plugin " << *it << "] " << plugin->toString() << std::endl; delete plugin; // Close the plugin dlclose(handle); } return 0; }
CMakeLists.txt
# Project Stuff cmake_minimum_required (VERSION 2.6) project (PluginDemo) # Default Options add_definitions("-std=c++0x") # Find Boost find_package(Boost REQUIRED) include_directories(${Boost_INCLUDE_DIRS}) # Pull in the project includes include_directories(${PROJECT_SOURCE_DIR}/include) set(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/lib) set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin) set(LIBS ${LIBS} pthread boost_thread rt) # Build the plugin experiment add_executable(pluginloader src/loader.cpp) target_link_libraries(pluginloader ${LIBS} dl) add_library(awesomeplugin SHARED src/awesomeplugin.cpp)
Basically create a directory with the folders bin, lib, and src. Put loader.cpp, awesomeplugin.cpp, and plugin.hpp in src, and CMakeLists.txt in the directory. Open a terminal and run “cmake . && make”. Run the pluginloader program and pass it the path to the plugin’s .so in the lib folder. Here is the output from my computer.
nathaniel@XtremePC:~/Programming/Experimentation> cmake .
— The C compiler identification is GNU
— The CXX compiler identification is GNU
— Check for working C compiler: /usr/bin/gcc
— Check for working C compiler: /usr/bin/gcc — works
— Detecting C compiler ABI info
— Detecting C compiler ABI info – done
— Check for working CXX compiler: /usr/bin/c++
— Check for working CXX compiler: /usr/bin/c++ — works
— Detecting CXX compiler ABI info
— Detecting CXX compiler ABI info – done
— Boost version: 1.46.1
— Configuring done
— Generating done
— Build files have been written to: /home/nathaniel/Programming/Experimentation
nathaniel@XtremePC:~/Programming/Experimentation> make
Scanning dependencies of target awesomeplugin
[ 50%] Building CXX object CMakeFiles/awesomeplugin.dir/Plugins/awesomeplugin.cpp.o
Linking CXX shared library lib/libawesomeplugin.so
[ 50%] Built target awesomeplugin
Scanning dependencies of target pluginloader
[100%] Building CXX object CMakeFiles/pluginloader.dir/Plugins/loader.cpp.o
Linking CXX executable bin/pluginloader
[100%] Built target pluginloader
nathaniel@XtremePC:~/Programming/Experimentation> bin/pluginloader lib/libawesomeplugin.so
Loading plugin “lib/libawesomeplugin.so”
[Plugin lib/libawesomeplugin.so] Coming from awesome plugin
nathaniel@XtremePC:~/Programming/Experimentation>
– Teknoman117