DynObj - C++ Cross platform plugin objects

NOTE: This article has not been updated for the DynObj 0.80 release. Most things should work as described, but there may be some differences. The examples in the source folders are up to date though.

NOTE: The Doxygen generated documenation is up to date: DynObj doxygen docs.

Introduction

DynObj is an open source library that provides a C++ application with run-time class loading facilities (aka plugins). It's written in standard C++, can be used with any C++ compiler with good template support, so it is cross-platform from the outset. It uses a minimal platform specific layer to handle OS specifics (Win32, Linux + various Unix supported now).

The project started out with me needing a way to support plugins in a cross platform application. The approaches I found were either too heavy weight (Modzilla XPCOM) or were platform/compiler specific. An article by Chad Austin provided a good starting point for the DynObj library.

This article will cover some ground, so it is split in a number of sections:
  • Intro - This page. General description of the problem area.
  • Background - Explores C++ classes and the linking process.
  • Solution - Describes the DynObj library.
  • Sample - A sample plugin together with a main application.
  • Library documentation - Documentation for the DynObj library.
  • Building - Documentation for building the DynObj library.

Sourceforge project here.

Background - Problem area

C++ is a very feature rich language and within the same link time module (all sources and libraries) functionality can be exposed and shared without much difficulty. You may need to get some link flags right, but it can be done, with templates and the whole C++ machinery operating.

This can be extended to run-time with shared libraries (DLLs on Windows, shared dynamic object files (SO) on various Unices).

However, it quickly becomes rather difficult, since there is an intricate linking process going on between the application and the loaded modules. With C++ name mangling and automatically generated templates and a rich set of compiler/linker options, dependencies become complicated. With a code base under heavy development, linking with a a DLL compiled on a different system a month ago is not likely to work.

This approach usually assumes that the same compiler is used for host and library. To link the binaries, often also the compiler version must be similar. You could not expect to link together a template or class library compiled with G++ with an application compiled with a Microsoft based compiler. You'd have problems doing it with a version of the same compiler from a year ago.

The approach described above can be termed a 'tight' or a 'full' linking scheme.

C++ has never had it easy to make its internal features available to the outside world in a standard way. Old style:

extern "C" int FunctionToExport(...)

has often been the way (and yes, this works reliably but can only expose global functions and variables).

A plugin approach

A plugin is a more decoupled run-time library, supposedly not dependent on the main application being a certain version, and preferably the requirements on the plugin should be a bit more loose (they shouldn't need to know about the applications every header file and #define, and they need not mutually resolve massive amounts of global symbols).

C++ doesn't provide any language or standard library support for this, but there are some good starting points in the language.

What can be investigated is what parts of the language can be used without creating link-time complexity, and what parts to be avoided for a plugin. Maybe one is not confined to only extern "C" ....

On the Windows platform, there is of course COM as a way forward. To be language neutral, it limits (severely) what features of C++ can be exposed and then re-used inside another run-time module (including expressing inheritence relationships). It is based on a particular way of sharing the VTables for run-time binary objects. However, it works, but has not evolved in a cross-platform direction.

Middle ground

What we're looking at is if there some larger middle ground that can be defined, between (A) the compile time process when the full C++ feature set (and lots of header files) are be shared, and (B) the run-time DLL loading where only type-less symbols can be looked up in a loaded module (extern "C"...")

The next section will define this middle ground and later introduce an object framework (DynObj) around it.