In general, ABI between different C++ compilers can vary in any way they see fit. The C++ standard does not mandate a given ABI.
However, C ABIs are extremely stable. One way to deal with this problem is to have header-only functions that translate your code into extern "C"
functions, which are exported from your DLL. Inside your DLL the extern "C"
functions then call a more conventional C++ interface.
For a concrete example,
struct Test;
// DLL exported:
extern "C" void Private_DoSomething_Exported( Test* );
// Interface:
namespace Interface {
inline void DoSomething( Test t ) { return Private_DoSomething_Exported(&t); }
};
// implementation. Using Test&& to make it clear that the reference is not an export parameter:
namespace Implementation {
void DoSomething( Test&&t ) {
std::cout << t.a << std::endl;
std::cout << t.b << std::endl;
}
}
void Private_DoSomething_Exported( Test* t ) {
Assert(t);
Implementation::DoSomething(std::move(*t));
}
This places the "most compatible" ABI (a pure "C" ABI) at the point where you export functions from a DLL. The client code calls Interface::DoSomething
, which inline
in the client code calls the "C"
ABI (which doesn't even know the layout of the object), which then calls a C++ Implementation::DoSomething
.
This is still not proof against every issue, because the layout of even POD structs could vary based on compilers (as a practical example, some compilers treat long
as 32 bit on 64 bit machines, others treat long
as 64 bits on 64 bit machines). Packing can also vary.
To reduce that impact, you'll first want to only use fixed size types from the C header files. You'll also want to examine the packing docs of both compilers.