Permission to read, use and copy
this documentation and accompanying software is hereby granted without fee,
provided that the above copyright notice appears in all copies and the contents
remains unmodified. Any recommended modification should be sent directly to the
author’s email address: tdemjen@yahoo.com.
The author makes no representations about the
suitability of the accompanying software for any purpose. It is provided
"as is" without expressed or implied warranty.
How to Export C++ Classes from DLL Plugins
Introduction
Those who have tried to write plugins for the Win32 platform in C++ know that the operating system does not directly support object-oriented programming with DLLs. Although it is relatively easy to dynamically load a DLL and call one of its exported functions, this concept does not work with C++ member functions. Fortunately there is an effective and virtually painless solution to dynamically exporting C++ classes from a DLL.
In this article we are going to discuss the basics of DLLs, including how to export C functions, and how to load them dynamically from your application in a safe way. You will then learn how to export C++ classes using interfaces. Finally, we are going to cover some very important memory management and security issues. By the end, you will have a very good understanding of plugins, and will be able to employ them routinely in your everyday development.
The Basics
If you want to make a function defined in a DLL available from the outside world, you have to export it. To export a function, you have to use the following declaration:
extern "C" __declspec(dllexport) void __stdcall Function(/* arguments */);
If you are using Borland C++Builder, this declaration is enough. For Microsoft Visual C++ you usually have to list the names of the exported functions in the EXPORTS section of the project’s .DEF file. The “extern "C"” declaration tells the compiler that the function should be treated as a plain old C function, which means its name is not mangled.
As an example, let’s write a very simple function that calculates the square of an integer number:
extern "C" __declspec(dllexport) int __stdcall Square(int value);
int __stdcall Square(int value)
{
return value * value;
}
After building the DLL, you are ready to test it. Create a new console project, and write the following test code:
typedef int __stdcall (*PtrSquare)(int);
HINSTANCE h = LoadLibrary("square.dll");
if(h)
{
if(Square)
{
cout << Square(8);
}
}
FreeLibrary(h);
This is how you typically do it in C. As a C++ programmer, however, you can do a couple of improvements that increase the readability and the safety of the code. I usually wrap the DLL into a manager class that automatically takes care of the FreeLibrary call:
class SquarePlugin
{
typedef int __stdcall (*PtrSquare)(int value);
public:
SquarePlugin();
~SquarePlugin();
void Open(const char* filename);
bool IsValid() const { return h != 0; }
int Square(int value) const;
private:
HINSTANCE h;
PtrSquare square;
};
SquarePlugin:: SquarePlugin()
: h(0), square(0)
{
}
SquarePlugin::~SquarePlugin()
{
if(h != 0)
FreeLibrary(h);
}
void SquarePlugin::Open(const char* filename)
{
if(h == 0)
h = LoadLibrary(filename);
}
int SquarePlugin::Square(int value) const
{
if(square == 0)
square = reinterpret_cast<PtrSquare>(GetProcAddress(h, "Square"));
assert(square);
if(square == 0) return 0;
return square(value);
}
This class seems to be an overkill in this case, but this concept is very useful when the DLL is more complex. Compared to the standard C code, we have made the following modifications:
- The DLL handler is encapsulated in a class, where the destructor guarantees that the library gets freed.
- GetProcAddress is called on demand, the first time when it is needed.
- An assertion is used to ensure that the function pointer is initialized. This will point out the exact problem right away in case something is wrong with the DLL. This is usually enough, but just in case we have also added a real “if”. This may or may not be necessary.
- I prefer reinterpret_cast to regular C-style casts, because it communicates the programmer’s intention more clearly and is searchable.
Exporting Classes
You can not directly export a C++ class from a dynamically loadable DLL. You can, however, export a function that creates an instance of a class, this way you can eliminate the need of exporting the class itself. This is a workaround that has some disadvantages. In particular, you have to type a little bit more. Also, every object must be instantiated on the heap using “new”, and every single function must be virtual.
Here is how it works. First, you create an interface class, which is nothing more than a skeleton, without any functionality whatsoever:
class Interface
{
public:
virtual ~Interface();
virtual void Function1(int) = 0;
virtual int Function2() const = 0;
};
It is practical to declare all functions pure virtual, and use no data members. The interface class is only used as a communication foundation between the DLL and the application. Note that this class must be included from both the DLL and the application, so if you had any code other than the destructor, it would have to be duplicated.
In the DLL, you simply inherit from the interface and implement all the virtual functions:
class Implementation : public Interface
{
public:
virtual void Function1(int)
{
// implementation
}
virtual int Function2() const
{
// implementation
return 0;
}
};
Now all you have to do is export a C-style function that creates an instance of this class:
extern "C" __declspec(dllexport) Interface* __stdcall CreateInstance();
Interface* __stdcall CreateInstance()
{
return new Implementation;
}
If Implementation’s construction had any arguments, you would pass them via the CreateInstance function.
There is one final issue to consider before we move on to the application side. Usually DLLs have their own memory managers, independent from that of the application. Therefore any memory allocated in the DLL must be freed in the DLL. In other words, you have to ensure that the DLL has an infrastructure for deleting the objects allocated by CreateInstance, otherwise you would have a memory leak.
There are three ways to ensure this:
- Export another function from the DLL called DeleteInstance.
- Introduce one more virtual member function called DeleteThis.
- Use allocators.
The first two solutions are very simple. Here is how to export a DeleteInstance function from the DLL:
extern "C" __declspec(dllexport) void __stdcall DeleteInstance(Interface* ptr);
void __stdcall CreateInstance(Interface* ptr)
{
delete ptr;
}
This disadvantage of this solution is that there may be more than one implementation, each in its own DLL. In this case it is up to the application to call the correct DeleteInstance for each pointer. This is way too much of a burden, an excellent source of nasty bugs, very hard to find bugs. Moreover, often you don’t even know which exact DLL has the implementation. From a polymorphic Interface pointer it is impossible to tell which DeleteInstance should be called.
Therefore it is better to integrate the delete functionality into the class itself. To do that, just add another virtual function to the interface:
class Interface
{
[...]
virtual void DeleteThis() const = 0;
};
The DLL must implement this function using delete:
void Implementation::DeleteThis() const
{
delete this;
}
It is often said that the expression “delete this” is politically incorrect and very dangerous, but I believe in this situation it is acceptable, especially because every other solution is even worse. Note that the function is declared const, because according to the C++ standard, it is perfectly legal to call delete on a const pointer. If your compiler can not handle that, you need to remove the const keyword.
The third solution is to use an allocator, which we are going to discuss a little bit later. It is slightly more complex than what we have done before.
Now let’s take a look at the application side of the plugin. It is fairly simple to use the exported interface class. All you have to do is import the CreateInstance function the usual way, and use that as a replacement for the constructor:
typedef Interface* __stdcall (*PtrCreateInstance)();
HINSTANCE h = LoadLibrary("plugin.dll");
if(h)
{
PtrCreateInstance CreateInstance =
reinterpret_cast<PtrCreateInstance>(GetProcAddress(h, " CreateInstance "));
if(CreateInstance)
{
Interface* object = CreateInstance();
object->Function1(object->Function2());
object->DeleteThis();
}
}
FreeLibrary(h);
Here I assume that you have used the DeleteThis type of implementation to resolve the memory management issues. Once again, to be on the safe side, you will want to wrap this all into an easy-to-use class, which also improves the readability of your code:
class Handle
{
typedef Interface* __stdcall (*PtrCreateInstance)();
public:
Handle ();
~ Handle ();
void Function1(int value);
int Function2() const;
private:
static HINSTANCE h;
static PtrCreateInstance create_instance;
Interface* pimpl;
};
HINSTANCE Handle::h = 0;
PtrCreateInstance Handle::create_instance = 0;
Handle:: Handle ()
{
if(h == 0)
{
h = LoadLibrary("plugin.dll");
if(h == 0)
throw MyException();
}
if(create_instance == 0)
{
create_instance =
reinterpret_cast< PtrCreateInstance>(GetProcAddress(h, "CreateInstance"));
if(create_instance == 0)
throw MyException();
}
pimpl = create_instance();
}
Handle::~ Handle ()
{
if(pimpl)
pimpl->DeleteThis();
}
void Handle::Function1(int value)
{
pimpl->Function1(value);
}
int Handle::Function2() const
{
return pimpl->Function2();
}
This class is called a handle, and it implements the so-called “pimpl” idiom, which means it encapsulates a pointer to its own implementation. The idea behind this is that Handle hides its actual implementation. If you want to eliminate the plugin, you can #include the Implementation directly and call new to create an instance internally. If you want to write a sophisticated plugin manager that automatically finds the DLL, you can do that too. The user of the Handle class does not care how the functionality is implemented. Think of Handle as a shell that hides the actual implementation layer and makes the DLL import completely transparent and painless for the user. You can now use this class as if there was no DLL involved at all:
Handle object;
object.Function1(object.Function2());
It is as simple as that... as long as you don’t have to return a vector or a string from the DLL.
Advanced Portability Issues
So far you have learned the basics of writing plugins in C++. Unfortunately, things are a whole lot more complicated in the real word, especially if you want to make your plugins portable.
So far we have assumed that both the DLL and the application are compiled with the same calling convention. This is not necessarily true. For example, in Borland C++Builder the default calling convention is __stdcall, but in Microsoft Visual C++ the default is __cdecl. Therefore it is almost always a good idea to explicitly specify the calling convention for each member function in the Interface, like this:
class Interface
{
public:
virtual __stdcall ~Interface();
virtual void __stdcall Function1(int) = 0;
virtual int __stdcall Function2() const = 0;
};
Please follow this little advice, and you will save a lot of
annoying debugging in the future. Also declare the same functions __stdcall in the Implementation, although the other functions
you may use there need not have an explicit calling convention specifier. Note that you could also use the __cdecl convention, it does not matter; the only important
thing is that you should explicitly specify a convention. Also note that __stdcall is compatible with
If you need to pass a string or a container to the DLL, you should use plain C-style types, such as const char* for string, and const T* for an array of type T:
virtual void __stdcall DoIt(const char* name, const int* items, int items_size);
Theoretically you could pass string and vector to a DLL, like this:
virtual void __stdcall DoIt(const std::string& name, const std::vector<int>& items);
The problem with this is that STL types are not strictly standard.
Excuse me? The Standard C++ library is not fully standard? That’s right. The standard only mandates the interface of the classes, it has nothing to say about the internal implementation. For example, a string can be implemented as a pointer and a length, but it might also be implemented as a pointer to the beginning and another pointer to the end. As a matter of fact, it may even be reference counted (using lazy evaluation).
In most cases, you have no way of knowing which STL implementation will be used to create the plugins. The very reason plugins are used is to provide the ability for other programmers to enhance your application. If you choose to use C++ classes in a plugin interface, you are already restricting the scope of enhancements, because such plugins can only be implemented in C++. But that is often not a problem. However, if you use STL classes in you interface, you are introducing further dependencies. Very strong dependencies, for that matter. Including STL in an interface means that you bind your architecture to a particular version of an STL shipped by a particular vendor. This type of strong bind is almost always unwanted.
You could, however, pass a const pointer or a const reference to your own classes, as long as those classes are compiler-independent. Also, your Handle class can freely use STL classes, because it is not a part of the plugin’s interface declaration. You just have to translate std::string to const char*, std::vector<T> to const T*, and so on, before calling into the DLL.
DLL Memory Management
Passing constant arguments to the DLL is easy. Problems begin when the DLL has to return information, especially dynamic information, whose size is completely unknown before the DLL function call. The simplest example of this is a plugin that returns a string or vector of any length.
The caller has no way of allocating memory for the result, because it does not know the size of the information to be returned.
The DLL could allocate the memory for the result, but it is dangerous and error-prone, because it must be ensured that the DLL is deallocating the allocated memory.
The solution is to use allocators. An allocator is an infrastructure that is able to allocate memory using a specific memory manager. An allocator can easily be passed from the application to the DLL, and thus the DLL can allocate memory using the application’s memory manager, instead of using its own.
It might sound very complex, but it is in fact very simple. An allocator is nothing else but a pointer to a global C function that allocates memory. Think of it as a callback function:
typedef void* (*PtrAlloc)(int bytes);
The implementation is surprisingly simple:
void* StandardAlloc(int bytes)
{
return new char[bytes];
}
You could also use the C malloc function instead of the C++ new[].
The problem is that this way you can only allocate raw memory, but you often need to allocate objects. Fortunately, in order to pass strings and vectors from the DLL to the application all you need to do is allocate raw memory and fill it with contents. So first we are going to discuss this first, and at the end of this section I will also show how to generalize the allocators to allocate any type, including C++ classes.
Before we deal with strings, vectors, and other high-level objects, we have to build a class around raw memory buffers. This is necessary for exception safety, to reduce the possibility of bugs, and to enhance the readability of the code. Also, it is much better to attack the problem little by little than to solve it at once. In fact, the raw memory buffer class could be used for other high-level types as well.
The memory buffer class is going to be called DllScopedBuffer, indicating that its main purpose is to pass raw data from a DLL. It is “scoped”, because it automatically deallocates itself when it goes out of scope, just like boost::scoped_array, or other smart pointers, or even a vector. The difference between a vector<char> and DllScopedBuffer is that the latter supports allocators. Well, vector does as well, but that is a half-baked solution, is very complicated, and is not supported by every compiler. Furthermore, we don’t want to use allocator templates, because being resolved at compile time, templates are useless in plugins. We need a dynamic solution that can be passed runtime from the application to the DLL.
Place take a look at files DllScopedBuffer.h and .cpp.
DllScopedBuffer has an internal type definition called PMemAllocFunc, which is an allocator function. As we discussed earlier, it is a global C-style allocator function, only that it is able to free memory as well. This dual allocate-and-free functionality is necessary, because DllScopedBuffer will allocate and free memory automatically. The type is declared like this:
typedef
void* (*PMemAllocFunc)(void* P, unsigned Bytes);
Here is how the allocator works:
- If P is 0 and Bytes is non-0, the function must allocate memory and return a pointer to it. In case of an error it must throw std::bad_alloc, as opposed to returning 0.
- If P is non-0, the function must delete the memory addressed by P; Bytes is not used in this case, it is set to 0. The function should return 0 and must not throw.
- If both P and Bytes are 0, the function must return 0 and do nothing else. It is like calling delete [] on a NULL pointer.
It is very easy to implement such an allocator function:
static void* AllocFunc(void* p, unsigned bytes)
{
if(p) { delete [] reinterpret_cast<unsigned char*>(p); return 0; }
else if(bytes) return new unsigned char[bytes];
else return 0;
}
Note how we use delete [], and not a simple delete, to free the memory. To do that, we first have to cast it to the same type used with new[]. The new[] automatically throws std::bad_alloc in case there is not enough memory.
The implementation for DllScopedBuffer is very simple. The constructor initializes the standard allocator and sets the pointer to 0. The destructor calls the allocator to delete the pointer. There is another constructor that allocates memory. There is a get function that gives read access to the underlying pointer. Use IsValid to test if the pointer is non-zero. The Swap function is used to swap two pointers. Finally, there is a Free function, just in case you want to free the memory earlier than the object goes out of scope.
As you see, the implementation is almost trivial, and is very minimalistic. You can not copy a DllScopedBuffer variable, because the class does not even know what it stores inside. It could never implement a reasonable copying mechanism. In addition, it would be bad practice, as this class is just a shell, as its name suggests it. After all, you don’t make a copy of a C-string by assigning char*’s, you call strcpy. Similarly, you don’t copy a DllScopedBuffer as a raw pointer, and the compiler will enforce this very strictly.
Using these classes it is very easy to implement two simple classes called DllString and DllVector. Both classes are consistent with the STL implementation, so they have .c_str(), .begin(), .end(), and so on. However, my classes provide a very minimal implementation only, because I don’t want you to use them as a replacement for STL. Remember, DllString and DllVector are only designed for marshalling data from a DLL to the application, or between two DLLs.
Now here is how to use these types to implement a plugin that performs an OCR (optical character recognition) of an image file:
struct TDllOcrWord
{
DllString word;
RECT bounds;
TDllOcrWord(const char* _word, const RECT& _bounds)
: word(_word), bounds(_bounds) { }
};
typedef DllVector<TDllOcrWord> TDllOcrWordList;
bool Ocr(const char* filename, const char* language, TDllOcrWordList& words);
TDllOcrWord is a simple structure that encapsulates a word and a bounding rectangle with the coordinates. The OCR itself is performed by the function called Ocr, which is exported from the plugin. It has three arguments:
- filename: input; the name of the file to be OCRed.
- language: input; the name of the language, such as "English".
- words: output; a vector of words returned by the OCR engine.
The function returns true on success and false on error.
The function attempts to open the file specified by “filename” and OCR it using “language”. It returns a vector of word and bounding rectangle pairs.
The caller has no idea of how many words the OCR engine will return, and it can not be pre-calculated. Using DllVector and DllString, we were able to hide the complex memory management issues going on behind the scene. You never have to worry about the memory, because the memory is guaranteed to be freed by the same party that allocated it.
It does not mean, however, that you should not be careful anymore. First you might think that the main applicator’s allocator is passed to the Ocr function, and therefore it will always use the application to allocate memory. Wrong! That is absolutely not true! Let’s take a look at how the plugin is implemented:
extern "C" __declspec(dllexport) bool APIENTRY Ocr(const char* filename,
const char* language, TDllOcrWordList& words);
bool APIENTRY Ocr(const char* filename,
const char* language, TDllOcrWordList& words)
{
OcrVendorInit();
if(! OcrVendorLoad(filename)) return false;
if(! OcrVendorRecognize(language)) return false;
if(OcrVendorFirstWord())
{
bool isnext;
do
{
std::string word;
RECT rect;
isnext = OcrVendorGetWord(word, rect);
words.Add(TDllOcrWord(word.c_str(), rect));
}
while(isnext);
}
OcrVendorClose();
return true;
}
The OcrVendor[...] functions are vendor specific. Here we just show an imaginary implementation that first initializes the engine, loads the image, performs the recognition, and fills the “words” vector before it closes itself.
The key line to watch is this:
words.Add(TDllOcrWord(word.c_str(), rect));
Here the TDllOcrWord type is created locally, inside the DLL, which means the DllString uses the DLL’s memory allocator to create the string, not the main application’s allocator!
So what does it mean to you? You still don’t have to worry about who is freeing the memory, but you do have to worry about not closing the DLL while the OCR’s result is alive. Here is how you would typically call the Ocr function from your application (omitting LoadLibrary and GetProcAddress):
TDllOcrWordList words;
bool result = Ocr("scan001.tif", "English", words);
You might be tempted that you close the DLL right away, as it may seem that you don’t need it anymore:
[...]LoadLibrary[...]
[...]GetProcAddres[...]
TDllOcrWordList words;
bool result = Ocr("scan001.tif", "English", words);
[...]FreeLibrary[...]
for(int i = 0; i < words.size(); ++i)
std::string str = words[i].word; // crash boom bang!!!
Such a code would result in an immediate access violation. Why is that? That is because the string was allocated in the DLL, and therefore it must be freed using the DLL’s memory manager. However, you have closed the DLL before you tried to accessed the string. By the time you iterate through the words, the words themselves do not exist anymore.
Oh, of course, what a stupid mistake. That’s easy. So let’s do it this way:
[...]LoadLibrary[...]
[...]GetProcAddres[...]
TDllOcrWordList words;
bool result = Ocr("scan001.tif", "English", words);
for(int i = 0; i < words.size(); ++i)
std::string str = words[i].word;
[...]FreeLibrary[...]
[...]// crash boom bang!!!
You will be surprised that this is going to crash just as badly.
Wait a minute! You are properly freeing the DLL, the TDllOcrWordList automatically takes care of the words, and you never ever use the words variable after the call to FreeLibrary. How could this code possibly fail?
The key is in the order. You call FreeLibrary before “words” goes out of scope, which means “words” is still alive when FreeLibrary is called. The proper solution is to make sure that “words” does not exist anymore before you call FreeLibrary. A more readable alternative solution is to call Clear to manually delete all the words in the vector:
[...]LoadLibrary[...]
[...]GetProcAddres[...]
TDllOcrWordList words;
bool result = Ocr("scan001.tif", "English", words);
for(int i = 0; i < words.size(); ++i)
std::string str = words[i].word;
words.Clear();
[...]FreeLibrary[...]
To be 100% sure, always do this:
[...]LoadLibrary[...]
[...]GetProcAddres[...]
{
// setting up my own types
// calling the DLL function
// post processing
}
[...]FreeLibrary[...]
Or even better, put the LoadLibrary / FreeLibrary calls inside a guard class, like you have seen earlier:
ScopedLoadLibrary dll;
TDllOcrWordList words;
Ocr([...]);
C++ always guarantees that objects go out in the reverse order as they are allocated, so words goes out of scope before dll. All you have to remember is that the dll instance must be created first, and not like this:
TDllOcrWordList words; // THIS IS WRONG, CRASHES!!
ScopedLoadLibrary dll;
Ocr([...]);
Advanced Allocators
TODO