Monday, 8 August 2011

Writing a DLL containing C++ classes

Putting functions into a DLL is a good thing. It helps you to reuse stuff, save space on updates, save build time etc. To write a DLL the proper way you have to keep some things in mind though. Those hints are basically the "best-practice" from this page.

A DLL is a library, a collection of data and/or code. To use a DLL, on Windows (on Unixes this is a bit different) you need a .h file(s) declaring the functions and classes contained in the DLL (if any),  a .lib file containing stubs for the functions in the DLL, possibly a .def file containing the exported functions and the actual .dll file containing the code and data.

When writing a DLL you will want to export some functions from the DLL you want make available to the outside. Those you need to declare with __declspec(dllexport):
class __declspec(dllexport) CC {
    void foo();
};
Now the problem with that is that the compiler does name mangling - it gives the functions/classes a unique name - but how it does this depends on the compiler and its version. A DLL compiled by one compiler can thus not be used with a different compiler. UNLESS you define the functions as extern "C" and their call type as APIENTRY. The problem with that again is, that those are C functions. APIENTRY is also usually __stdcall, meaning those can't be (non-static) class members - Not what you want unless you have "all static" classes.
The workaround is using a virtual base class for your class and providing an object creation/destruction function:
class CCInterface { //notice: no __declspec(dllexport) here
    virtual void foo() = 0;
    virtual void Release() = 0;
};

class CC { //notice: no __declspec(dllexport) here either
    void foo() {//do something};
    void Release() {delete this;};
};

extern "C" CCInterface * APIENTRY createCC() {
    return new CC;
}
This is also more or less the way COM works, which is in turn why all compilers support this. The downside is that you can not put the creation function into the interface and you need to explicitly call the release function. You could get around the Release call by using an auto_ptr or use the AutoClosePtr from the original document.

When you create a class that is a singleton you can get around having to call Release() by destroying the object when the DLL is detached from the last process:
static int attached = 0;
static CC * instance = NULL;

extern "C" CCInterface * APIENTRY getCCInstance() {
    if (NULL == instance) {
        instance = new CC;
    }
    return instance;
};

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
    //Perform actions based on the reason for calling.
    switch(fdwReason) {
        case DLL_PROCESS_ATTACH:
        case DLL_THREAD_ATTACH:
            attached++;
            break;
        case DLL_PROCESS_DETACH:
        case DLL_THREAD_DETACH:
            //Delete instance when the last process/thread detaches.
            if (0 >= --attached && NULL != instance) {
                delete instance;
                instance = NULL;
            }
            break;
    }
    return TRUE; //Successful DLL_PROCESS_ATTACH.
}
You could also use an auto_ptr for that purpose.

No comments:

Post a Comment