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

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:
  1. Ordinary global functions:
    float SumVector( float *elems, int size );    // Not in a plugin!
    Non in-line constructors fall in this category.

  2. 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'.

  3. 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.