DynObj - C++ Cross platform plugin objects

The DynObj Library

DynObj is an open-source cross-platform library that uses the run-time plugin approach just decribed. Although the mechanisms used are generic and fairly simple, the library fills many gaps and make it straight-forward to use plugins inside a C++ application.

The library provides:
  • A small class hierarchy (VObj, DynI, DynObj, DynSharedI) establishing some common ground between a host and a plugin. The DynObj library also works with classes that are not rooted in this hierachy.

  • A type description facility that allows types to be defined and shared by both host and plugins (DynObjType).

  • A way to convert C++ types to a plugin library (doTypeInfo, DO_DECL_TYPE_INFO).

  • Cast functions to query an object about the types it implements. This is similar to dynamic_cast<T> in C++ or QueryInterface in COM (do_cast<T>, doGetObj,...).

  • Instantiating C++ objects from plugins (do_new<T>).

  • A plugin library loading/unloading mechanism(DynObjLib).

  • C-level functions to handle objects.

  • Other practical C++ classes and templates for objects.
In addition to these library facilities, it includes a tool (pdoh) that parses C++ header files and generates source code for type registration.

VObj, DynI, DynObj and friends

All classes discussed below are defined in DynObj.h. The library is based on some properties of objects with VTables:
  • The VPTR is always stored first in a binary object
  • VTables are shared by all instances of a class, but not with instances of any other class (so it provides a type identifier).
In C++ there is not a builtin way to denote these classes. However, we define the class VObj to represent an object with a VTable with unknown size and methods. VObj in that sense becomes the 'stipulated' root class for all classes that contain one or more virtual functions.

The VObj class:

  Base:     (no base class)  
  Methods:     (no methods)  

We see that VObj does not introduce any methods (it cannot since that would interfere with derived classes which use the first VTable slot).

However VObj has a number of convenient inline functions to query for types (VObj::IsA, VObj::CanBeA, VObj::GetObj,...), asking about errors (VObj::Get/Set/ClearError) and more.

To determine if a class is a VObj or not, these templates can be used:

bool has_vtable = IsVObj<SomeType>::v;

template<class T>
VObj* to_vobj( T* pt );

This provides type safety so that we cannot try to convert say a char* to an interface pointer (the compiler would give an error).

The DynI class:

  Base:     VObj  
  Returns     Methods:     Arguments  
  DynObjType*     doGetType       
  void*     doGetObj     const char* type_name  
  const char*     doGetError     int *perr_code  
  void     doClearError       

The DynI class provides a way to know its type (doGetType) and for asking about other types it supports (doGetObj). To ask if a DynI supports the DynStr interface:

DynI *pdi = /* Wherever pointer comes from */;
DynStr *pds = pdi->doGetObj("DynStr");

This is equivalent to:

DynStr *pds = do_cast<DynStr*>(pdi);

The DynI class has an advantage over VObj:
  • It knows its own derived type
In contrast, to find the type of a VObj, a lookup into a global table, using the VPTR, has to be made (and works only after the types has been registered).

Since DynI is used across DLL (and possibly compiler) boundaries, we cannot use C++ exceptions. To provide error handling, the methods doGetError and doClearError are introduced. They allow for an object specific error state, without burdening the class with member variables for this. SetError is not a member, since object errors are usually are not set from 'outside'.

We see also that the DynI interface has no support for creation or destrcution. The same applies to VObj. The lifespan that can be assumed is that of the current method.

If a reference to the object is to be kept, these are different ways to go about it:
  • Ask for a DynSharedI interface (ref counted ownership)
  • Create a weak reference (if object supports NotifierI))
  • The object may be a known global or singleton which explicitely allows for references to be stored

The DynObj class:

  Base:     DynI  
  Returns     Methods:     Arguments  
  void     doDestroy       
  void     doDtor       

The DynObj interface represents an object that can be created an destroyed. It represents an object owned from a single point. Usually the functions doDestroy and doDtor would be implemented like:

void MyClass::doDestroy(){ ::delete this; }
void MyClass::doDtor(){ this->~MyClass(); }

An object is destroyed through doDestroy. doDtor provides access to the destructor of the class (library internal use).

Objects can be created in some different ways:
  • Using do_new<T>
  • Using DynObjLib::Create(...)
  • Temporary objects can be created and released using DynObjHolder<T>

The DynSharedI class:

  Base:     DynObj  
  Returns     Methods:     Arguments  
  int     doAddRef       
  int     doRelease       

The DynSharedI interface represents an object with shared ownership. doAddRef and doRelease increases and decreases the ownership counter.

DynSharedI derives from DynObj since it depends on a way of destroying itself (DynObj::doDestroy) when the lifetime counter reaches 0.

To protect the object from being deleted before its actual end-of-life, a doDestroy method can check that the counter is actually zero:

virtual void docall doDestroy( ) {
   if( !m_ref_cnt )
      ::delete this;
            "DynSharedI - Destroy on non-zero ref");

Documenation on building the DynObj library.