Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Building Your Own Plugin Framework: Part 2


Plugin System Components

This section describes the main components of the generic plugin framework and what they do. You can find all these components in the plugin_framework subdirectory of the source code.

DynamicLibrary

The DynamicLibrary component is a simple cross-platform C++ class. It uses the dlopen/dlclose/dlsym system calls on UNIX (including the Mac OS, X) and the LoadLibrary/FreeLibrary/GetProcAddress API calls for Windows.

Listing Four is the header file for DynamicLibrary.

#ifndef DYNAMIC_LIBRARY_H
#define DYNAMIC_LIBRARY_H

#include <string>

class DynamicLibrary
{
public:
  static DynamicLibrary * load(const std::string & path, 
                               std::string &errorString);
  ~DynamicLibrary();
  void * getSymbol(const std::string & name);
private:
  DynamicLibrary();
  DynamicLibrary(void * handle);
  DynamicLibrary(const DynamicLibrary &);
private:
  void * handle_;  
};
#endif
Listing Four

Each dynamic library is represented by an instance of the DynamicLibrary class. Loading a dynamic library involves calling the static load() method that returns a DynamicLibrary pointer if everything was fine or NULL if it failed. The errorString output argument contains the error message if any. The dynamic library will store the platform-specific handle used to represent the loaded library, so it will be available for getting symbols and unloading later.

The getSymbol() method is used to get symbols out of loaded library and the destructor unloads the library (just delete the pointer).

There are different ways to load dynamic libraries. For simplicity, DynamicLibrary just picks one option on each platform. It is possible to extend it, but due to platform differences the interface will not be simple anymore.

PluginManager

PluginManager is the big Kahuna of the plugin framework. Everything that has to do with plugins goes through the PluginManager. Listing Five contains the header file the PluginManager.

#ifndef PLUGIN_MANAGER_H
#define PLUGIN_MANAGER_H

#include <vector>
#include <map>
#include <apr-1/apr.h>
#include <boost/shared_ptr.hpp>
#include "plugin_framework/plugin.h"

class DynamicLibrary;
struct IObjectAdapter;

class PluginManager
{
  typedef std::map<std::string, boost::shared_ptr<DynamicLibrary> > DynamicLibraryMap; 
  typedef std::vector<PF_ExitFunc> ExitFuncVec;  
  typedef std::vector<PF_RegisterParams> RegistrationVec; 
public:   
  typedef std::map<std::string, PF_RegisterParams> RegistrationMap;
  static PluginManager & getInstance();
  static apr_int32_t initializePlugin(PF_InitFunc initFunc);
  apr_int32_t loadAll(const std::string & pluginDirectory, PF_InvokeServiceFunc func = NULL);
  apr_int32_t loadByPath(const std::string & path);
  void * createObject(const std::string & objectType, IObjectAdapter & adapter);
  apr_int32_t shutdown();  
  static apr_int32_t registerObject(const apr_byte_t * nodeType, 
                                    const PF_RegisterParams * params);
  const RegistrationMap & getRegistrationMap();
private:
  ~PluginManager();    
  PluginManager();
  PluginManager(const PluginManager &);
  
  DynamicLibrary * loadLibrary(const std::string & path, std::string & errorString);
private:
  bool                inInitializePlugin_;
  PF_PlatformServices platformServices_;
  DynamicLibraryMap   dynamicLibraryMap_;
  ExitFuncVec         exitFuncVec_;

  RegistrationMap     tempExactMatchMap_;   // register exact-match object types 
  RegistrationVec     tempWildCardVec_;     // wild card ('*') object types

  RegistrationMap     exactMatchMap_;   // register exact-match object types 
  RegistrationVec     wildCardVec_;     // wild card ('*') object types
};
#endif
Listing Five

The application initiates the loading of plugins by calling PluginManager::loadAll(), passing the directory that contains the plugins. The PluginManager loads all the dynamic plugins and initializes them. It stores every dynamic plugin library in the dynamicLibraryMap_, every exit function in exitFuncVec_ (for both dynamic and static plugins) and every registered type in the exactMatchMap_. Wildcard registrations are stored in the wildCardVec_. The PluginManager is now ready to create plugin objects. If there are static plugins they are registered too (either by the application or via auto-registration).

During plugin initialization the PluginManager keeps all registrations in temporary data structures that are merged into exactMatchMap_ and wildCardVec_ if the initialization is successful and discarded if it fails. This transactional behavior guaranties that all stored registrations come from successfully initialized plugins that are still loaded into memory.

When the application needs to create a new plugin object (either dynamic or static) it calls the PluginManager::createObject() and passes an object type and an adaptor. The PluginManager creates the object using the registered PF_CreateFunc and adapts it from C to C++ if it's a C object (based on the PF_ProgrammingLanguage member of the registration struct).

At this point, the PluginManager gets out of the picture. The application interacts with the plugin object directly and finally destroys it. The PluginManager is blissfully ignorant of all these interactions. It is the responsibility of the application to destroy plugin objects before the plugin is unloaded (or at least not call its methods after its plugin was unloaded).

The PluginManager is in charge of unloading the plugins too. The PluginManager::shutdown() method should be called the application after it destroyed all the plugin objects it created. The shutdown() calls the exit function of all the plugins (both static and dynamic), unloads all the dynamic plugins and clears all the internal data structures. It is possible to "restart" the PluginManager by calling its loadAll() method. The application may also "restart" static plugins by calling the PluginManager::initializePlugin() for each one. Static plugins that used auto-registration will be gone for good.

If the application forgets to call shutdown(), the PluginManager will call it in its destructor.

ObjectAdapter

The ObjectAdapter's job is to adapt a C plugin object to a C++ plugin object as part of object creation. The IObjectAdapter interface is straightforward. It defines a single method (besides the mandatory virtual destructor) -- adapt. The adapt() method accepts a void pointer, which will be a C object and PF_DestroyFunc function pointer that can destroy the C object. It is supposed to return a C++ object that wraps the C object. It is the responsibility of the application to provide a proper wrapper object. The plugin framework can't do it because this task requires knowledge of the application object model. However, the plugin framework provides the ObjectAdapter template that simply performs a static_cast of the C object to the wrapper object provided by the application (see Listing Three).

The resulting object can be passed to any context that requires the C++ interface. This will be the main focus of the next article in this series, so don't sweat it if it sounds a little obscure at the moment. The main point here is that the ObjectAdapter template provides an implementation of the IObjectAdapter interface that the application can specialize to its own C plugin object and its C++ wrapper.

Listing Six contains the ActorFactory that subclasses a specialization of the ObjectAdapter template. It functions as an adapter from a C object that implements the C_Actor to the ActorAdapter C++ object that implements the IActor interface. ActorFactory also provides a static createActor() function that calls the PluginManager's createObject() with itself as the adapter and casts the resulting void pointer to an IActor pointer. This lets the application call a friendly createActor() static function with just an actor type and not mess with adapters and casts.

#ifndef ACTOR_FACTORY_H
#define ACTOR_FACTORY_H

#include "plugin_framework/PluginManager.h"
#include "plugin_framework/ObjectAdapter.h"
#include "object_model/ActorAdapter.h"

struct ActorFactory : public ObjectAdapter<ActorAdapter, C_Actor>
{
  static ActorFactory & getInstance()
  {
    static ActorFactory instance;

    return instance;
  }
  static IActor * createActor(const std::string & objectType)
  {
    void * actor = PluginManager::getInstance().createObject(objectType, getInstance());
    return (IActor *)actor;
  }
};
#endif // ACTOR_FACTORY_H
Listing Six

PluginRegistrar

The PluginRegistrar lets static plugins register their objects automatically with the PluginManager without requiring the application to explicitly initialize them. The way it works (when it works) is that the plugin defines a global instance of the PluginRegistrar and passes it its initialization function (with a signature that matches PF_InitFunc). The PluginRegistrar simply calls the PluginManager::initializePlugin() method that ignites the static plugin initialization just like with dynamic plugins after loading the dynamic library; see Listing Seven.

#ifndef PLUGIN_REGISTRAR_H
#define PLUGIN_REGISTRAR_H
#include "plugin_framework/PluginManager.h"
struct PluginRegistrar
{
  PluginRegistrar(PF_InitFunc initFunc)
  {
    PluginManager::initializePlugin(initFunc);
  }
};
#endif // PLUGIN_REGISTRAR_H
Listing Seven

Next Time

That's it for now. In the next installment, I examine the issues that involved in cross-platform development and the dual C/C++ object model, among other topics.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.