DLL export quirks
by Stoyan Nikolov August. 11, 12 0 Comment
Can you spot an error in this code (compiled with MSVC 2010 SP1 running on Win7; TestDLL is just a simple DLL project and an executable imports and uses it as described):
Although innocuous looking the code results in undefined behavior. What happens is that operator new is called in the EXE while operator delete is called in the DLL.A little playing around in the disassembly shows the reason. When you have a virtual destructor it’s address is of course put in the vtable of the object created. However when the compiler sees a class like the one illustrated it creates two destructors – one that works just as the programmer would expect – destroying all the members etc. and another one that does the same things but also calls operator delete on the object(the ‘deleting destructor’). This second destructor is the one set in the vtable of the object and is responsible for the behavior.
A fix for this problem is exporting the whole class, as pointed by Microsoft themselves –
In this case the compiler creates a ‘scalar deleting destructor’ for the class in the exe – calling the vanilla destructor and operator delete of the executable and putting it in the vtable, so everything works as expected.
Checking-out the assembly shows that the constructor of MyClass in the first case sets the address of the destructor to MyClass::`vector deleting destructor’ in the DLL (the one that calls delete) and nothing more.
However in the export-all-class case the compiler generates a ‘local vftable’ and overwrites the one created in the DLL.
As it turns out before version 5.0(!) of VC++ only the first case used to work but created all the said problems. So in 5.0 they changed the behavior to the current one that also has it’s drawbacks (like calling FreeLibrary as explained nicely in this thread).
If you __dllimport a whole class with a virtual destructor, the compiler creates a new virtual table and redirects the destructor to a local version in order to preserve the new/delete correctness. This appears to be the ONLY case it does this so in all other situations the programmer must be careful.
It is very tempting to just export the needed methods for a task and leave the rest hidden. However one must be aware of this quirk, that if left creeping unnoticed, might bring many headaches. In those cases the best solution is to rely on pure interfaces and factory functions like COM does it. This appears to be the most portable solution too. You could also override new and delete for the exported classes that has the advantage of not forcing you to use factories and can be easily be done with a common base class.
Follow Stoyan on Twitter: @stoyannk