|
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.
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:
- Notifier - This base class keeps all reference connection data
in member variables.
- 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:
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.
|