1

I would like to understand the DLL mechanism and what the compiler does when I loads the DLL at run-time (i.e. I will not use the generated .lib).

Consider the following C++ code:

DLL interface header file

#ifdef MYDLL_EXPORTS
#define MYDLL_API __declspec(dllexport)
#else
#define MYDLL_API __declspec(dllimport)
#endif

class MYDLL_API Base
{
public:
  Base();

  virtual ~Base();

  virtual int get_number() const;
  virtual const char* what() const = 0;

private:
  int i_;
};

class MYDLL_API Child : public Base
{
public:
  Child();

  virtual ~Child();

  virtual int get_number() const override;
  virtual const char* what() const override;
private:
  int j_;
};

extern "C" {
  MYDLL_API Base* __cdecl initializeObject();
}

DLL implementation source file

#include "MyDLL.hh"

Base::Base()
  : i_(42)
{}

Base::~Base()
{}

int Base::get_number() const
{
  return i_;
}

Child::Child()
  : Base()
  , j_(24)
{}

Child::~Child()
{}

int Child::get_number() const
{
  return j_;
}

const char* Child::what() const
{
  return "Hello!";
}

Base* initializeObject()
{
  return new Child();
}

The goal of this DLL is to have a common interface defined by the Base class, but it allows specifics implementations compiled in different DLLs that are loaded at runtime (here the Child class is exposed for the purpose of the example).

At this stage, if I naively include the DLL's header:

#include "MyDLL.hh"

int main()
{
  Base* b = new Child();

  std::cout << b->get_number() << std::endl;
  std::cout << b->what() << std::endl;

  delete b;

  getchar();
  return 0;
}

The linker complains LNK2019 and LNK2001 errors: it can not resolves symbols. So, it behaves as expected (I did not use the .lib).

Consider now, the following code that I use to load the DLL at runtime:

#include "MyDLL.hh"

typedef Base* (*initFuncType)();

int main()
{
  HINSTANCE handle = LoadLibrary(L"MyDLL.dll");
  initFuncType init = nullptr;
  init = (initFuncType)(GetProcAddress(handle, "initializeObject"));
  if (init)
  {
    Base* b = init(); //< Use init() !

    std::cout << b->get_number() << std::endl;
    std::cout << b->what() << std::endl;

    delete b;
  }
  getchar();
  FreeLibrary(handle);
  return 0;
}

This time it works, the linkage is done.

  • 1st question: What happened? What changed for the compiler and the linker? The use of the function pointer on initializeObject() solves the problem.

The other issue I do not understand well is when I remove virtual and override of get_number():

int get_number() const;

I have a LNK2019 error because of the unresolved Base::get_number(void) const symbol in the _main function. I understand that the virtual keyword will resolve the member function dynamically (at run-time). In our case, the DLL is not loaded yet, the get_number symbol is not available.

  • 2nd question: Does this means that methods must always be virtual using DLL run-time linking?

  • 3rd question: How can I have the C++ function exportation with the Windows API? So that I could remove the extern "C" { ... } stuff.

Thanks for your reading! I hope I will read interesting answers! :)

  • the compiler does nothing special/different. The dynamic linker of your OS tries to find the appropriate symbol using the symbol table in the DLL. – The Paramagnetic Croissant Dec 15 '14 at 22:59
  • You are re-inventing COM, possibly imperfectly. As long as the method is virtual, it is called indirectly through the object's v-table. When you make it non-virtual then you have to use GetProcAddress() again. – Hans Passant Dec 15 '14 at 23:05
  • @TheParamagneticCroissant I assume this is done by `LoadLibrary` and `GetProcAddress`, but when I use `init()` I do not have linker errors on the class member functions. Could you explain why? – Pierre Pagnoux Dec 15 '14 at 23:06
  • @PierrePagnoux why would you expect a linker error? – The Paramagnetic Croissant Dec 15 '14 at 23:12
  • @HansPassant I try to not rely too much on Windows technologies. The v-table (that is created during allocation, I forgot that) does effectively the work. Is it a good way of using it? So that, I do not have to bind each methods by myself? – Pierre Pagnoux Dec 15 '14 at 23:15
  • @TheParamagneticCroissant I expect the linker to fail because the linker do not have symbols yet, but it seems that using `init()` the returned object will relies only on the v-table. – Pierre Pagnoux Dec 15 '14 at 23:18
  • No, not really. The imperfect part. It is very brittle to versioning. The client code calls the completely wrong function, very hard to diagnose. – Hans Passant Dec 15 '14 at 23:19
  • This isn't going to end well. What's wrong with COM? – David Heffernan Dec 15 '14 at 23:22
  • @PierrePagnoux the binary you are building per se doesn't rely on anything related to the class. It's the `initializeObject` function that needs to use the vtable of the class. However, `initializeObject` is in the same library as the class it uses. So, the linker sees at the time you are building **the DLL** what symbols there are (including the class' vtable). – The Paramagnetic Croissant Dec 15 '14 at 23:25
  • @TheParamagneticCroissant I do not understand well why the `initializeObject` use the v-table, it does only dynamic allocation? Otherwise, when I call `b->get_number()` in the `main` function, it look at the v-table, right? – Pierre Pagnoux Dec 15 '14 at 23:33
  • @HansPassant I understand your point of view, but my original problem was to have a common interface and different implementations that relies on different APIs. So, I chose to have multiple DLLs to have better control on resources allocation by 3rd-party APIs. – Pierre Pagnoux Dec 15 '14 at 23:42
  • @PierrePagnoux: Wrong, it does not "only dynamic allocation". It uses the `new` operator, which is translated by the compiler to a sequence of *two* actions: First, an allocation function named `operator new(size_t)` is called. Second, the object constructor is called. The v-table setup is performed by the constructor. – Ben Voigt Dec 16 '14 at 00:44
  • Also, for virtual member functions, `declspec(dllexport)` on the class is completely useless. – Ben Voigt Dec 16 '14 at 00:46

1 Answers1

0

There are two ways to link dll files.

The 2nd way (the way it works) is the C Binding approach, where you query the dll for a specific function name and it returns a functor to you.

Using the 2nd way you won't be able to extend the base classes, since they are not defined (you don't have any code to be copy pasted so to speak at linkage time).

In order to have a dll who's classes can be extended, you will need to use dynamic binding. You need to compile your .dll and also provide a Symbols Library (or an export library). You have this option in VS studio in project properties.

The mechanism is as following :

  • Compile Dll project -> output : myLib.dll , myLib.lib
  • Use exported symbols from myLib.lib inside your main project (main project takes myLib.lib as dependency)
  • at runtime,due to the binding, your program will know it requires myLib.dll to work so it will load it (if found, else you get runtime error)

Another advantage of using Export Library is that you can export C++ functions (which are mangled on export).

It's very hard to have C Binding on mangled functions.

C Binding on the otherhand, compared to the dynamic binding, won't make your program scream if myLib.dll isn't found , you will just get a null pointer to function.

MichaelCMS
  • 4,703
  • 2
  • 23
  • 29
  • Use the `.lib` will solves most of my problems, but it is the *load-time* way of loading DLLs and it does not fit my needs. Finally, according to [HowTo: Export C++ classes from a DLL](http://www.codeproject.com/Articles/28969/HowTo-Export-C-classes-from-a-DLL), it seems that I was close to the *C++ mature approach*. Finally, I chose to create an interface that contains only pure virtual methods and no data. I had to export one C function to create the object and everything else is relying on the object v-table. – Pierre Pagnoux Dec 16 '14 at 19:59
  • 1
    Well, the C++ mature approach that you mentioned is an answer to a question that you didn't ask :) . It's actually a design pattern , it has nothing to do with dll or library exports, and it's widely used on multiple languages when you want to hide your implementation :). Check Bridge Pattern and Factory Pattern. Your Exposed pure virtual class follows the Bridge Pattern while the exposed C function follows the Factory Patter . – MichaelCMS Dec 17 '14 at 10:23