VC++ Version Boundaries

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.

This entry was posted in VC++. Bookmark the permalink.

1 Response to VC++ Version Boundaries

  1. Alan Ning says:

    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.

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s