DynObj - Dynamic Binary Runtime Plugin Objects Hosted by:
SourceForge.net Logo

Object ownership in DynObj

Here we will look at object ownership and destruction, object sharing and tracking over DynObj plugin boundaries.

Single strong point of ownership

The default ownership model is single point strong ownership, implemented through the DynObj interface. An object is destroyed using the member function DynObj::doDestroy():
  • DynObj *pdo = /* instantiate object */;
    // Use object
    pdo->doDestroy(); // The object is destroyed
To hold references to 'single point ownership objects', weak references are used, see below.

Coming from COM or xpCOM, where reference counted objects is the only option, this is different. However, reference counting can be implemented with single point ownership but the opposite is not true. So, the DynObj library provides a more fundamental method.

VObj, DynI and DynObj

In many situations we access a 'sub-object' of a larger binary object through an interface. In this situation, ownership makes no sense. We are using an object without any ownership on it, so we should not be able to affect its life span in any way. The DynI interface fills this role (or equivalently VObj which only states that an object has a VTable).

The DynObj interface derives from DynI and represents an object that can be created and destroyed. In particular, it adds functions for object destruction (doDestroy(),doDtor()).

It is also possible for an interface to define its own methods for object life time handling.

Trackable objects and weak references

By convention, any object that implements NotifierI is trackable. Knowing this, we can be sure that to receive a notification whenever the object is destroyed. This allows a smart pointer to reset its reference.

It is enough that we add an implementation of NotifierI as a base class for our object:
  • // %% DYNOBJ class(VObj)
    class GrowableSignal : public SignalI, public Notifier {
    • virtual const char* GetName( ) = 0;
      // ... and the rest of the interface
    };
Then a host or another plugin can hold safe references to our type.

Note: The template classes DynObjPtr<T> and DynWeakPtr<T> are smart DynObj pointers relying on this mechanism (see doBase.hpp). The former can track objects in some different ways, while the later one relies exclusively on NotifierI.

For object tracking, there are two main implementations of NotifierI:
  1. Notifier - This base class keeps all reference connection data in member variables.
  2. GNotifier - This base class keeps reference data in external tables and does not add extra weight to the derived type.
The two does the same thing. If an object is likely to be referenced, it is best to use 1. If objects of a type are only rarely referenced from outside, GNotifier is the better choice.

An example: The VFileBase interface represents a file in any type of storage media. Often we have trees with 1000+ files opened. But only very few of these are referenced from outside. So GNotifier is the better choice here.

Static global objects

One useful technique for exposing some functionality from a plugin is to declare a single global instance of the type in the source file. Then, after making it publicly available, a plugin or host is free to use this object. In this way, we side step the ownership issue completely (the users cannot affect the life time of a global instance in any way).

As an example, the interface StrConvI provides string conversion between different character representations:
  • // %% DYNOBJ class(VObj)
    class StrConvI : public DynI {
    • // Convert from UTF8 string to local 8 bit code page.
      virtual int docall ToLocal8Bit( const char* pu8_str, DynArrChar& out,
      • const char* pu8_end=NULL ) = 0;
      // Convert from UTF8 to wchar_t string
      virtual int docall ToWide( const char* pu8_str, DynArrWCharT& out,
      • const char* pu8_end=NULL ) = 0;
    };
Now in the implementation source file, we have:
  • // %% DYNOBJ class(VObj) bases(StrConvI, NotifierI)
    class StrConvC : public StrConvI, public Notifier {
    • // Convert from UTF8 string to local 8 bit code page.
      virtual int docall ToLocal8Bit( const char* pu8_str, DynArrChar& out,
      • const char* pu8_end=NULL ){
        // Implementation
      }
      // and more implementation...
    };
    static StrConvC g_str_conv; // Declaring a global singleton!
Adding Notfier as a base class we make sure that any users of our class gets notified, in the case this plugin is unloaded.

Reference counted objects

Reference counted objects can be implemented via the interface RefCountI:
  • // %% DYNOBJ class(VObj)
    class RefCountI {
    • virtual int docall IncRef() = 0;
      virtual int docall DecRef() = 0;

      // Utility functions, DecRef is a safer name than Release
      inline int AddRef(){ return IncRef(); }
      inline int Release(){ return DecRef(); }
    };
Then, any plugin or host can query for the RefCountI interface and use it:
  • SomeVObj *psvo = /* get reference to object with VTable from somewhere */;
    RefCountI *prci = do_cast<RefCountI*>(psvo);
    if( prci ){
    • pcri->IncRef();
      // Do what should be done...
In the core class hierarchy, the interface DynSharedI implements RefCountI and delegates object destruction to DynObj::doDestroy(). The class DynSharedC [in file doDynShared.hpp] provides an implementation of this with atomic reference counting. It forms a useful base class to reference counted objects.