Multiple inheritance across .dll - weirdness

Started by
2 comments, last by NotAYakk 17 years, 10 months ago
I have zipped a small example program to demonstrate the problem (VC++ 2003). You can download it here: download zip file. Run the exe in the Debug folder, look at the output, and look at the source. Why is init1() being called twice?? It is dependent on the order in which you derive from Base1 and Base2. For example: class __declspec(dllexport) Derived : public Base1, Base2 will call init1() twice. class __declspec(dllexport) Derived : public Base2, Base1 will call init2() twice. I am thinking there is some memory issue here, or possibly the unique dll function ids are being foobared. Major ++ to anyone who can explain this oddity ;)
Advertisement
First, simplify your problems. The problem would have occurred without the DLL-crap.

Second, your init function returns a void*.

You then C-style CAST A VOID* and expect something reasonable to happen. C-style casts are evil. Don't use them. Ever. You don't know what they do, so you are not allowed to use them. By the time you know what they do, you will know why not to use them. (It is possibly you will have been ireversably brain damaged by the knowledge, and end up thinking you can continue to use them. In that case, there are crazytoriums you can be placed for the rest of your natural life...)

(reinterpret_cast, static_cast, dynamic_cast, and the good-old implicit implicit_cast are your casting friends. They at least let the reader know what insane gobblygook the code writer is actually up to.)

So, your C-style cast said "take this pointer, and pretend the exact binary address points at an object of type Base1".

But... wait! That generate complete and utter garbage. Undefined behaviour. Random crap!

Because the pointer was actually to an object of type Derived, which has a different memory layout than Base1. So any and all operations on this object... are completely undefined.

As it happens, when you took that block of memory, and interprited the memory as a Base1, and looked for the function init1(), it found it! You got lucky.

Then when you interprited as Base2... well, you found init1() again. Also pretty lucky -- you could have found the "format the C drive and email porn to my mailing list" function. Admittedly that is less likely, but we are talking about undefined behaviour.

So... in conclusion...

You called the wrong function because you invoked undefined behaviour when you cast an instance of a non-POD class X* to a void*, then to a class B*.

Stop using C-style casts.

Third, I'm crankey.

Forth, I never ran your program. Just read the code. I may have reversed an init1/init2/class name up above, but the jist I'm pretty confident of.

Fifth, I'm feeling hyperbolic. So take the above "you must never ever" with a grain of salt. Not that big of a grain of salt, but some salt. Or maybe some potasium.
I changed the code to remove the void pointer and return a Derived pointer instead, and Presto! it works correctly. Polymorphism and void pointers do not mix well.

But definitely follow NotAYakk's good, if not friendly given, advice to use C++ casts instead of C casts.
If you are hooked on the void* return type idea, use the following:

void* init() {  return reinterpret_cast<void*>(new Derived());}// other sideDerived* d = reinterpret_cast<Derived*>(init());// THIS STILL DOESN'T WORK:Base1* b = reinterpret_cast<Base1*>(init());



The above is brittle[1], but at the least it invokes well defined behaviour. You can cast from a class X* to a void* to an X* and not end up with undefined behaviour. You cannot cast from a non-POD class X* to a void* to a B* and end up with defined behaviour.

POD here refers to "Plain old data". POD is a technical term defined within the C++ standard. A POD class or struct behaves like a C class or struct, and (effectively) has a well-defined memory layout (taken with a pile of assumptions).

A non-POD class can have an arbitrary memory layout. Classes and Structs become non-POD when:
1> You add a constructor
2> You add a virtual function
3> You add virtual inheritance
4> You include any non-POD members or parent classes
5> You include any references
6> I believe overriding operator= might also make you non-POD, but I'm less than certain about that.

POD classes/structs are collections of flat binary data. Non-POD classes are magical unicorns whose guts you are not allowed to look at/play with, unless you want to be cursed by undefined behaviour.

[1] The contract between producer and consumer isn't spelled out in the function signature, so a small change in the init() function requires a change in every caller of init() -- if you fail to make the change, you get undefined behaviour and not a compiler error.

This topic is closed to new replies.

Advertisement