Exporting a class from a DLL for multiple instantiations

Implementing a class in a DLL allows for exchanging a component of a system without affecting the remainder of it. Exporting a class from a DLL using an abstract interface has been variously documented, but multiple instantiations using that setup have not. Widely referenced is Alex Blekhman's article at the Code project (http://www.codeproject.com/Articles/28969/HowTo-Export-C-classes-from-a-DLL). While this works well for single instantiations, it does not work at all for various compilers, including MinGW's C++ compiler, when multiple instances are needed.

The solutions is to add __declspec(dllimport)/__declspec(dllexport) qualifiers to the abstract and real classes, as well as the factory functions for creating and deleting instances. The code below shows a bare minimal example of how this looks like, in a format designed for maximal portability between compilers at the source code level. Thus, the ground rules for exporting a class from a DLL are:

  1. Use an abstract interface class.
  2. Use factory functions to create and delete instances of the real class.
  3. Decorate the definitions of the interface class and the real class with __declspec(dllimport/dllexport).
  4. Decorate the prototypes of the factory functions with __declspec(dllimport/dllexport).
  5. The calling program only needs to see the interface class directly.

The code below has been tested in May 2012 with MinGW 4.4.0, C++Builder 5p1, C++Builder 2007, Visual C++ 6 and Visual C++ 2008. The only true compiler dependence is in the module definition file, which covers for the name mangling affecting the factory functions; the code below shows the version for MinGW 4.4.0. One peculiarity in the code below is the placement of the decoration for the creator factory functions before the type specification rather than after. This is meant to accommodate porting to Visual C++ 2008. The other compilers take the order either way. Sample commands for compiling and linking using the MinGW C++ compiler follow below the code.

The sample code is stripped to the bones. The first three files are used to build the DLL named libtestdll.dll, and the first and last (fourth) to build the test program. There are no provisions for testing success of any calls, nor for the operating system used.

A big thank you is due to Pierre Martin, who identified the missing pieces in the factory function approach. I also appreciate the help of Mark Christison in performing the tests with various compilers.

testdll.h DLL include file

#ifdef MY_COMPILE_DLL
#define MY_DECLSPEC __declspec(dllexport)
#else
#define MY_DECLSPEC __declspec(dllimport)
#endif

class MY_DECLSPEC TestInterface
{
public:
   TestInterface() ;
   virtual ~TestInterface() = 0 ;
   virtual bool Get(bool &flag, int &num) = 0 ;
} ;

typedef TestInterface* (*newtestdlltype)(const bool, const int) ;
typedef void (*deletetestdlltype)(TestInterface *) ;

TestInterface* MY_DECLSPEC newtestdll(const bool, const int) ;
void MY_DECLSPEC deletetestdll(TestInterface*) ;

testdll.def DLL module definition

EXPORTS
   newtestdll = _Z10newtestdllbi
   deletetestdll = _Z13deletetestdllP13TestInterface

testdll.cpp DLL class source

#define MY_COMPILE_DLL
#include "testdll.h"

TestInterface::TestInterface()
{
}

TestInterface::~TestInterface()
{
}

class MY_DECLSPEC Test : public TestInterface
{
public:
   Test(const bool, const int) ;
   virtual ~Test();
   virtual bool Get(bool &flag, int &num);
private:
   bool f_flag ;
   int f_num ;
   char f_text80[80] ;
};

Test::Test(const bool flag, const int num)
      : TestInterface(), f_flag(flag), f_num(num)
{
}

Test::~Test()
{
}

bool Test::Get(bool &flag, int &num)
{
   flag = f_flag ;
   num = f_num ;
   return true ;
}

TestInterface* newtestdll(const bool flag, const int num)
{
   return new Test(flag, num);
}

void deletetestdll(TestInterface *f)
{
   delete f;
}

testcall.cpp Main Program

#include <windows.h>
#include "testdll.h"

int main(void)
{
   bool bb ;
   int i, ii, numinstances=4 ;
   HMODULE testdll = LoadLibrary("libtestdll.dll") ;
   newtestdlltype ffunc_newtestdll =
            (newtestdlltype)GetProcAddress(testdll, "newtestdll") ;
   deletetestdlltype ffunc_deletetestdll =
            (deletetestdlltype)GetProcAddress(testdll, "deletetestdll") ;

   TestInterface **ftest = NULL ;
   ftest = new TestInterface*[numinstances] ;
   for (i=0; i<numinstances; i++) {
      ftest[i] = ffunc_newtestdll(i%2==0, i) ;
      ftest[i]->Get(bb, ii) ;
   }

   for (i=0; i<numinstances; i++) {
      ffunc_deletetestdll(ftest[i]) ;
      ftest[i] = NULL ;
   }
   delete [] ftest ;
   FreeLibrary(testdll) ;
   return 1 ;
}

Compile and link commands

Compile and link commands, suitable for use in a console window, for MinGW residing in C:\MinGW with C:\MinGW\bin being part of the standard PATH setting:

g++ -O0 -g3 -Wall -c -fmessage-length=0 -o testdll.o testdll.cpp
g++ -shared -g -o libtestdll.dll testdll.o testdll.def
g++ -O0 -g3 -Wall -c -fmessage-length=0 -o testcall.o testcall.cpp
g++ -g -otestcall.exe testcall.o

Site Status

Site maintenance performed successfully, site online Dec 13th 12:00 AM Eastern.