See what's going on with flipcode!




This section of the archives stores flipcode's complete Developer Toolbox collection, featuring a variety of mini-articles and source code contributions from our readers.

 

  OOP Plugins
  Submitted by



A while back I needed to implement plugin libraries in game for unit AI, to promote engine flexibility. The common method of doing this would be to write specific AI functions in a separate library and export them for use with LoadLibrary. But I was using c++, so I figured I could do better than that. What I came up with allowed me to fit classes created externally directly into the local class heirarchy. I've attached a generalized version of plugin classes with a simple example plugin to this article, as an MSVC6 project. Now to the details. It will probably be easier to follow this article if you download the sample project now. To create plugins with this code you first need to derive an interface from the utility::plugin class (either directly or indirectly) for each plugin type you need (e.g. unit_plugin, monster_plugin, weapon_plugin, item_plugin, etc.). Then, in each of these interface classes place the __PLUGIN_INTERFACE macro under the public header. For example, if we were implementing data streams as plugins, our heirarchy might look something like this:

class stream_plugin : public utility::plugin
{
public:
	__PLUGIN_INTERFACE1(2, stream_plugin, utility::plugin);
	// Overrideable functions here...
};
class istream_plugin : public stream_plugin
{
public:
	__PLUGIN_INTERFACE1(3, istream_plugin, stream_plugin);
	// Overrideable functions here...
	virtual int read(char* buffer, int len) = 0;
};
class ostream_plugin : public stream_plugin
{
public:
	__PLUGIN_INTERFACE1(4, ostream_plugin, stream_plugin);
	// Overrideable functions here...
	virtual int write(const char* buffer, int len) = 0;
};
class iostream_plugin : public istream_plugin, public ostream_plugin
{
public:
	// Note: The __PLUGIN_INTERFACE2 handles inheritance using 2 superclasses.
	// __PLUGIN_INTERFACE# defines inheritance with # superclasses.  Currently,
	// values of 0 to 5 are supported (although more can be added).
	__PLUGIN_INTERFACE2(5, iostream_plugin, istream_plugin, ostream_plugin);
	// Overrideable functions here...
}; 



Note, for each class the interface is declared with a different constant (in the preceding example, 2, 3, 4, and 5). These constants need not be sequential, only unique amongst the interface classes. After the plugin interfaces are defined, any number of plugin classes can be derived from the interfaces and compiled into separate dlls. A plugin dll needs three things: 1 or more plugin derivations, a factory for creating plugins, and an exported function for retrieving the factory. Continuing the example above, we could create a plugin for file input like so:

// Include interface plugin header.

// An implementation of the istream_plugin for file-input.
class ifstream_plugin : public istream_plugin
{
public:
	// Note: implementation classes use the __PLUGIN_IMPLEMENTATION# macro.
	__PLUGIN_IMPLEMENTATION1(ifstream_plugin, istream_plugin);
	// Allocation/deallocation is typically unsafe across dll boundary, so the implementation
	// is entirely responsible for it.  Hence, these functions override their abstract base
	// members (defined in utility::plugin) to do just that.
	virtual void destroy() throw()  { delete this; }
	virtual utility::plugin* clone() const throw()  { return new ifstream_plugin(*this); }
	virtual utility::plugin* instance() const throw()  { return new ifstream_plugin(); }
	// Override istream_plugin functions...
	virtual int read(char* buffer, int len)
	{
		// Do whatever is necessary to fill buffer...
	}
};

// A simple factory for creating instances of ifstream_plugin. class myfactory : public utility::factory { public: virtual size_type size() const throw() { return 1; // This plugin only defines one plugin implementation. } virtual utility::plugin* create_plugin(size_type index) const throw() { if (index == 0) return new ifstream_plugin(); else return 0; } virtual const utility::alien_type_info* plugin_typeid(size_type index) const throw() { if (index == 0) return &alien_class_typeid(ifstream_plugin); else return 0; } };

// Exports the get_factory function, required by the plugin loading mechanism. Returns this // library's factory so the calling code can create plugins from it. __PLUGIN_EXPORT const utility::factory* get_factory() { static const myfactory f; return &f; }





This only a trivial example, but most plugins will follow exactly the same format. To save space and time, it is often useful to place multiple plugin implementations within a single plugin dll. For instance, in the data stream example, it would be logical to place all file io plugins in one dll, and all network io plugins in another dll. When more than one plugin implementation is compiled into a dll, the factory distinguishes them by the creation index. And finally, after the interfaces are defined and some plugins are implemented, they can be loaded into your program and used. This is where having OOP plugins really pays off. Simply create a factory from a plugin library, create a plugin from that, then use it like any locally implemented object. I.e.:

{
	// Load the factory from a plugin library.
	utility::factory::handle fh = utility::load_factory(path);
	if (!fh.valid() || (fh-size() <= 0))
	{
		printf("Failed to load plugin library: %s\n", path);
		return;
	}

// Create a plugin from the factory. utility::plugin* p = fh-create_plugin(0);

// Cast the plugin to the required type. istream_plugin* is = utility::plugin_cast<istream_plugin*(p); if (is == 0) { printf("Failed to create istream_plugin\n"); return; }

// Call any interface functions defined by istream_plugin (or superclass)... is-read(buffer, bufferlen);

// Destroy the plugin. is-destroy();

// The factory handle gets deconstructed here, and implicitly unloads the library. }



Of note here is the use of utility::plugin_cast. Wondered what the point of those __PLUGIN_INTERFACE# and __PLUGIN_IMPLEMENTATION# macros were? They implement runtime type checking across the dll boundary. This is something that normally isn't possible using dynamically loaded classes. If you want RTTI across dll boundaries you are limited to MFC (clunky, no multiple-inheritance), COM (really clunky, requires GUIDs), and doing it yourself. Using the code provided, you can use utility::plugin_cast in place of dynamic_cast for class heirarchies declared with the interface and implementation macros. This allows the controlling code to identify the interface for a loaded plugin, and allows the plugin to verify the implementation of any plugin objects passed to it from the controlling code. There are a few caveats, though:
  • Statically allocated data is never shared across a dll boundary unless explicitly passed across by reference or pointer. For instance, if a static member is placed in an interface class, upon plugin dll load there would then be two copies of that member in the process-space: one in the executable, and one in the plugin dll. Changing one would leave the other unchanged, leading to inconstencies and bugs that are difficult to track.
  • Most (or all) allocation/deallocation functions use static data for keeping track of allocated memory, and thus cannot span dll boundaries. I.e. a block of memory allocated in the main program cannot be deallocated in a dll, and vice-versa. Note, this is the reason plugins are deleted via the destroy() member, and not delete. Since most STL containers allocate memory on the heap (typically with new), they cannot be safely passed by non-const between the controlling code and plugins. There are a few solutions to this problem: 1) Override new and delete to be dll-safe, like so:


  • inline void* operator new(size_t amt)  { return HeapAlloc(GetProcessHeap(), 0, amt); }
    inline void operator delete(void* ptr)  { HeapFree(GetProcessHeap(), 0, ptr); }
    inline void* operator new[](size_t amt)  { return operator new(amt); }
    inline void operator delete[](void* ptr)  { operator delete(ptr); } 



    This works, but likely would slow down heap allocations across the breadth of your application. It also can cause problems with some of the standard libraries. 2) Use custom allocators for all STL containers to preserve allocation/deallocation context across the dll boundary. This method is safer than #1, but a bit more difficult and verbose, since it requires the allocator to be specified in all template declarations. 3) Pass all STL containers by const-reference. This is safe, but error-prone and occasionally awkward (for instance, a plugin function can't return a dynamically sized string, the calling function has to provide a buffer for it).
  • The ids specified for the first parameter of the __PLUGIN_INTERFACE# macros must be unique amongst plugin interfaces for RTTI to work correctly. The ids must also be consistent across compiles and compilers, since these ids must match across the plugin dll boundaries.
  • The utility::plugin_cast function only works with pointers, not references. The c++ dynamic_cast function allows you to specify a reference for the source and destination values, and will throw an exception if there is an invalid cast. No such functionality exists for plugin_cast.
  • Compile errors for utility::plugin_cast are not very instructive. For instance:


  • const utility::plugin* p1;
    istream_plugin* p2 = utility::plugin_cast<istream_plugin*(p1); 





    This generates an error in the utility::plugin_cast template function since p1 cannot be implicitly cast to non-const. Optimally, the error would occur at the call to utility::plugin_cast, but instead it occurs in the definition for utility::plugin_cast, which is confusing. Just keep this in mind if you get compile errors with utility::plugin_cast.

    The factory handle implicitly unloads a plugin library when all copies of it (the handle) are destroyed. Thus, you should consider the destruction of a factory handle as an invalidation of all plugins created from that factory. It is also a good idea to destroy all plugins created by a factory before destroying its factory handle (good form, and ensures proper release of resources).

    And that's it. You may consider this article and the accompanying project as public-domain. Justin Wilder
    justin@bigradio.com





    Currently browsing [oop-plugin.zip] (16,051 bytes) - [test_object_plugin/main.cpp] - (924 bytes)

    #include "testbase.h"

    #ifndef NDEBUG static const char testplugin_path[] = "./testplugin/Debug/testplugin.dll"; #else static const char testplugin_path[] = "./testplugin/Release/testplugin.dll"; #endif

    void main() { // Load the factory from a plugin library. utility::factory::handle fh = utility::load_factory(testplugin_path); if (!fh.valid() || (fh->size() <= 0)) { printf("Failed to load plugin library: %s\n", testplugin_path); return; }

    // Create a plugin from the factory. utility::plugin* p = fh->create_plugin(0);

    // Cast the plugin to the required type. testbase* testp = utility::plugin_cast<testbase*>(p); if (testp == 0) { printf("Failed to create test plugin\n"); return; }

    // Use the plugin. testp->print_hello();

    // Destroy the plugin. testp->destroy();

    // The factory handle gets deconstructed here, and implicitly unloads the library. }

    Currently browsing [oop-plugin.zip] (16,051 bytes) - [test_object_plugin/plugin.h] - (3,572 bytes)

    #ifndef __PLUGIN_H__
    #define __PLUGIN_H__

    #include "plugin_type_info.h" #include <utility>

    namespace utility {

    class plugin;

    // Creates multiple types of plug-ins. class factory { public: typedef size_t size_type;

    // Returns the number of factory types available. virtual size_type size() const throw() = 0; // Creates a plug-in object of the given type (between 0 and length() - 1). Returns NULL // if there is an error. The object returned will be destroy()'ed when no longer needed. virtual plugin* create_plugin(size_type index) const throw() = 0; // Returns a pointer to the type-info of the given index (between 0 and length() - 1). // Returns NULL if there is an error. virtual const utility::plugin_type_info* plugin_typeid(size_type index) const throw() = 0;

    // Handle to a factory. Simplifies the task of loading and unloading plugin libraries. // A factory handle *must* remain in existence as long as any plugins created by it exist. class handle { public: // Constructs invalid handle. handle() throw() : f(0), data(0) {} // Constructs handle with factory loaded from given file. explicit handle(const char* path) throw(); // Copies the handle. A library remains loaded until *all* its handles are destroyed. handle(const handle& c) throw() : f(0), data(0) { *this = c; } // Unloads the library if this is the last handle to the loaded library. ~handle() throw(); // Assigns this handle to another. If this handle is valid, it is unload()ed prior to assignment. handle& operator=(const handle& c) throw(); // Explicitly unloads the library. Makes this handle invalid. void unload() throw(); // Returns true if the handle is valid, false otherwise. bool valid() const throw() { return f != NULL; } // Both allow access to the underlying factory object. The factory handle must be valid. const factory* operator->() const throw() { return f; } const factory& operator*() const throw() { return *f; } protected: const factory* f; void* data; }; };

    // All plug-in objects must derive from this class. class plugin { public: // All plugin derivatives must declare either a native or an alien interface. __PLUGIN_INTERFACE0(1, plugin); // Calls the objects destructor and deallocates its memory. This function is called as opposed to // explicitly deleting (with delete) object, since allocation mechanisms usually cannot span dll-boundaries. virtual void destroy() throw() = 0; // Creates a copy of this plugin, or returns NULL if it cannot. The plugin returned will be destroy()'ed // when no longer needed. virtual plugin* clone() const throw() = 0; // Creates a new instance of this plugin's type (not a copy). virtual plugin* instance() const throw() = 0; };

    // Function pointer to a "get_factory" function. All external plug-in libraries must // export a function of this type. I.e. all external plug-in libraries must define the // following function: // __PLUGIN_EXPORT const factory* get_factory() { ... } #define __PLUGIN_EXPORT extern "C" __declspec(dllexport) static const char fp_get_factory_name[] = "get_factory"; typedef const factory* (*fp_get_factory)();

    // Loads a factory from a plugin dll. Returns an invalid handle if the plugin dll cannot be loaded. // Alternately, the factory::handle constructor can be used directly. inline factory::handle load_factory(const char* path) { return factory::handle(path); }

    }; // namespace utility #endif //#ifndef __PLUGIN_H__

    Currently browsing [oop-plugin.zip] (16,051 bytes) - [test_object_plugin/plugin_type_info.h] - (7,492 bytes)

    #ifndef __PLUGIN_TYPE_INFO__
    #define __PLUGIN_TYPE_INFO__

    namespace utility {

    struct plugin_type_info;

    template <typename _PLUGIN_TYPE> const plugin_type_info& plugin_typeid(_PLUGIN_TYPE& _p) { return _p.__get_plugin_typeid(); }

    #define plugin_class_typeid(_PLUGIN_TYPE) (_PLUGIN_TYPE::__class_plugin_typeid()) #define plugin_class_pointer_typeid(_PLUGIN_POINTER_TYPE) (((_PLUGIN_POINTER_TYPE)0)->__class_plugin_typeid())

    // Type information about plugin classes. struct plugin_type_info { public: // For collation and comparison. Does not correlate in any way to inheritance. bool operator==(const plugin_type_info& c) const { return _id == c._id; } bool operator!=(const plugin_type_info& c) const { return _id != c._id; } bool operator<(const plugin_type_info& c) const { return _id < c._id; } bool operator<=(const plugin_type_info& c) const { return _id <= c._id; } bool operator>(const plugin_type_info& c) const { return _id > c._id; } bool operator>=(const plugin_type_info& c) const { return _id >= c._id; }

    // Returns true if this type is the given type (i.e. the class with this type-info is the class or // derives from the class with the given type-info). bool is(const plugin_type_info& info) const { int temp; return below_or_equal(info, temp); }

    // Reserved for internal use. public: struct _parent_node { inline _parent_node(const plugin_type_info& _info, int _ptroffset, const _parent_node* _next = 0) : _info(_info), _ptroffset(_ptroffset), _next(_next) {} const plugin_type_info& _info; int _ptroffset; const _parent_node* _next; }; const void* cast_pointer(const void* obj, const plugin_type_info& from, const plugin_type_info& to) const { return cast_pointer((void*)obj, from, to); } void* cast_pointer(void* obj, const plugin_type_info& from, const plugin_type_info& to) const { int fromoffset = 0; if (!below_or_equal(from, fromoffset)) return 0; int tooffset = 0; if (!below_or_equal(to, tooffset)) return 0; return (void*)((char*)obj - fromoffset + tooffset); } protected: plugin_type_info(unsigned int _id, const _parent_node* _pn) : _id(_id), _pn(_pn) {} bool below_or_equal(const plugin_type_info& info, int& ptroffset) const { if (_id == info._id) return true; else { for (const _parent_node* curnode = _pn; curnode != 0; curnode = curnode->_next) { if (curnode->_info.below_or_equal(info, ptroffset)) { ptroffset += curnode->_ptroffset; return true; } } return false; } } unsigned int _id; const _parent_node* _pn; };

    // Dynamically casts an plugin class to another plugin class. The syntax for this operation is identical to // dynamic_cast, except it only works with pointers, not references. template <typename _TO, typename _FROM> _TO plugin_cast(_FROM _obj) { if (_obj == 0) return (_TO)0; else { return static_cast<_TO>(plugin_typeid(*_obj).cast_pointer(_obj, plugin_class_pointer_typeid(_FROM), plugin_class_pointer_typeid(_TO))); } }

    #define __CALC_POINTER_OFFSET(_t1, _t2) ((int)(void*)(_t2*)(_t1*)(void*)16777216 - (int)16777216) #define __DECLARE_PLUGIN_PARENT_NODE(_name, _pn, _p, _next) static const utility::plugin_type_info::_parent_node _pn(_p::__class_plugin_typeid(), __CALC_POINTER_OFFSET(_name, _p), _next); #define __PLUGIN_IMPLEMENTATION_ID(_name) ((unsigned int)_name::__class_plugin_typeid) #define __PLUGIN_INTERFACE_ID(_id) ((unsigned int)_id * 4 + 3)

    #define __PRE_DECLARE_PI(_name) \ virtual const utility::plugin_type_info& __get_plugin_typeid() const { return __class_plugin_typeid(); } \ struct __my_plugin_type_info : public utility::plugin_type_info { inline __my_plugin_type_info(int _id, const _parent_node* _pn) : plugin_type_info(_id, _pn) {} }; \ inline static const utility::plugin_type_info& __class_plugin_typeid() \ {

    #define __POST_DECLARE_PI(_id, _name, _pn) \ static const __my_plugin_type_info ret(_id, _pn); \ return ret; \ }

    #define __DECLARE_PI0(_id, _name) \ typedef _name __root_plugin_type; \ __PRE_DECLARE_PI(_name) \ __POST_DECLARE_PI(_id, _name, 0)

    #define __DECLARE_PI1(_id, _name, _p1) \ typedef _p1::__root_plugin_type __root_plugin_type; \ __PRE_DECLARE_PI(_name) \ __DECLARE_PLUGIN_PARENT_NODE(_name, pn1, _p1, 0); \ __POST_DECLARE_PI(_id, _name, &pn1)

    #define __DECLARE_PI2(_id, _name, _p1, _p2) \ typedef _p1::__root_plugin_type __root_plugin_type; \ __PRE_DECLARE_PI(_name) \ __DECLARE_PLUGIN_PARENT_NODE(_name, pn2, _p2, 0); \ __DECLARE_PLUGIN_PARENT_NODE(_name, pn1, _p1, &pn2); \ __POST_DECLARE_PI(_id, _name, &pn1)

    #define __DECLARE_PI3(_id, _name, _p1, _p2, _p3) \ typedef _p1::__root_plugin_type __root_plugin_type; \ __PRE_DECLARE_PI(_name) \ __DECLARE_PLUGIN_PARENT_NODE(_name, pn3, _p3, 0); \ __DECLARE_PLUGIN_PARENT_NODE(_name, pn2, _p2, &pn3); \ __DECLARE_PLUGIN_PARENT_NODE(_name, pn1, _p1, &pn2); \ __POST_DECLARE_PI(_id, _name, &pn1)

    #define __DECLARE_PI4(_id, _name, _p1, _p2, _p3, _p4) \ typedef _p1::__root_plugin_type __root_plugin_type; \ __PRE_DECLARE_PI(_name) \ __DECLARE_PLUGIN_PARENT_NODE(_name, pn4, _p4, 0); \ __DECLARE_PLUGIN_PARENT_NODE(_name, pn3, _p3, &pn4); \ __DECLARE_PLUGIN_PARENT_NODE(_name, pn2, _p2, &pn3); \ __DECLARE_PLUGIN_PARENT_NODE(_name, pn1, _p1, &pn2); \ __POST_DECLARE_PI(_id, _name, &pn1)

    // Macros for declaring classes with the native run-time information. This declaration assumes that // _id is an unsigned int unique amongst all of the native classes declared, and deterministic across // compilers. The former implies that every native declaration has to know a little bit about every // other native declaration (in order to avoid id conflicts). The latter implies that the id cannot // be compiler or load-time dependent (so pointers won't work!). And one additional restriction, _id // must be less than INT_MAX / 4, or it may conflict with other native ids. This macro must be defined // under a public class heading (couldn't put public: in a macro). // Syntax: __DECLARE_NATIVE<N>(<unique id>, <class name>, <1st parent>, <2nd parent>, ...), // where <N> is the number of parents the class has. #define __PLUGIN_INTERFACE0(_id, _name) __DECLARE_PI0(__PLUGIN_INTERFACE_ID(_id), _name) #define __PLUGIN_INTERFACE1(_id, _name, _p1) __DECLARE_PI1(__PLUGIN_INTERFACE_ID(_id), _name, _p1) #define __PLUGIN_INTERFACE2(_id, _name, _p1, _p2) __DECLARE_PI2(__PLUGIN_INTERFACE_ID(_id), _name, _p1, _p2) #define __PLUGIN_INTERFACE3(_id, _name, _p1, _p2, _p3) __DECLARE_PI3(__PLUGIN_INTERFACE_ID(_id), _name, _p1, _p2, _p3) #define __PLUGIN_INTERFACE4(_id, _name, _p1, _p2, _p3, _p4) __DECLARE_PI4(__PLUGIN_INTERFACE_ID(_id), _name, _p1, _p2, _p3, _p4)

    #define __PLUGIN_IMPLEMENTATION0(_name) __DECLARE_PI0(__PLUGIN_IMPLEMENTATION_ID(_name), _name) #define __PLUGIN_IMPLEMENTATION1(_name, _p1) __DECLARE_PI1(__PLUGIN_IMPLEMENTATION_ID(_name), _name, _p1) #define __PLUGIN_IMPLEMENTATION2(_name, _p1, _p2) __DECLARE_PI2(__PLUGIN_IMPLEMENTATION_ID(_name), _name, _p1, _p2) #define __PLUGIN_IMPLEMENTATION3(_name, _p1, _p2, _p3) __DECLARE_PI3(__PLUGIN_IMPLEMENTATION_ID(_name), _name, _p1, _p2, _p3) #define __PLUGIN_IMPLEMENTATION4(_name, _p1, _p2, _p3, _p4) __DECLARE_PI4(__PLUGIN_IMPLEMENTATION_ID(_name), _name, _p1, _p2, _p3, _p4)

    }; //namespace utility {

    #endif //#ifndef __PLUGIN_TYPE_INFO__

    Currently browsing [oop-plugin.zip] (16,051 bytes) - [test_object_plugin/plugin.cpp] - (884 bytes)

    #include "plugin.h"
    #include <windows.h>

    namespace utility {

    factory::handle::handle(const char* path) : f(0), data(0) { data = (void*)::LoadLibrary(path); if (data == 0) return; fp_get_factory fp = (fp_get_factory)::GetProcAddress((HMODULE)data, fp_get_factory_name); if ((fp == 0) || ((f = fp()) == 0)) { ::FreeLibrary((HMODULE)data); data = 0; return; } }

    factory::handle::~handle() { unload(); }

    factory::handle& factory::handle::operator=(const handle& c) { unload(); if (c.valid()) { char buffer[MAX_PATH]; if (::GetModuleFileName((HMODULE)c.data, buffer, MAX_PATH) != 0) { data = (void*)::LoadLibrary(buffer); if (data != NULL) f = c.f; } } return *this; }

    void factory::handle::unload() { if (!valid()) return; f = 0; ::FreeLibrary((HMODULE)data); data = 0; }

    }; // namespace utility

    Currently browsing [oop-plugin.zip] (16,051 bytes) - [test_object_plugin/testbase.h] - (388 bytes)

    #ifndef __TESTBASE_H__
    #define __TESTBASE_H__

    #include "plugin.h"

    class testbase : public utility::plugin { public: // Make sure id does not conflict with any other plugin interface. (id 1 is reserved for plugin class). __PLUGIN_INTERFACE1(2, testbase, utility::plugin);

    // Test function. virtual void print_hello() const = 0; };

    #endif //#ifndef __TESTBASE_H__

    Currently browsing [oop-plugin.zip] (16,051 bytes) - [test_object_plugin/testplugin/testplugin.cpp] - (969 bytes)

    #include "../testbase.h"

    class testplugin : public testbase { public: __PLUGIN_IMPLEMENTATION1(testplugin, testbase); virtual void destroy() throw() { delete this; } virtual utility::plugin* clone() const throw() { return new testplugin(*this); } virtual utility::plugin* instance() const throw() { return new testplugin(); } virtual void print_hello() const { printf("hello world\n"); } };

    class testfactory : public utility::factory { public: virtual size_type size() const throw() { return 1; }

    virtual utility::plugin* create_plugin(size_type index) const throw() { if (index == 0) return new testplugin(); else return 0; }

    virtual const utility::alien_type_info* plugin_typeid(size_type index) const throw() { if (index == 0) return &alien_class_typeid(testplugin); else return 0; } };

    __PLUGIN_EXPORT const utility::factory* get_factory() { static const testfactory f; return &f; }

    The zip file viewer built into the Developer Toolbox made use of the zlib library, as well as the zlibdll source additions.

     

    Copyright 1999-2008 (C) FLIPCODE.COM and/or the original content author(s). All rights reserved.
    Please read our Terms, Conditions, and Privacy information.