I disagree with Chris Becke's viewpoint, while seeing the advantages of his approach.
The disadvantage is that you are unable to create libraries of utility objects, because you are forbidden to share them across libraries.
Expanding Chris' solution
http://stackoverflow.com/questions/232926/how-to-make-consistent-dll-binaries-across-vs-versions#232959
Your choices depend on how much the compilers are different. In one side, different versions of the same compiler could handle data alignement the same way, and thus, you could expose structs and classes across your DLLs. In the other side, you could mistrust the other libraries compilers or compile options.
In Windows Win32 API, they handled the problem through "handlers". You do the same by:
1 - Never expose a struct. Expose only pointers (i.e. a void * pointer)
2 - This struct data's access is through functions taking the pointer as first parameter
3 - this struct's pointer allocation/deallocation data is through functions
This way, you can avoid recompiling everything when your struct change.
The C++ way of doing this is the PImpl. See http://en.wikipedia.org/wiki/Opaque_pointer
It has the same behaviour as the void * concept above, but with the PImpl, you can use both RAII, encapsulation, and profit from strong type safety. This would need compatible decoration (same compiler), but not the same runtime or version (if decorations are the same between versions).
Another solution?
Hoping to mix together DLLs from different compilers/compiler versions is either a recipe for disaster (as you explained in your question) or tedious as you have let go most (if not all) C++ solutions to your code to fall back to basic C coding, or both.
My solution would be:
1 - Be sure all your modules are compiled with the same compiler/version. Period.
2 - Be sure all your modules are compiled to link dynamically with the same runtime
3 - Be sure to have an "encapsulation" of all third-party modules over which you have no control (unable to compile with your compiler), as explained quite rightly by Chris Becke at http://stackoverflow.com/questions/232926/how-to-make-consistent-dll-binaries-across-vs-versions#232959.
Note that it is not surprising nor outrageous to mandate that all modules of your application compiled against the same compiler and the same version of the compiler.
Don't let anyone tell you mixing compilers is a good thing. It is not. The freedom of mixing compiler is, for most people, the same kind of freedom one can enjoy by jumping from the top of a building: You are free to do so, but usually, you just don't want that.
My solution enables you to:
1 - export classes and thus, make real, non-castrated, C++ libraries (as you are supposed to do with the __declspec(dllexport) of Visual C++ for example)
2 - transfert allocation ownership (which happens without your consent when using allocating and/or deallocating inlined code, or STL)
3 - not be annoyed with problems tied to the fact each module has its own version of the runtime (i.e. memory allocation, and some global data used by the C or C++ API)
Note that it means you're not supposed to mix debug version of your modules with release versions of other modules. Your app is either fully in debug or fully in release.