There’s a DLL in the system directory called
MSVCRT.DLL
,
and from its name, you might think that it is the
Microsoft Visual C/C++ Run-Time library.
That is a perfectly reasonable guess.
But it would also be wrong.
The Microsoft Visual C/C++ Run-Time libraries go by names
like
MSVCR71.DLL
or
MSVCR80.DLL
or
MSVCR90.DLL
or
MSVCR100.DLL
,
and the debugging versions have a D
in there, too.
And
like MFC,
these binaries might be on your machine as a side effect
of the implementation of a particular Windows component,
but they are not contractual.
If your program requires the
Visual C/C++ Run-Time library,
then your program needs to install the appropriate version.
(There are redistributable packages you can
include with your application.)
Okay, so what’s with the DLL with the misleading name
MSVCRT.DLL
?
The unfortunate name is a consequence of history.
Back in Windows 95,
MSVCRT.DLL
was the
Microsoft Visual C/C++ Run-Time library,
or at least it was the runtime library for Visual C/C++ 4.2.
As each new version of Visual C/C++ came out, the Windows team
had to go update their copy of MSVCRT.DLL
to match.
And if the Windows team wanted to fix a bug in
MSVCRT.DLL
,
they had to make sure that the Visual C/C++ team made the corresponding
change in their version.
This high degree of coördination became untenable,
especially since it required the Windows team to do things like
push a new version of MSVCRT.DLL
to all downlevel
platforms whenever a new version of Visual C/C++ came out.
(Good luck doing this in the days before Windows Update!)
And sometimes these fixes caused compatibility problems. For example, I remember there was a fix for a Y2K problem which caused one application to crash because the fix altered the stack usage in such a way that exposed an uninitialized variable bug.
One serious problem with the MVSCRT.DLL
“one runtime to rule them all” model is that
multiple versions of Visual C++ would all use the same library,
and keeping one DLL compatible with all versions of Visual C++
was a maintenance nightmare.
For example, if a new C++ language feature required a change
to the ostream
class,
you had to be careful to design your change so that the
class was still binary-compatible with the older version
of the class.
This meant not changing the size of the class (because somebody
may have derived from it)
and not changing the offsets of any members,
and being careful which virtual methods you call.
This was in practice not done, and the result was that (for example)
Windows 95 and Windows 98 both had DLLs called
MSVCRT.DLL
that were not compatible with each other.
And of course there was the problem of some application installer
unwittingly overwriting the existing copy of
MSVCRT.DLL
with an older one,
causing the entire operating system to stop working.
At some point, the decision was made to just give up and declare it an operating system DLL, to be used only by operating system components. All newer versions of Visual C/C++ used specifically-numbered DLLs for their runtime libraries. (Giving different names to each version of the run-time library solves the problem of trying to make one DLL service multiple versions of clients, as well as addressing the accidental downgrade problem.)
Although MSVCRT.DLL
has been an operating system DLL
for a long time,
and has been documented as off-limits to applications,
there are still
a lot of people
who
treat it as a C runtime delivery channel,
and those programs create a lot of grief for the product team.
I remember one change that the runtime library folks made to
MSVCRT.DLL
that had to be backed out and revisited
because they found an application that not only linked to
MSVCRT.DLL
instead of the runtime library the
compiler intended,
but also groveled into an internal array and manipulated
private members.
(I was one of the people who investigated this compatibility
issue, but I was not the one who solved it.)
// Note: The issue has been simplified for expository purposes struct SomethingInternal { int widget; short widgetFlags; char widgetLevel; int needs_more_time; }; SomethingInternal InternalArray[80];
The runtime library folks added a new member to the structure:
struct SomethingInternal { int widget; short widgetFlags; char widgetLevel; int needs_more_time; int needs_more_cowbell; };
This change increased the size of the
SomethingInternal
structure,
which in turn meant that when the application did
// Redeclare this internal structure in MSVCRT.DLL // so we can poke the needs_more_time member to get more time. struct SomethingInternal { int widget; short widgetFlags; char widgetLevel; int needs_more_time; }; extern SomethingInternal InternalArray[80]; ... InternalArray[i].needs_more_time = 1; ...
it ended up poking the wrong byte because the structure size didn’t match.
The runtime library folks had to go back and squeeze the cowbell
flag into the structure in a way that didn’t alter the size
of the SomethingInternal
structure.
I don’t remember exactly what the fix was,
but one way they could’ve done it was by squeezing the flag
into the one byte of padding between
widgetLevel
and
needs_more_time
.
struct SomethingInternal { int widget; short widgetFlags; char widgetLevel; char needs_more_cowbell; int needs_more_time; };
Bonus chatter:
The application had an easy time messing with the internal array
because the source code to the C runtime library
is included with the compiler,
So much for “All these compatibility problems would go away
if you published the source code.”
Publishing the source code makes it easier to introduce
compatibility problems,
because it lays bare all the internal undocumented behaviors.
Instead of trying to reverse-engineer the runtime library,
you can just sit down and read it,
and if you want to do something sneaky,
you can just
copy the
declaration of the internal array
and
party on
the
needs_more_time
member.
0 comments