Introduction
Sometimes it is useful to put code that is meant to be shared and reused in a
separate module, a dynamic link library (dll). This may save time and effort in
the development of new code as some of the code is already implemented and tested.
Implementing a dll that contains global functions is fairly simple, but unfortunately,
implementing a class inside a dll involves slightly more consideration.
A Class In Dll Example
We will implement a test dll that contains a class. Then we will see how this class
can be instantiated, accessed and deleted from an exe program. Note that this is only
tested on MS Windows XP.
The dll will consist of an interface, a class implementation and a factory function.
First we will show the structure of the interface.
The Interface Listing
The listing below, IMyInterface.h, is useful to easily enable the exe to access the class member
functions using the instantiated class object. Utilising the virtual keyword, we can
automatically get a hold of the member function address from its vtable and thereby safely
call the member function.
Note that the __stdcall keyword is necessary to get a calling
signature that matches that of the exe. As this is supposed to be an interface, the member
functions should be made pure virtual. This interface file should be included with the exe.
#pragma once
class IMyInterface
{
public:
IMyInterface(void){}
virtual ~IMyInterface(void){}
virtual bool __stdcall SetNumber(int aNumber) = 0;
virtual bool __stdcall AddNumber(int aNumber) = 0;
virtual bool __stdcall GetNumber(int& aNumber) const = 0;
};
The Class Implementation File
Here, in MyClass.h, we have put both the class declaration and the definition in the same file,
for simplicity. In a real implementation, the class definition should obviously be located in a .cpp file.
We inherit from the simple test interface and also add one member variable to store the value in. The definition
is equally simple but will have enough code to show how the dll works. Note that a member function does not need
to return a boolean.
#include "IMyInterface.h"
class MyClass : public IMyInterface
{
public:
MyClass();
~MyClass();
public:
bool __stdcall SetNumber(int aNumber);
bool __stdcall AddNumber(int aNumber);
bool __stdcall GetNumber(int& aNumber) const;
private:
int iNumber;
};
MyClass::MyClass()
: iNumber(0)
{
}
MyClass::~MyClass()
{
}
bool __stdcall MyClass::SetNumber(int aNumber)
{
iNumber = aNumber;
return true;
}
bool __stdcall MyClass::AddNumber(int aNumber)
{
iNumber += aNumber;
return true;
}
bool __stdcall MyClass::GetNumber(int& aOutNumber) const
{
aOutNumber = iNumber;
return true;
}
The Dll File
Below, in DllTest.cpp, is the dll factory function. It is a simple and convenient way to instantiate an
object of our test class, as we cannot (easily) call new on the class in the exe since we don't have
the address to the constructor. Using a factory function is an established way of handling
the object creation.
The extern "C" __declspec(dllexport) before the function is
necessary to expose the function so that the exe can access it using the GetProcAddress, as we will show next.
#include "IMyInterface.h"
#include "MyClass.h"
extern "C"
__declspec(dllexport) IMyInterface* DllGetObject()
{
IMyInterface* pObj = static_cast<IMyInterface*>(new MyClass);
return pObj;
}
The Exe Source Code
Looking at DllRunTest.cpp, we declare the pointer to the class factory function using typedef.
In the main function, we first load the dll into the address space of the exe, with LoadLibrary.
Then we need to get a pointer to the factory function by calling GetProcAddress with the correct function name.
Using the function pointer, we are now ready to actually instantiate the class and assigning it to the interface
pointer. Using the interface (base class) is not mandatory, but convenient.
Thanks to making the class member functions virtual, they are now available for being unit tested.
If everything works as expected, the "successful" message will be printed on the screen.
When finished with the instantiated class and the dll, make sure to tidy up the resources.
#include <windows.h>
#include <tchar.h>
#include <iostream>
#include "IMyInterface.h"
typedef IMyInterface* (__stdcall *DLLGETOBJECT)(void);
int _tmain(int argc, _TCHAR* argv[])
{
HINSTANCE hInstDll = LoadLibraryA("DllTest");
if (hInstDll == NULL)
return 0;
DLLGETOBJECT pDllGetObject =
(DLLGETOBJECT) GetProcAddress(hInstDll, "DllGetObject");
IMyInterface* pMyObject = (pDllGetObject)();
if (pMyObject == NULL)
return 0;
int resultNumber = 0;
bool result = pMyObject->SetNumber(12);
result = result && pMyObject->AddNumber(27);
result = result && pMyObject->GetNumber(resultNumber);
if (result && resultNumber == 39)
{
std::cout << "Successfully tested dll class members.";
}
else
{
std::cout << "Dll test failed!";
}
delete pMyObject;
FreeLibrary(hInstDll);
return 0;
}
|