Introduction
Since I spent the last few days in searching how to export a class from DLL and found a lot of stuff on DLLs but merely anything that helped me, I decided to summarize the useful information in this brief tutorial.
Background
To most of us involved in OO programming, it's rather natural to expect to be able to export whole objects from a DLL. The good news is it's doable. The bad news: it's not that obvious. And, to make things even worse: try to export inherited classes or functions that return class pointers.
Solution
It's not the only way to solve this, and I don't know whether it's the best solution. It's rather an approach that provides flexibility and robustness.
The problems with exporting classes from a DLL are the mangled names of functions due to the C++ style. With functions, it's quite easy - just define them as extern "C"
, and you've got it. Since extern "C"
has no effect on classes, you have to come up with something else. One way is to derive your class from a DLL wrapper class with pure virtual member functions for every function you want to export with the class. The thing your class really needs to inherit is the virtual table of the functions of the wrapper class. The wrapper class may look like an interface class, and there are cases when you can use it as one, but you must think of it rather as exporting a wrapper that is meant for only one real class. This is quite obvious in a simple inheritance scheme where you have a real base class and a real derived class.
When you want to have the same structure in a DLL and export Derived1
or Derived2
, you must reform your scheme to:
In C++, that would look like:
class DllWrapperBase
{
public:
DllWrapperBase();
virtual ~DllWrapperBase();
virtual long __stdcall test1()=0;
virtual float __stdcall test2()=0;
};
class Base : public DllWrapperBase
{
public:
Base();
virtual ~Base();
virtual long __stdcall test1(){return 1;}
virtual float __stdcall test2(){return 1.8f;};
};
class DllWrapperDerived : public Base
{
public:
DllWrapperDerived();
virtual ~DllWrapperDerived();
virtual long __stdcall test3()=0;
virtual float __stdcall test4()=0;
};
class Derived : public DllWrapperDerived
{
public:
Derived();
virtual ~Derived();
virtual long __stdcall test3(){return 3;}
virtual float __stdcall test4(){return 3.9f;}
};
To make it more complicated, we introduce a Factory class that produces objects of the derived class. It's a common design pattern, and would look as this:
enum eProducts
{
PRODUCT1
};
class Base;
class Derived;
class DllWrapperFactory
{
public:
DllWrapperFactory();
virtual ~DllWrapperFactory();
virtual Base * __stdcall CreateProduct(eProducts iValue)=0;
protected:
virtual Derived * __stdcall CreateDerived()=0;
};
class Factory : public DllWrapperFactory
{
public:
Factory();
virtual ~Factory();
Base * __stdcall CreateProduct(eProducts iValue);
protected:
Derived * __stdcall CreateDerived();
};
Then, we add the main DLL files:
#include "Factory.h"
extern "C" __declspec(dllexport) DllWrapperFactory * returnFactory()
{
DllWrapperFactory * pObj =
static_cast<dllwrapperfactory*>(&FactorySingleton::Instance());
return pObj;
}
#include "stdafx.h"
#include "dll_export.h"
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
You use the newly created DLL like this:
#include "stdafx.h"
#include <stdlib.h>
#include <windows.h>
#include "Factory.h"
#include "Derived.h"
typedef Factory * (__stdcall *DLLGETFACTORY)(void);
int main(int argc, char* argv[])
{
printf("Hello World!\n");
HINSTANCE hInstDll = LoadLibrary("dll_test.dll");
if(!hInstDll) printf("Failed to load dll\n");
DLLGETFACTORY pDllGetFactory =
(DLLGETFACTORY) GetProcAddress(hInstDll, "returnFactory");
Factory * pMyFactory = (pDllGetFactory)();
if (pMyFactory == NULL)
return 0;
Derived * d = (Derived *)pMyFactory->CreateProduct(PRODUCT1);
int i = d->test1();
float f = d->test2();
int j = d->test3();
float e = d->test4();
system("PAUSE");
return 0;
}
You have to keep in mind that in order to use any class through a DLL, you have to include its header in the application.