Explicitly Linking to Classes in DLL’s

Sometimes Explicit Linking to DLL’s is advantageous over implicit linking.
For example, if at runtime the DLL is not found the application can display an error message and still continue.
Explicit linking is also useful if you want users to provide a plugin for your application, in which
case you could explicitly load the dll and call some predefined set of functions in it.

Explicit linking to global(non-member) C/C++ is quite
easy. For example, suppose you wan’t to call to a function ExportedFn in a dll. You can
simply export the function like this (or through the def file):-


extern “C” _declspec(dllexport)
void ExportedFn(int Param1, char* param2);

The extern “C” linkage
specification is required because otherwise C++ compiler generates a decorated
name for the function and the function would not be exported with the name
“ExportedFn” instead it would be exported something like
“??ExportedFn@QAEX” . If this function resides in a DLL called DLL1.dll a client exe can
call this function simply like this :-


HMODULE hMod = LoadLibrary(“Dll1.dll”);
typedef void (*PExportedFn)(int, char*);

PExportedFn pfnEF = (PExportedFn)GetProcAdress(“ExportedFn”);

pfnEF(1, “SomeString”);

But what if you want to export a bunch of member
functions of a C++ class and link to them explicitly. There are two problems.
The first problem is that C++ member function names are decorated names
(Specifying extern “C” does not help). The second problem is that C++ language
specifications do not allow pointer to member functions to be converted into
other types (later on I will present a simple way to do that). These two
problems do restrict exporting of C++ classes in DLL’s. In this article I will
show some ways by which we could overcome these restrictions and explicitly load
classes from dlls.

I am going to
present two methods in this article and I will cover another method (delegation) in
an other article.

The three methods which I am going to cover in this article are :-


  1. Using virtual functions table or vtable. This is the
    way COM operates.
  2. Using direct calls through GetProcAddress.

I will be taking the following example class for the purpose if this
article.



class A
{
private:
int m_nNum;
public:
A();
A(int n);
virtual ~A();
void SetNum(int n);
int GetNum();
};

I have provided two different implementations of the
class in two different DLLs. A client exe allows the user to enter the name
of the dll from which the class should be loaded and accordingly output the
results. You can download the sample project from this link The sample contains
one workspace known as ExpClass.dsw, which has three projects one each for the two
dlls and one for the client exe. The code demonstrates both the methods.


Exporting Class Using VTable

This method is the basis of COM. When
we declare member function(s) of a class as virtual, compiler creates a
table of all the virtual functions in the order in which they appear in
the declaration. When an object of that class is created the first four bytes of
the object point to that table. If we change the declaration of the class A to
be :-


class A
{
private:
int m_nNum;
public:
A();
A(int n);
virtual ~A();
virtual void SetNum(int n);
virtual int GetNum();
};

Now a table is generated by the compiler which has the
three virtual functions – the destructor, SetNum and GetNum. (You can
actually observe that a virtual function table is created if you list the
assembly with the source code. You can change the project options for
that).

Now the object needs to be created in the dll. Since we are going to link
only explicitly we need some global exported functions that create an object of
the class through operator new. Since there are two constructors we can create
two functions : CreateObjectofA() and CreateObjectofA1(int)
and export them. Finally the exe can use the object as


typedef A* (*PFNCreateA1)();

PFNCreateA1 pfnCreateA1 =
(PFNCreateA1)GetProcAddress(hMod, TEXT(“CreateObjectofA1”));

A* a = (pfnCreateA1)();
a->SetNum(1);
_tprintf(TEXT(“Value of m_nNum in a is %dn”),a->GetNum());

delete a;

The important thing to note is that
CreateObjectofA creates the the class using operator new this allows
the client exe to safely call opeartor delete.


extern “C” __declspec(dllexport) A* CreateObjectofA1()
{
return new A();
}

This method is very useful if you want users to make plugins for your applications.
The drawback of this method is that the memory for the class must always be allocated
in the dll. If the client wants to create an object by allocating memory
in a different way it can’t do so.

The next method involves obtaining the functions directly through
GetProcAddress and calling the functions. The trick is to convert
the FARPROC returned by GetProcAddress into C++ pointer to member
functions. Fortuantely through C++ facility of templates and union this could be
don very easily. All that needs to be done is to define a function like this


template
Dest force_cast(Src src)
{
union
{
Dest d;
Src s;
} convertor;

convertor.s = Src;
return convertor.d;
}


The above function lets us cast variables of any different types and is
more powerful then reinterpret_cast. For example, if we define a pointer type


typedef void (A::*PSetNum)(int);

We can convert a pointer fp which is of type FARPROC to PSetNum simple by using.


FARPROC fp;
.
.

PSetNum psn = force_cast<PSetNum>(fp);

The above operation is not possible through reinterpret_cast or C-Style cast.

Having found a way to convert FARPROC to a pointer to member, let’s see
ways to export C++ class member functions through friendly names. This can be done
through .def files.

The first step is to find the decorated names of each of the functions
that need to be exported, this can be done through either through map file
or by generating assembly listing. Then the functions can be exported by
friendly names through the following .def file syntax :-


EXPORTS
ConstructorOfA1 = ??0A@@QAE@XZ PRIVATE
ConstructorOfA2 = ??0A@@QAE@H@Z PRIVATE
SetNumOfA = ?SetNum@A@@UAEXH@Z PRIVATE
GetNumOfA = ?GetNum@A@@UAEHXZ PRIVATE
DestructorOfA = ??1A@@UAE@XZ PRIVATE

Now the functions are exported through much more friendly names. Now here
is the way by which these are called


typedef void (A::*PfnConstructorOfA1)();
typedef void (A::*PfnConstructorOfA2)(int);
typedef void (A::*PfnDestructorOfA)();
typedef void (A::*PfnSetNumOfA)(int);
typedef int (A::*PfnGetNumOfA)();

A* a1 = (A*)_alloca(sizeof(A));

PfnConstructorOfA1 pfnConsA =
force_cast<PfnConstructorOfA1>(GetProcAddress(hMod,
TEXT(“ConstructorOfA1”)));

(a1->*pfnConsA)();

PfnSetNumOfA pfnSetNumA =
force_cast<PfnSetNumOfA>(GetProcAddress(hMod,
TEXT(“SetNumOfA”)));

(a1->*pfnSetNumA)(1);

PfnGetNumOfA pfnGetNumA =
force_cast<PfnGetNumOfA>(GetProcAddress(hMod,
TEXT(“GetNumOfA”)));

_tprintf(TEXT(“Value of m_nNum in a is %dn”),(a1->*pfnGetNumA)());

PfnDestructorOfA pfnDestA =
force_cast<PfnDestructorOfA>(GetProcAddress(hMod,
TEXT(“DestructorOfA”)));

(a1->*pfnDestA)();

An interesting things to note here is that constructors and
destructors are both being called explicitly. This is perhaps only way by which
class constructors could be invoked explicitly. The other point to observe is
that the object has been allocated memory over the stack through function alloca
(if want to allocate memory on heap you need to use malloc), this is because
allocating memory through new or by just decalaring an object of type A the
constructor of A is automatically called. We don’t want’t this because the constructor
of A is implemented in the Dll and we have not implicitly linked to the
dll. You need not explicitly call the constructor and destructor instead you could
implement the constructor in you exe file as :-


A::A()
{
static PfnConstructorOfA1 pfnConsA1 =
force_cast<PfnConstructorOfA1>
(GetProcAddress(ClassLoader<A>::s_hMod,
TEXT(“ConstructorOfA1”)));

(this->*pfnConsA1)();
}

A::~A()
{
static PfnDestructorOfA pfnDestA =
force_cast<PfnDestructorOfA>
(GetProcAddress(ClassLoader<A>::s_hMod,
TEXT(“ConstructorOfA1”)));

(this->*pfnDestA)();
}

The above two implementations just delegate to the actual
function in the dll at the same time allow you two declare and use an object of
A in normal fashion. I will cover a better form of delegation in the next
article. An important point to mention here is that you need not implement the
functions SetNum, GetNum
etc. if you only call them through
pointers. The sample attached explains all the methods.

Downloads

Download demo project – 8.8 Kb

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read