Using a binary built in VC verXXX from a binary built in VC verYYY is very dangerous.
This is very obvious in retrospect, but real life recently forced us to try just that: we migrated to VS2010, and a few 3rd party components still hadn’t. Below are two of the more general lessons we learnt along the way – hope that they might save someone some trouble.
Lesson 1: You can’t pass STL containers as arguments in a call from one version to another.
STL classes memory layout is subject to change in major versions. This is true in principle for all internal structures (vftable / vbtable layout, RTTI tables, _com_XXX helpers, whatever), but seems true in practice only for STL containers.
As an example take std::vector, and examine its /d1reportclasslayout dumps. In VS2005:
class ?$vector@HV?$allocator@H@std@@ size(20):
+—
| +— (base class ?$_Vector_val@HV?$allocator@H@std@@)
| | +— (base class _Container_base)
0 | | | _Myfirstiter
| | +—
4 | | ?$allocator@H _Alval
| | <alignment member> (size=3)
| +—
8 | _Myfirst
12 | _Mylast
16 | _Myend
+—
while in VS2010:
class ?$_Vector_val@HV?$allocator@H@std@@ size(20):
+—
| +— (base class _Container_base12)
0 | | _Myproxy
| +—
4 | _Myfirst
8 | _Mylast
12 | _Myend
16 | ?$allocator@H _Alval
| <alignment member> (size=3)
+—
Quite a few changes has taken place – the implementation moved from within vector to the parent _Vector_val, the parent’s member _Container_base::_Myfirstiter was replaced by _Container_base12::_Myproxy, and what have you. The change of interest in this context, however, is the seemingly benign move of the allocator member _Alval, from the start of the object to its end – thereby rendering VS2005-generated vectors completely unreadable to VS2010-generated binaries.
Lesson 2: You can’t allocate memory in one version and free it in another.
– because the DLLs were linked against different CRT versions, and thus use separate CRT-heaps.
At process startup, every dependent DLL entry-point is called – ‘DllMain’ in user DLLs (by default), ‘CRTDLL_INIT’ for MSVCRXXX.DLL. That makes two such calls, in two CRT DLLs. Each of these call _heap_init, which includes:
Where _crtheap is a static handle, accessible via _get_heap_handle.
Each CRT DLL maintains bookkeeping structures referring to its own heap (essentially linked lists with block usage info). When you delete (or free) a pointer the CRT tries to update its bookkeeping. If the pointer was allocated on another heap – say, one created by a different CRT DLL – the bookkeeping update fails, and all hell breaks loose.
This is all more than legit.
For one, MS openly declares in various channel 9 interviews and podcasts that they break binary compatibility in every major VS version. Second, MS did deliver COM which is an exceptionally stable ABI (I’m not sure there’s even a concept of COM-version). Third, and most importantly, c++ does not have a standard ABI. Moreover, MS – unlike others – go out of their way to respect backward compatibility. Certainly evey framework must be allowed room to evolve, etc. etc. etc – from every angle I can think of, no contract was broken here.
And still.
Still, the scenario of gradually upgrading a multi-vendor app is fairly basic, and I shouldn’t be made to jump through unnecessary hoops to achieve that. Certainly swapping around the layout of std::vector is such an unnecessary migration barrier.
Thanks for this tip. Great post as usual.
I still remember the pain from two years ago when we upgraded to 2008. Only after migrated 98% of our software suite we realized that we are lacking source code for the other 2%. It was a horror story.