Table of Contents
Part 1: A simple plug-in architecture pattern for C++ applications on Win32 [1] .................................... 4
The Problem .............................................................................................................................................. 4
The Solution .............................................................................................................................................. 4
High-level design ....................................................................................................................................... 5
Low-level design........................................................................................................................................ 5
The code.................................................................................................................................................... 6
Other enhancements .............................................................................................................................. 12
Part 2: DynObj - C++ Cross Platform Plugin Objects [2] ......................................................................... 13
Introduction ............................................................................................................................................ 13
Background - Problem Area ................................................................................................................ 14
A Plugin Approach ............................................................................................................................... 14
Middle Ground .................................................................................................................................... 15
A C++ Class .............................................................................................................................................. 15
VTables ................................................................................................................................................ 16
C++ Inheritance ................................................................................................................................... 16
Common Ground Defined ................................................................................................................... 18
The Run-Time Link............................................................................................................................... 18
Plugin Object Creation ........................................................................................................................ 18
Plugin Object Destruction ................................................................................................................... 19
Linking Revisited ................................................................................................................................. 20
Solution ................................................................................................................................................... 20
Cross-Platform .................................................................................................................................... 20
Cross Compiler .................................................................................................................................... 20
C++ Classes used Across DLL/SO Boundary ........................................................................................ 20
Object Model ...................................................................................................................................... 21
C++ Type Query ................................................................................................................................... 21
Arbitrary Types and DynI Derived Types............................................................................................. 21
Simple Type Identifiers ....................................................................................................................... 22
Plugin Role .......................................................................................................................................... 22
Light-Weight........................................................................................................................................ 22
Facilities............................................................................................................................................... 22
Source Code Preprocessor .................................................................................................................. 23
With other Languages ......................................................................................................................... 23
Requirements ...................................................................................................................................... 23
A Sample: Plugin + Application ............................................................................................................... 24
Creating an Interface File .................................................................................................................... 24
Creating an Implementation File ........................................................................................................ 26
A Main Application.............................................................................................................................. 29
The DynObj Library ................................................................................................................................. 30
VObj, DynI, DynObj and Friends.......................................................................................................... 31
The VObj Class..................................................................................................................................... 31
The DynI Class ..................................................................................................................................... 32
The DynObj Class................................................................................................................................. 33
The DynSharedI Class .......................................................................................................................... 33
Building and Using the Library ................................................................................................................ 34
Build Model ......................................................................................................................................... 35
Compiler Defines................................................................................................................................. 35
Building the DynObj library ................................................................................................................. 35
Building a Main Application (Using Plugins/Modules)........................................................................ 36
The DynStr Library............................................................................................................................... 36
Building the Samples ........................................................................................................................... 36
The pdoh Tool ..................................................................................................................................... 37
Part 3: Send data to/from C++ and C# Windows processes [3] ............................................................. 37
Introduction ........................................................................................................................................... 37
Why use WM_COPYDATA? .............................................................................................................. 37
Platforms supported ............................................................................................................................. 38
Other facts ............................................................................................................................................. 38
Part 1: A simple plug-in architecture
pattern for C++ applications on Win32 [1]
By George Mihaescu
Summary: This article presents a simple and elegant solution to creating components that can be
dynamically deployed and loaded by a C++ Win32 application without the need of any
framework / infrastructure or technology (such as COM). It relies on basic C++ mechanisms and
two commonly used Win32 API calls.
Download the code here (VC++ projects + source).
Note: code snippets in the document and code available for download are now based on VS .Net
2005 / CRT 8.0
The Problem
You have a C++ Win32 application to which you want to be able to “attach” components
dynamically, developed either by yourself or by other parties. In the context of this document I
will call those components plug-ins, by analogy with the well-known web browser add-on
components. In the same manner with the browsers, you want your application to be aware of
such plug-ins that were developed and deployed possibly long after the application itself was
developed and deployed. Ideally, you don’t want the application to even need to restarted – what
you’d like is to drop the plug-in at a know location (e.g. somewhere under the application
installation directory) even as the application is running, and the application all of a sudden has
enhanced functionality. Can this be done in a simple and reliable way?
The Solution
Many readers will immediately dismiss the problem and this article by saying “of course, that’s
what COM is all about”. But I’m not in favor of using COM unless there is a very compelling
reason for it. Generally, if it can be done without COM (without making the code overly
complex – I don’t want to re-implement COM), then why bother with COM? Say COM and you
say code complexity, registration issues (such as registration requiring certain user security
privileges), dependency on registry, application usually needing to be re-started, etc. I argue
below that I can meet the requirements of this problem without COM, in a much simpler and
reliable way.
High-level design
My solution is based on marrying the C++ polymorphic mechanism with the Win32 APIs
LoadLibrary and GetProcAddress.
The principle is that the main application publishes a contract with the plug-ins in the form of an
interface that the plug-ins are expected to implement in order to meet the contract. Then the main
application will scan a known location on disk (e.g. a “plugins” directory relative to its
installation directory) and attempt to load all plug-ins (using LoadLibrary API) that export
implementations of this interface (determined by using the GetProcAddress API). Each plug-in
is packaged in its own DLL (or multiple DLLs) that must be deployed at the location the
application expects them in ordered to be detected and loaded by the application.
Low-level design
As C++ does not offer a language construct to model the concept of an interface, we will use the
next best thing available: an abstract base class.
Also, because the Win32 API GetProcAddress uses a function name as a parameter, while our
plug-ins are C++ (because they need to provide a concrete derivative of the abstract base class)
and because C++ does function name mangling, our plug-ins will need to export as a minimum
one C-style function to act as the class factory. To keep things balanced and because the
application has (in theory) no way of knowing what allocation strategy each plug-in factory
function uses, it only makes sense to ask plug-ins to also export the counter-part of the factory
function, another C-style function to act as the plug-in clean-up / tear-down procedure.
So, to sum it up:
program has an abstract class through which it will use all dynamically
The main
loaded plug-ins. It also has a few lines of code to scan a known location and look for DLLs
that export two known C-style functions: the plug-in factory and the plug-in clean-up.
Each plug-in has a class implementing the abstract class in the main program, and
is packaged as a Win32 DLL exporting two C-style functions: the plug-in factory and the
plug-in clean-up.
This is illustrated in the diagram below. As you can see, there is no registration required, no need
for the user to have special privileges on the machine, no framework / runtime dependency other
than what you already have: C++ and Win32.
The code
Below is a sample “contract” (abstract class) in the main program that plug-ins will need to
implement. Of course, the methods of this class are going to be specific to what your plug-ins
need to do:
//////////////////////////////////////////////////////////////////////////
// Abstract base class ("interface") for the concrete plugin implementations
class IPlugin
{
public:
//Add whatever functions each plugin needs to implement
//Those below are just dummy examples to illustrate the principle
//returns the name of the concrete plugin
virtual const char* Get_Name () const = 0;
//does the actual data processing
virtual void Process_Data () = 0;
};
/////////////////////////////////////////////////////////////////////////
//Extern "C" functions that each plugin must implement in order to be
//recognized as a plugin by us.
// Plugin factory function
//extern "C" IPlugin* Create_Plugin ();
// Plugin cleanup function
//extern "C" void Release_Plugin (IPlugin* p_plugin);
Below is the code in the main program that scans for plug-ins, determines that they are indeed
exporting the two C-style functions it expects from a plug-in, then loads and executes each plug-
in found:
#include "IPlugin.h" //for the IPlugin abstract base
//convenience typedef for the pointers to the 2 functions we
//expect to find in the plugins
typedef IPlugin* (*PLUGIN_FACTORY)();
typedef void (*PLUGIN_CLEANUP)(IPlugin*);
int main(int argc, char* argv[])
{
//get the program's directory
char dir [MAX_PATH];
::GetModuleFileName (NULL, dir, MAX_PATH);
//eliminate the file name (to get just the directory)
char* p = ::strrchr (dir, '\\');
*(p + 1) = 0;
//find all DLLs in the plugins subdirectory
char search_parms [MAX_PATH];
::strcpy_s (search_parms, MAX_PATH, dir);
::strcat_s (search_parms, MAX_PATH, "plugins\\*.dll");
WIN32_FIND_DATA find_data;
HANDLE h_find = ::FindFirstFile (search_parms, &find_data);
BOOL f_ok = TRUE;
while (h_find != INVALID_HANDLE_VALUE && f_ok)
{
//load each DLL and determine whether it is exporting
//the functions we care about
char plugin_full_name [MAX_PATH];
::strcpy_s (plugin_full_name, MAX_PATH, dir);
::strcat_s (plugin_full_name, MAX_PATH, "plugins\\");
::strcat_s (plugin_full_name, MAX_PATH, find_data.cFileName);
HMODULE h_mod = ::LoadLibrary (plugin_full_name);
if (h_mod != NULL)
{
PLUGIN_FACTORY p_factory_function =
(PLUGIN_FACTORY) ::GetProcAddress (h_mod, "Create_Plugin");
PLUGIN_CLEANUP p_cleanup_function =
(PLUGIN_CLEANUP) ::GetProcAddress (h_mod, "Release_Plugin");
if (p_factory_function != NULL &&
p_cleanup_function != NULL)
{
//yes, this DLL exposes the 2 functions we need,
//it is a plugin we can use!
//invoke the factory to create the plugin object
IPlugin* p_plugin = (*p_factory_function) ();
//show which plugin it is, and let the plugin
//do the processing
printf ("Now working with plugin: %s\n",
p_plugin ->Get_Name ());
p_plugin ->Process_Data ();
//done, cleanup the plugin by invoking its
//cleanup function
(*p_cleanup_function) (p_plugin);
}
::FreeLibrary (h_mod);
}
//go for the next DLL
f_ok = ::FindNextFile (h_find, &find_data);
}
return 0;
}
And finally, here is the code for one such plug-in:
#include "stdio.h"
#include "..//MainProgram//IPlugin.h"
////////////////////////////////////////////////////////////////////////
// A concrete plugin implementation
////////////////////////////////////////////////////////////////////////
// Plugin class
class Plugin1 : public IPlugin
{
public:
//returns the name of the concrete plugin
const char* Get_Name () const
{
return "Plugin1";
}
//does the actual data processing
virtual void Process_Data ()
{
for (int i = 0; i ; // This works
to instantiate objects from inside plugins. The conversion:
C++ type => (type string,type ID)
is taken care of by templates classes available in the host application.
Plugin Object Destruction
There are some points to consider here to keep cross-compiler compatibility:
We cannot be sure that the host and the plugin share the same memory allocator (on Windows
this is often not the case). So using C++ delete on plugin objects is not a good idea.
Virtual destructors are used in different ways by different compilers.
Essentially, the host must make sure a plugin object is 'recycled' by the same plugin that created
it. To handle this, the DynObj framework has used a solution where each object that is created
has a virtual member function doDestroy():
Collapse
DynObj *pdo = /* Create object and use it */;
pdo->doDestroy(); // End of object
We see here that we have used DynObj as a base class for objects that are created by a plugin.
The DynObj framework works with any classes, but objects that can be created and destroyed by
plugins must derive from DynObj.
Linking Revisited
The solution with factory functions gives the responsability of setting up the VTable to the
plugin, and so, all the functions we need from the plugin are contained in these pre-linked
VTables. Each instantiated object comes back with a VPTR as its first binary member.
The only run-time linking we have to do is to lookup these factory functions inside the plugin
DLL (and possibly some other init/exit functions). This keeps the host and the plugin in a loosely
coupled relationship, defined by the plugin interface. Next comes the description of the DynObj
solution using this approach.
Solution
This describes the properties of the DynObj library solution to the plugin/linking problem.
Cross-Platform
The library is written in C++, and a decent C++ compiler should build it (tested with MSVC 8
and G++ [4.1.2 and 3.4.5]). It relies on a minimalistic cross-platform layer for dynamic linking
and a spartan threading interface.
Cross Compiler
The library/plugin compiler can be a different one than the main application compiler. All
casting between types is allways done based on offsets from the source (library) compiler.
C++ Classes used Across DLL/SO Boundary
DynObj supports ordinary C++ classes across the plugin boundary. Any class that consists of:
Zero, one or more base classes/interfaces
Virtual functions (argument overloading not supported)
Inline functions
Operators
Data members (keep track of member alignment!)
So a fairly large subset of the C++ class concept can be used over the boundary. This is what
cannot be used:
Non-virtual member functions implemented in a separate source file
Static members (functions,data)
Object Model
The object from a plugin represents a full C++ object, including the possibility of having
multiple nested base classes. At the source code level, a tagging scheme is used to decide which
bases to expose. The whole (exposed part) of the inheritance tree is communicated to users of the
object.
The object is usually accessed using a single inheritance interface/class. Using a cast operation
(query type) one can move between the different interfaces/sub-objects that are implemented.
C++ Type Query
An object can implement a number of interfaces and/or classes. To query an object for another
type, the C++ template:
Collapse
template U do_cast(T t)
is used. It operates the same way as C++ dynamic_cast and provides typed safe casts across
the plugin boundary. do_cast (and related functions) provides similar functionality to
QueryInterface in COM.
An example:
Collapse
DynI pdi = /* Basic DynI pointer from somewhere */;
DynSharedI pdsi = do_cast(pdi)
Arbitrary Types and DynI Derived Types
The library introduces a small interface and class collection, based on DynI (a class which knows
its own type and can be queried for other types). Both classes based on DynI and arbitrary classes
with virtual methods may be used across the plugin boundary.
When using classes derived from DynI, a separate registration step may be skipped, since a DynI
object always knows its own type.
The provided classes derived from DynI also provides for a certain way of instantiating and
destroying objects (DynObj), for handling objects with shared ownership (DynSharedI), and also
for weak references.
When using arbitrary classes, they must have at least one virtual member function. The library
provides templates that safely detect if an object has a vtable or not. To use such objects across a
plugin boundary, one instance of the type must be registered first.
Simple Type Identifiers
Types are identified based on the pair:
Type string
Type identifier (32-bit integer)
This is a simple scheme that does not guarantee global (world-wide) type uniqueness. It can,
however, guarantee that the types used inside the application are unique. It is always simple to
find the string name for types. In cast operations, usually only the type integer is carried around
(no 128 bit ID structures).
Most times we don't need to know these, we just use the C++ types (which in their turn use the
type strings/IDs when needed).
Plugin Role
Plugins can use types from the main application (as long as it has headers for it) and also from
other loaded plugins. It can also instantiate plugin objects (from itself, other plugins, or the main
app).
Light-Weight
The library is self-contained and relatively small, including the cross-platform layer. A
compressed archive of the source is around 200 KB. It does not rely on STL, Boost or any other
big component library. It is not tied to a single platform API.
Facilities
The library includes a collection of practical classes, to handle libraries, object
instantiation/destruction, smart pointers and more.
Optionally (and recommended) one can use the class DoRunTimeI, which provides shared
resources to the application and the plugins. Among other things it makes sure that the various
libraries access the same type information, it provides for a pool of named 'published' objects,
per-object and per-thread error handling.
A run-time string class, DynStr (in itself a plugin object) is provided, giving plugins a way to
deal with Unicode strings.
Source Code Preprocessor
To setup a C++ class as a plugin type, some registration needs to be done and a library file must
be created. To help with this, a tool pdoh (Parse DynObj Header) is used. It reads C++ source
file and triggers on // %%DYNOBJ tags in the source code.
The pdoh tool outputs most of the glue code that is needed, including generating type ID:s.
With other Languages
The library relies on the default way of using vtables in C++ together with a binary type
description structure. This is a simple binary scheme. So, plugin classes could be used from any
language that can use these. A C implementation is straight forward (an object would be a
structure with the first member being a pointer to an array of functions). Also, a plugin class
could be implemented in another language and used from C++.
Inline functions cannot be shared with another language (they are really compiled on both host
and plugin side).
Requirements
The library relies on these features from the C++ compiler:
It uses vtables in the default way (one pointer per function, first function at index 0, new
functions are stored in declaration order)
Support for extern "C" style of exposing non-mangled function names
Support for __cdecl function calling convention
When a library is compiled, this information is stored and made available at load time, so an
incompatible library can be detected. Virtual destructors are not used across plugin boundaries,
since compilers implement them in slightly different ways. Some earlier versions of g++ (prior to
version 2.8) used two slots per function in the VTable, that would not have been compatible.
When exposing data members in a class across a plugin boundary, the best is to make each
member fill up one word (32/64-bit) in the structure. That avoids any possibility of unaligned
data access. The size of an exposed type (using sizeof from the plugin compiler) is stored in the
type information. The user of a plugin class could detect if data members are aligned differently.
The calling convention can be configured when the library is compiled, some other convention
could be used as long as the main and plugin compiler agree on it. On Linux, the default
(implicit) calling convention is __cedcl. Next: A sample using the DynObj library.
A Sample: Plugin + Application
Here we will create a couple of plugin libraries and use them from a simple main application. It
wil demonstrate how to instantiate plugin objects, how to use plugin objects as ordinary C++
classes, how to query for supported types.
Creating an Interface File
We start out with defining a simple interface file that manages data about a person (PersonI.h):
Collapse
#include // We use strcmp below
class DynStr;
// %%DYNOBJ class(DynI) .
Collapse
protected:
// Need not really be protected since user of PersonI cannot look here
anyway.
char m_name[NAME_MAX_LENGTH];
int m_age;
};
// %%DYNOBJ library
The last comment tells the preprocessor that we want library code inserted at this location. In this
library section it will put the factory functions and any glue needed to instantiate plugin objects
to the host.
Next we run the parser on this source file (the -p option tells pdoh where it can find template
code):
Collapse
$ ./pdoh PersonImpl1.cpp -o -p../
Found library insertion point
$
The parser has now inserted code that generates glue for library functions. The glue code can be
included/excluded using #define DO_MODULE:
Collapse
// %%DYNOBJ section library
...
// Only include below when compiling as a separate library
#ifdef DO_MODULE
...
// The object creation function for this module
extern "C" SYM_EXPORT DynObj* CreateDynObj(
const char *type, int type_id,
const DynI *pdi_init, int object_size )
{
...
if( ((!strcmp(type,"PersonImpl1") || type_id==PERSONIMPL1_TYPE_ID))
||
((!strcmp(type,"PersonI") && type_id==PERSONI_TYPE_ID)) ){
return new PersonImpl1(pdi_init);
}
DO_LOG_ERR1( DOERR_UNKNOWN_TYPE, ... );
return 0;
}
After compiling, we can connect this as a plugin to a host application, the preprocessor has
generated the bits and pieces that are required, both for the host and the plugin side.
A Main Application
Finally we create the main application (main1.cpp) that uses the plugin:
Collapse
#include
#include
// DynObj support
#include "DynObj/DynObj.h"
#include "DynObj/DynObjLib.h"
#include "DynObj/DoBase.hpp"
// Interfaces we're using
#include "PersonI.h"
#include "ProfessionI.h"
int main( ) {
// Check that things have started OK
if( !doVerifyInit() ){
printf("Failed DoVerifyInit\n");
exit(-1);
}
Now we want to start using the plugin. For this, we use DynObjLib which wraps a cross-
platform run-time (DLL/SO) library loader (initial code for this came from Boby Thomas).
It is worth noting that the main application and the library are loosely linked, making it easy to
implement on any platform that supports explicit run-time loading of binary libraries.
Collapse
// Load library
DynObjLib lpimpl1( "pimpl1", true );
if( lpimpl1.GetState()!=DOLIB_INIT ){
printf( "Could not load library (pimpl1): status (%d)\n",
lpimpl1.GetState() );
exit( -1 );
}
// Create object
PersonI *pp = (PersonI*)lpimpl1.Create("PersonI",PERSONI_TYPE_ID);
if( pp ) {
pp->SetName( "George" );
pp->SetAge( 34 );
...
We have instantiated the object the 'raw' way here, giving type name and ID to DynObjLib. After
that, it is just to start using the object as any standard C++ object.
We next query the object for an interface using do_cast:
Collapse
ProfessionI *pi = do_cast(pp);
if( pi )
;// Use the interface
pp->doDestroy();
return 0;
}
The template do_cast takes care of the details of checking if ProfessionI is supported. It
transforms the C++ type to type name and ID. Using type information from the plugin, it can
walk the class layout and return an adjusted interface pointer.
It is important that any address offsets applied inside objects are always based on information
from the plugin compiler. When using an interface pointer returned in this way, we can only
assume it is valid for the duration of the current function. We do not need to release it in any
way. Finally, we delete the object using DynObj::doDestroy (which will recycle it in the
memory manager of the plugin that created it).
Further documentation here.
The DynObj Library
DynObj is an open-source cross-platform library that uses the run-time plugin approach just
decribed. Although the mechanisms used are generic and fairly simple, the library fills many
gaps and make it straight-forward to use plugins inside a C++ application.
The library provides:
A small class hierarchy (VObj, DynI, DynObj, DynSharedI) establishing some common
ground between a host and a plugin. The DynObj library also works with classes that are not
rooted in this hierachy.
A type description facility that allows types to be defined and shared by both host and plugins
(DynObjType).
A way to convert C++ types to a plugin library (doTypeInfo, DO_DECL_TYPE_INFO).
Cast functions to query an object about the types it implements. This is similar to
dynamic_cast in C++ or QueryInterface in COM (do_cast, doGetObj,...).
Instantiating C++ objects from plugins (do_new).
A plugin library loading/unloading mechanism(DynObjLib).
C-level functions to handle objects.
Other practical C++ classes and templates for objects.
In addition to these library facilities, it includes a tool (pdoh) that parses C++ header files and
generates source code for type registration.
VObj, DynI, DynObj and Friends
All classes discussed below are defined in DynObj.h. The library is based on some properties of
objects with VTables:
The VPTR is always stored first in a binary object
VTables are shared by all instances of a class, but not with instances of any other class (so it
provides a type identifier).
In C++ there is not a built-in way to denote these classes. However, we define the class VObj to
represent an object with a VTable with unknown size and methods. VObj in that sense becomes
the 'stipulated' root class for all classes that contain one or more virtual functions.
The VObj Class
Base: (no base class)
Methods: (no methods)
We see that VObj does not introduce any methods (it cannot since that would interfere with
derived classes which use the first VTable slot).
However, VObj has a number of convenient inline functions to query for types (VObj::IsA,
VObj::CanBeA, VObj::GetObj,...), asking about errors (VObj::Get/Set/ClearError) and
more.
To determine if a class is a VObj or not, these templates can be used:
Collapse
bool has_vtable = IsVObj::v;
template
VObj* to_vobj( T* pt );
This provides type safety so that we cannot try to convert say a char* to an interface pointer (the
compiler would give an error).
The DynI Class
Base: VObj
Returns Methods: Arguments
DynObjType* doGetType
void* doGetObj const char* type_name
const char* doGetError int *perr_code
void doClearError
The DynI class provides a way to know its type (doGetType) and for asking about other types it
supports (doGetObj). To ask if a DynI supports the DynStr interface:
Collapse
DynI *pdi = /* Wherever pointer comes from */;
DynStr *pds = pdi->doGetObj("DynStr");
This is equivalent to:
Collapse
DynStr *pds = do_cast(pdi);
The DynI class has an advantage over VObj:
It knows its own derived type
In contrast, to find the type of a VObj, a lookup into a global table, using the VPTR, has to be
made (and works only after the types have been registered).
Since DynI is used across DLL (and possibly compiler) boundaries, we cannot use C++
exceptions. To provide error handling, the methods doGetError and doClearError are
introduced. They allow for an object specific error state, without burdening the class with
member variables for this. SetError is not a member, since object errors are usually are not set
from 'outside'.
We see also that the DynI interface has no support for creation or destrcution. The same applies
to VObj. The lifespan that can be assumed is that of the current method.
If a reference to the object is to be kept, these are different ways to go about it:
Ask for a DynSharedI interface (ref counted ownership)
Create a weak reference (if object supports NotifierI))
The object may be a known global or singleton which explicitely allows for references to be
stored
The DynObj Class
Base: DynI
Returns Methods: Arguments
void doDestroy
void doDtor
The DynObj interface represents an object that can be created and destroyed. It represents an
object owned from a single point. Usually the functions doDestroy and doDtor would be
implemented like:
Collapse
void MyClass::doDestroy(){ ::delete this; }
void MyClass::doDtor(){ this->~MyClass(); }
An object is destroyed through doDestroy. doDtor provides access to the destructor of the class
(library internal use).
Objects can be created in some different ways:
Using do_new
Using DynObjLib::Create(...)
Temporary objects can be created and released using DynObjHolder
The DynSharedI Class
Base: DynObj
Returns Methods: Arguments
int doAddRef
int doRelease
The DynSharedI interface represents an object with shared ownership. doAddRef and doRelease
increases and decreases the ownership counter.
DynSharedI derives from DynObj since it depends on a way of destroying itself
(DynObj::doDestroy) when the lifetime counter reaches 0.
To protect the object from being deleted before its actual end-of-life, a doDestroy method can
check that the counter is actually zero:
Collapse
virtual void docall doDestroy( ) {
if( !m_ref_cnt )
::delete this;
else
SetError(DOERR_DESTROY_ON_NON_ZERO_REF,
"DynSharedI - Destroy on non-zero ref");
}
Documenation on building the DynObj library.
Building and Using the Library
Directory layout of the library:
doc
o doxygen - docs generated from doxygen
src
o DynObj - main source of DynObj library
samples - samples of using library here
tools - source for the pdoh tool
msdev - Visual C++ project for DynObj and samples here
o pi - sources for platform independence layer
o utils - utility like C++ classes
A description of the pdoh tool is available here.
Build Model
Plugins are usually opened while the application is running, so there is no build-time link
between the main application and the plugins. When a plugin module is loaded, by default, the
linker is instructed not to backlink into the application (UNIX). On Windows, backlinking is not
possible.
An application can expose functionality to plugins through interfaces. DoRunTimeI provides a
way to make named instances of objects known globally.
Compiler Defines
A number of compilers define controls how libraries and main applications are built. The defines
are described in detail in src/DynObj/DoSetup.h (and under DynObj defines in doxygen doc).
When compiling a library DO_MODULE should be defined. Also, the name of the library should be
stored in DO_LIB_NAME (i.e. #define DO_LIB_NAME "DynStr").
The main application should define DO_MAIN.
The default settings in DoSetup.h are OK when compiling the samples. In general the defines
are used like this (example DO_USE_DYNSHARED):
#define DO_USE_DYNSHARED - The option is not used
#define DO_USE_DYNSHARED 0 - The option is not used
#define DO_USE_DYNSHARED 1 - The option is activated
This allows having sensible defaults and for overriding them reliably from outside in a build
environment.
Building the DynObj library
There are two build methods provided with the library:
Cross-platform GNU makefile - This works for the g++ compiler on Unices and Windows
(mingw).
Visual C++ - Solution and project files for for Visual C++ on Windows.
Building a Plugin (Module)
The compiler define DO_MODULE should be set.
A plugin module requires compiling with:
DynObj.cpp
vt_test.cpp
Building a Main Application (Using Plugins/Modules)
The compiler-defined DO_MAIN should be set. The main application links against one of two
static libraries:
dolibdort - Enables using DynObj:s and DoRunTime
doliblt - A minimalistic library without support for DoRunTime
The libraries are projects in the Visual C++ solution file. To build the libraries using the
makefile:
Collapse
$ cd src/DynObj
$ make dolibdort
$ make doliblt
The DynStr Library
A run-time plugin class for strings is provided: DynStr. This enables plugins to use a C++ string
class in a safe way, internally and in function calls. The DynStr library is built with:
Collapse
$ cd src/DynObj
$ make ds
Building the Samples
The samples defines a PersonI and ProfessionI interfaces. Then three slightly different
implementations are provided in three plugins: pimpl1, pimpl2, pimpl3. Three different main
applications are provided as well: main1, main2, main3. There are sub-projects for each of
them in the VC++ solution file.
From the command prompt:
Collapse
$ cd samples
$ make pimpl1
$ make pimpl2
$ make pimpl3
$ make bmain1
$ make bmain2
$ make bmain3
The pdoh Tool
This tool takes a C++ header or source file and outputs a modified version of the file, provided it
finds // %%DYNOBJ tags in it. It basically scans for class and struct declarations and collects
inheritance information.
The pdoh tool can generate these sections:
A general section (in a header file). This part is used by both the plugin and the main
application. It contains class declarations, type IDs and the bridge from C++ types to type IDs.
An implement section (by default in a header file). This part is used only by the plugin. To keep
things simple, the code is generated inside the header file (that keeps things in one place) and
the plugin must trigger inclusion of this section with a #define DO_IMPLEMENT_NAMEOFLIB.
A library section. This goes into a source file and makes up the part that the user of the plugin
communicates with directly. The most important function is the one that receives a type ID/
type string and instantiates this object to the caller.
By default pdoh sends its output to stdout. Use option -o to overwrite the input file, or -
oNewFile.h to write to another file. To generate less comments in the output -b can be used.
pdoh can be integrated into a build environment, it is a simple file scanner so it is fast. If it does
not find any //%%DYNOBJ tags it will not generate any output.
If the tags have not been modified since the previous run (on the same file), it will also not
generate any output (so it does not affect file time stamps when there is no need for it).
Part 3: Send data to/from C++ and C#
Windows processes [3]
Introduction
This demo demonstrates how to send / receive WM_COPYDATA messages between a collection of
C++ and C# programs.
Why use WM_COPYDATA?
Traditionally WM_COPYDATA was used to send a limited amount of data between processes on the
same machine. It is still desirable to interoperate this way even with the rise of .NET. .NET
Remoting is out of the scope of this demo. This demo is intended for people like myself that are
dealing with legacy environments.
The demo consists of four programs and one DLL. Two programs are written in C++ (MS
Version 5.0) and two are written in C# (using .NET 2003). A C++ exe and C# exe will send the
structure shown below to a C++ and C# receiving program.
Collapse
struct sTagData
{//this is the structure that will be sent (copied) by the WM_COPYDATA
private:
char szTag[ciMaxTag];
char szData[ciMaxData];
}
The DLL was written in VC 5.0 and it wraps the WM_COPYDATA message.
Additionally, the DLL provides a uniform way of :-
a. Protecting the receiving application from third party processes that may be unwise
enough to HWND_BROADCAST the WM_COPYMESSAGE.
b. Allows incoming messages to be limited to specific senders either by handle or sender id.
c. Optional use a crude bitwise encryption to discourage hackers from spying on messages.
Unzip using the full paths to your C drive. The result should be the shown directory structure.
C:\WmCpyDta
C:\WMCpyDat\Build - the folder where all build output is placed for all projects.
C:\WMCpyDat\From_C - a C++ program that sends the structure
C:\WMCpyDat\TO_C - a C++ program that receives the structure
C:\WMCpyDat\From_C_Sharp – a C# program that sends the structure
C:\WMCpyDat\\TO_C_Sharp - a C# program that receives the structure.
To use the demo, open folder build and click on all 4 executables. Click on the send buttons in
the FROM apps and the message will appear in the TO apps. Both FROM programs are
document to show how to use the DLL’s interface. Likewise, both TO programs are documented
in their WindowProc()’s to show how to receive the data.
Platforms supported
These programs have only been tested on Windows XP.
Other facts
Philip McGahan is the sole creator of this demo. No part of it has been plagiarized from other
sources. Any party may use all or part of this demo with out credit or reference to Philip
McGahan.
Lastly, it is only fair to tell my reader that I am very new at C#. I did try to use C# to build the
DLL. But beyond never getting the data to come across with out junk at the end of the string. I
was not comfortable with having to setup unsafe blocks. The whole thing just seemed clumsy.
When or if I get better at C#, I will revisit this issue.
Reference
[1] http://www.abstraction.net/ViewArticle.aspx?articleID=67
[2] http://www.codeproject.com/KB/library/dynobj.aspx
[3] http://www.codeproject.com/KB/cs/wm_copydata_use.aspx