A friend asked me how to write and use a DLL in C++. I told him that I had to check and make sure my understanding was accurate. So here I am. Would this work as a DLL:

myDLL.cpp:

#include "myDLL.h"
void DLL_EXPORT ConstructMyInt(myIntStruct &i){i.val=0;}
void DLL_EXPORT DoSomething(myIntStruct &i){i.val*=i.mul++;}
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch (fdwReason)
    {
        case DLL_PROCESS_ATTACH:
            // attach to process
            // return FALSE to fail DLL load
            break;

        case DLL_PROCESS_DETACH:
            // detach from process
            break;

        case DLL_THREAD_ATTACH:
            // attach to thread
            break;

        case DLL_THREAD_DETACH:
            // detach from thread
            break;
    }
    return TRUE; // succesful
}

myDLL.h:

#include <windows.h>
#ifdef BUILD_DLL
    #define DLL_EXPORT __declspec(dllexport)
#else
    #define DLL_EXPORT __declspec(dllimport)
#endif
#ifdef __cplusplus
extern "C"
{
#endif
struct myIntStruct
{
int val;
int mul;
};
void DLL_EXPORT ConstructMyInt(myIntStruct &);
void DLL_EXPORT DoSomething(myIntStruct &);
#ifdef __cplusplus
}
#include "myInt.h"
#endif

myInt.h:

class myInt
{
private:
myIntStruct variables;
public:
myInt(){ConstructMyInt(variables);}
int do(){DoSomething(variables);return variables.val;}
};

myTestProgram.cpp:

#include "myDLL.h"
#include <iostream>
int main()
{
    myInt i;
    std::cout<<i.do()<<i.do()<<endl;
    return 0;
}

A couple of points:

1. In the dll implementation, preferrably #define BUILD_DLL prior to including the header file.

2. Writing DllMain is optional; if there is nothing special you need to do you can omit it, and the library gives a default implementation.

3. Unless your design intent is to make the DLL functionality usable by C clients, extern "C" is completely unnecessary; it limits C++ functionality. C++ features like classes, virtual functions, overloaded function names and operators, exceptions etc can be used seamlessly across DLLs. (Templates also can be used, but with some care - linking to Dlls is done at load/run time, templates provide pure compile time mechanisms).

For an example of using C++ across DLL boundaries, see: http://www.daniweb.com/software-development/cpp/threads/390349/1683141#post1683141

Edited 4 Years Ago by vijayan121: n/a

1) If you are not going to put anything in DllMain than you might as well remove it.

2) It seems you have made your DLL compatible with C, or at least, tried. If you really want compatibility with C, you cannot use references. If you will always compile the DLL with a C++ compiler, then it is OK to use references. However, if you want to make the DLL compilable with a C compiler, you need to use a pointer instead. Then, for using the DLL, the header file will have to be changed if you want it to be usable by C code, as so for example:

#ifndef DONT_FORGET_YOUR_HEADER_GUARDS_H
#define DONT_FORGET_YOUR_HEADER_GUARDS_H

#include <windows.h>
#ifdef BUILD_DLL
    #define DLL_EXPORT __declspec(dllexport)
#else
    #define DLL_EXPORT __declspec(dllimport)
#endif

struct myIntStruct
{
int val;
int mul;
};

#ifdef __cplusplus
extern "C"
{
void DLL_EXPORT ConstructMyInt(myIntStruct &);
void DLL_EXPORT DoSomething(myIntStruct &);
}
#include "myInt.h"
#else 
// This makes this DLL usable from C code.
void DLL_EXPORT ConstructMyInt(myIntStruct *);
void DLL_EXPORT DoSomething(myIntStruct *);
#endif

#endif

3) Generally, for wider compatibility it is often good to use the stdcall convention which you can lump into the DLLEXPORT symbol as so:

#ifdef BUILD_DLL
    #define DLL_EXPORT __declspec(dllexport) __stdcall
#else
    #define DLL_EXPORT __declspec(dllimport) __stdcall
#endif

4) Of course, if you don't actually want compatibility with any other language besides C++, then forget about points 2-3, except that you should still use extern "C" if you plan to make the DLL usable by a wider set of compilers and compiler options beside just your own, but you also sacrifice some features of C++.

5) Binary compatibility is also an issue with DLLs, and the way you solved this problem is very nice and pedantic. This is a very safe way to do things: create a plain-old-data type (i.e. C-style struct) to contain all the important data of your class, then define your DLL functions in terms of forwarding pointers to such POD structures, and wrap make your classes to wrap those structs and operate on them. This has the effect of strongly enforcing binary compatibility. Generally, however, such precaution is not necessary, simply using opaque pointers to C++ class objects is sufficient (you can also tread on more dangerous territory and pass C++ objects by value (like smart-pointers) but you have to be careful to follow some strict guidelines).

Taking your suggestions in hand, would this be 'perfect'?

myDLL.cpp:

#include "myDLL.h"
void DLL_EXPORT ConstructMyInt(myIntStruct *i){i->val=0;}
void DLL_EXPORT DoSomething(myIntStruct *i){i->val*=i->mul++;}

myDLL.h:

#ifndef MY_BAD_FORGETTING_HEADERS_H
#define MY_BAD_FORGETTING_HEADERS_H 1.0//I like a version here
#include <windows.h>
#ifdef BUILD_DLL
    #define DLL_EXPORT __declspec(dllexport) __stdcall
#else
    #define DLL_EXPORT __declspec(dllimport) __stdcall
#endif
#ifdef __cplusplus
extern "C"
{
#endif
struct myIntStruct
{
int val;
int mul;
};
void DLL_EXPORT ConstructMyInt(myIntStruct *);
void DLL_EXPORT DoSomething(myIntStruct *);
#ifdef __cplusplus
}
#include "myInt.h"
#endif
#endif

myInt.h:

#ifndef MY_BAD_FORGETTING_HEADERS_H
#ifndef MY_INT_H
#define MY_INT_H 1.0//version 1.0
#if MY_INT_H==MY_BAD_FORGETTING_HEADERS_H
class myInt
{
private:
myIntStruct variables;
public:
myInt(){ConstructMyInt(&variables);}
int do(){DoSomething(&variables);return variables.val;}
};
#endif
#endif
#endif

myTestProgram.cpp:

#include "myDLL.h"
#include <iostream>
int main()
{
    myInt i;
    std::cout<<i.do()<<i.do()<<endl;
    return 0;
}
This article has been dead for over six months. Start a new discussion instead.