Exporting a C++ class from a DLL
Asked Answered
T

6

29

Most of my C/C++ development involves monolithic module files and absolutely no classes whatsoever, so usually when I need to make a DLL with accessible functions I just export them using the standard __declspec(dllexport) directive. Then access them either dynamically via LoadLibrary() or at compile time with a header and lib file.

How do you do this when you want to export an entire class (and all it's public methods and properties)?

Is it possible to dynamically load that class at runtime and if so, how?

How would you do it with a header and lib for compile time linking?

Taproot answered 26/8, 2008 at 13:18 Comment(1)
A very good answer written later: https://mcmap.net/q/174000/-how-do-i-safely-pass-objects-especially-stl-objects-to-and-from-a-dllCommonweal
A
17

What about late-binding? As in loading it with LoadLibrary() and GetProcAddress() ? I'm used being able to load the library at run time and it would be great if you could do that here.

So there are two ways to load the DLL. The first is to reference one or more symbols from the DLL (your classname, for example), supply an appropriate import .LIB and let the linker figure everything out.

The second is to explicitly load the DLL via LoadLibrary.

Either approach works fine for C-level function exports. You can either let the linker handle it or call GetProcAddress as you noted.

But when it comes to exported classes, typically only the first approach is used, i.e., implicitly link to the DLL. In this case the DLL is loaded at application start time, and the application fails to load if the DLL can't be found.

If you want to link to a class defined in a DLL, and you want that DLL to be loaded dynamically, sometime after program initiation, you have two options:

  1. Create objects of the class using a special factory function, which internally will have to use (a tiny bit of) assembler to "hook up" newly created objects to their appropriate offsets. This has to be done at run-time AFTER the DLL has been loaded, obviously. A good explanation of this approach can be found here.

  2. Use a delay-load DLL.

All things considered... probably better to just go with implicit linking, in which case you definitely want to use the preprocessor technique shown above. In fact, if you create a new DLL in Visual Studio and choose the "export symbols" option these macros will be created for you.

Good luck...

Aman answered 26/8, 2008 at 15:7 Comment(1)
I don't believe you can use delayload with imported classes if the classes have virtual functions, because the vtables of those classes will be imported as data symbols. Importing data symbols preculdes using delayload.Fachanan
V
17

When you build the DLL and the module that will use the DLL, have some kind of #define that you can use to distinguish between one and the other, then you can do something like this in your class header file:

#if defined( BUILD_DLL )
    #define IMPORT_EXPORT __declspec(dllexport)
#else
    #define IMPORT_EXPORT __declspec(dllimport)
#endif
class IMPORT_EXPORT MyClass {
    ...
};

Edit: crashmstr beat me to it!

Viola answered 26/8, 2008 at 13:26 Comment(6)
how to deal with mangled class and constructors (etc.) names sir.?Huppah
That's the thing - you don't need to deal with mangled names. The __declspec(dllimport) tells the compiler / linker that you're importing it and it does the right thing.Viola
Neither this or crashmstr's response have anything to do with the original question. Also null's question hasn't been properly addressed.Folklore
@Folklore What are you talking about? If you include the header file that defines the class with __declspec(dllimport), and then link the exe with the import library (resulting from building the DLL), the resulting executable with automatically load the DLL at runtime and import the class, including all methods on it. Mangled names and overloaded constructors are not an issue because they're all handled by the linker and loader. It may not be as complete an answer as James Devlin's, but there's nothing wrong with it and it does address the original question.Viola
In fact, if you read Mr. Devlin's answer, he says "probably better to just go with implicit linking, in which case you definitely want to use the preprocessor technique shown above" (emphasis mine).Viola
Sorry - I had presumed that Adam might have been dealing with compiler incompatibility issues. You can not use this technique if you were trying to export classes between MSVC and MinGW, for instance, due to differing name mangling schemes.Folklore
A
17

What about late-binding? As in loading it with LoadLibrary() and GetProcAddress() ? I'm used being able to load the library at run time and it would be great if you could do that here.

So there are two ways to load the DLL. The first is to reference one or more symbols from the DLL (your classname, for example), supply an appropriate import .LIB and let the linker figure everything out.

The second is to explicitly load the DLL via LoadLibrary.

Either approach works fine for C-level function exports. You can either let the linker handle it or call GetProcAddress as you noted.

But when it comes to exported classes, typically only the first approach is used, i.e., implicitly link to the DLL. In this case the DLL is loaded at application start time, and the application fails to load if the DLL can't be found.

If you want to link to a class defined in a DLL, and you want that DLL to be loaded dynamically, sometime after program initiation, you have two options:

  1. Create objects of the class using a special factory function, which internally will have to use (a tiny bit of) assembler to "hook up" newly created objects to their appropriate offsets. This has to be done at run-time AFTER the DLL has been loaded, obviously. A good explanation of this approach can be found here.

  2. Use a delay-load DLL.

All things considered... probably better to just go with implicit linking, in which case you definitely want to use the preprocessor technique shown above. In fact, if you create a new DLL in Visual Studio and choose the "export symbols" option these macros will be created for you.

Good luck...

Aman answered 26/8, 2008 at 15:7 Comment(1)
I don't believe you can use delayload with imported classes if the classes have virtual functions, because the vtables of those classes will be imported as data symbols. Importing data symbols preculdes using delayload.Fachanan
H
13

Adding a simple working example for exporting a C++ class from a DLL :

The given below example gives you only a short overview of how dll and exe can interact each other (self explanatory ) but it needs more things to add for changing into a production code.

Full sample example is divided in to two part

A. Creating a .dll library (MyDLL.dll)

B. Creating an Application which uses .dll library (Application).

A. .dll project file (MyDLL.dll):

1. dllHeader.h

#ifdef  MYDLL_EXPORTS 
#define DLLCALL __declspec(dllexport)   /* Should be enabled before compiling 
                                           .dll project for creating .dll*/
#else
#define DLLCALL __declspec(dllimport)  /* Should be enabled in Application side
                                          for using already created .dll*/
#endif

// Interface Class
class ImyMath {
public:
    virtual ~ImyMath() {;}
    virtual int Add(int a, int b) = 0;
    virtual int Subtract(int a, int b) = 0;
};

// Concrete Class
class MyMath: public ImyMath {
public:
    MyMath() {}
    int Add(int a, int b);
    int Subtract(int a, int b);
    int a,b;
};

//  Factory function that will return the new object instance. (Only function
//  should be declared with DLLCALL)
extern "C" /*Important for avoiding Name decoration*/
{
    DLLCALL ImyMath* _cdecl CreateMathObject();
};

// Function Pointer Declaration of CreateMathObject() [Entry Point Function]
typedef ImyMath* (*CREATE_MATH) ();

2. dllSrc.cpp

#include "dllHeader.h"

// Create Object
DLLCALL ImyMath* _cdecl CreateMathObject() {
    return new MyMath();
}

int MyMath::Add(int a, int b) {
    return a+b;
}

int MyMath::Subtract(int a, int b) {
    return a-b;
}

B. Application Project which load and link the already created .dll file:

 #include <iostream>
#include <windows.h>
#include "dllHeader.h"

int main()
{
    HINSTANCE hDLL = LoadLibrary(L"MyDLL.dll"); // L".\Debug\MyDLL.dll"

    if (hDLL == NULL) {
        std::cout << "Failed to load library.\n";
    }
    else {
        CREATE_MATH pEntryFunction = (CREATE_MATH)GetProcAddress(hDLL,"CreateMathObject");
        ImyMath* pMath = pEntryFunction();
        if (pMath) {
            std::cout << "10+10=" << pMath->Add(10, 10) << std::endl;
            std::cout << "50-10=" << pMath->Subtract(50, 10) << std::endl;
        }
        FreeLibrary(hDLL);
    }
    std::cin.get();
    return 0;
}
Heuser answered 24/6, 2014 at 10:48 Comment(2)
Thanks. Without the Interface Class approach in this answer, I was having to put everything in the .h file to prevent linker errors.Nullity
why need a factory function? If without factory function, how should I do?Intelligentsia
E
12

I use some macros to mark the code for import or export

#ifdef ISDLL
#define DLL __declspec(dllexport)
#endif

#ifdef USEDLL
#define DLL __declspec(dllimport)
#endif

Then declare the class in a header file:

class DLL MyClassToExport { ... }

Then #define ISDLL in the libary, and USEDLL before including the header file in the place you want to use the class.

I don't know if you might need to do anything differently for working with LoadLibrary

Erdman answered 26/8, 2008 at 13:26 Comment(0)
D
7

Recently I asked myself exactly the same question, and summarized my findings in a blog post. You may find it useful.

It covers exporting C++ classes from a DLL, as well as loading them dynamically with LoadLibrary, and discusses some of the issues around that, such as memory management, name mangling and calling conventions.

Dubious answered 17/9, 2011 at 3:22 Comment(0)
J
0

If you're willing to put a vtable in the class you're exporting, you can export a function that returns an interface and implement the class in the .dll, then put that in the .def file. You might have to do some declaration trickery, but it shouldn't be too hard.

Just like COM. :)

Jocularity answered 27/8, 2008 at 13:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.