|
Limitations with DynObj Runtime Objects
DynObj enables creating plugin types and using plugin objects almost
as ordinary classes and objects are created and used. Even so, there
are a number of limitations and issues to keep in mind:
No static linking
The most obvious one is that we cannot use global functions from a plugin that
imply static linking. This includes:
- Ordinary global functions:
float SumVector( float *elems, int size ); // Not in a plugin!
Non in-line constructors fall in this category.
- Plain member functions:
-
// %% DYNOBJ class(VObj)
class SignalI {
-
virtual const char* GetName( ) = 0;
virtual int GetLength( ) = 0;
virtual float* GetSamples( ) = 0;
float GetEnergy(); // Not in a plugin!
};
The function GetEnergy() cannot be used by a host, it is
not virtual and a linker would report 'Unresolved symbol'.
- Static member functions:
These are essentially plain functions that are bound into the
namespace of a class.
...inline member functions are OK
Using inline functions in the interface is OK and quite useful:
-
// %% DYNOBJ class(VObj)
class SignalI {
-
// Virtual member funcs, see above
inline bool IsEmpty(){ return GetSamples()==NULL || GetLength()==0; }
};
Since both the plugin compiler and the source compiler generate
code for inline functions as they are used, there is no problem.
Inline 'utility' functions in a plugin class can be quite useful,
they indicate how to use the class and help keeping the virtual
function tables small. Their signatures may also be modified after
an interface has stabilized, doing that won't break any compiled apps
using the plugin.
...template member functions are OK
If an inline function is a template function, that is also OK,
provided that the body of the template function in the plugin
header file.
No C++ exceptions
The implementation of C++ exception handling has compiler specific
parts and linking ambiguities. In many cases, it is not possible to
throw and catch C++ exceptions across plugin boundaries.
No virtual function name overloading
For historical reasons, name overloading for a virtual function is
to be avoided:
-
// %% DYNOBJ class(VObj)
class SignalI {
-
// Virtual member funcs, see above
virtual SignalI& Append( SignalI& sig_other );
virtual SignalI& Append( float* samples, int length ); // No!
};
What happens is that two different compilers may choose to put the
functions with same name in opposite order in the VTable. We risk
calling the wrong function in that case.
A better solution in this case would be:
-
// %% DYNOBJ class(VObj)
class SignalI {
-
// Virtual member funcs, see above
virtual SignalI& Append( float* samples, int length );
// Make the other function inline:
inline SignalI& Append( SignalI& sig_other ){
- return Append( sig_other.GetSamples(), sig_other.GetLength() );
}
};
Overloading between inline functions and virtual functions is OK.
We get to keep the semantics intended in the first interface and
keep the VTable size down.
No raw allocations for parameters/return values
We must avoid any situation where memory is allcoated (using any
functions in the new/malloc family) on one side of a plugin
boundary and an attempt to free it is made on the other side.
Different compilers are often linked with different memory allocators
so we would end up with run-time errors.
No virtual destructors
Different compilers can implement and use virtual destructors in
slightly different ways. For example, the GCC compiler usually
allocates two slots in a VTable for destructors, while Visual
C++ just uses one.
The ordinary way of destroying objects in the DynObj library is
to use DynObj::doDestroy().
If you have to expose a plugin class that has a virtual destrcutor,
you can insert DO_VTABLE_DTOR_PADDING (from doSetup.h)
after each such declaration:
-
// %% DYNOBJ class(VObj)
class SomeIface {
-
// Virtual member funcs, see above
virtual ~SomeIface( );
DO_VTABLE_DTOR_PADDING;
/* Other declarations here */
};
The macro above only makes sure that the VTables end up the same size
from perspectives of a host and a plugin. Invoking the virtual
destructor over plugin boundaries is not supported by the DynObj
library.
Be careful with interface data members
It is possible to include data members in an interface declaration:
-
// %% DYNOBJ class(VObj)
class ColumnI {
-
// Virtual member funcs, see above
virtual const char* GetColumnName() = 0;
int m_column_id; // Data member!
inline int GetId(){ return m_column_id; }
};
If it is clear that each instance of ColumnI needs an ID,
that an int is the suitable type for it, and that an application
should be allowed to modify it, then, this is a way of achieveing
that.
Strictly speaking, ColumnI is no longer an interface, but a
class.
Issues to be aware of:
- If we declare several member variables:
-
int m_column_id;
bool m_inserted;
short m_width;
the compiler will have many options in laying out the binary
object.
We can try to align each member variable on a word boundary,
but we have no guarantee that a potential host compiler will
use the same data layout even so.
- Different language compilers may have problems with handling
objects like this. The model in D is for example that
interfaces have no data members.
|