Date: | February 13, 2004 / year-entry #61 |
Tags: | history |
Orig Link: | https://blogs.msdn.microsoft.com/oldnewthing/20040213-00/?p=40633 |
Comments: | 33 |
Summary: | Version numbers. Very important. And so many people check them wrong. This is why Windows 95's GetVersion function returned 3.95 instead of 4.0. A lot of code checked the version number like this: UINT Ver = GetVersion(); UINT MajorVersion = LOBYTE(uVer); UINT MinorVersion = HIBYTE(uVer); if (MajorVersion < 3 || MinorVersion < 10) { Error("This... |
Version numbers. Very important. And so many people check them wrong. This is why Windows 95's GetVersion function returned 3.95 instead of 4.0. A lot of code checked the version number like this: UINT Ver = GetVersion(); UINT MajorVersion = LOBYTE(uVer); UINT MinorVersion = HIBYTE(uVer); if (MajorVersion < 3 || MinorVersion < 10) { Error("This program requires Windows 3.1"); } Now consider what happens when the version number is reported as 4.0. The major version check passes, but the minor version check fails since 0 is less than 10. This bug was so rife that we gave up shimming every app that had the problem and just decided, "Fine. If anybody asks, say that the Windows version is 3.95." I suspect this is also why DirectX always reports its version as 4.x. |
Comments (33)
Comments are closed. |
The best version number API I’ve seen returns the version numbers as a string AND provides a strcmp-like function for comparing version numbers. That way you can perform calculations like so:
if (Version_Cmp(GetVersionStr(), "3.11") >= 0) {
Do something
}
Returning the version as a string is no good either. People would do
if (strcmp(GetVersionStr(), "3.11") >= 0) …
and then they would fail once the OS reached version 10.
The function VerifyVersionInfo is essentially the Version_Cmp function above.
In light of these mentioned problems, I would return the value as a 16.16 fixed point number. So, 3.11 would be 3 << 16 + 11 (which is 3 * 65536 + 11 or 0x0003 000B). Then it is a simple compare of two integers.
Does anyone see any problems with this?
It just sucks how much you have to pander to the stupidest common denominator. It also is bad that you can’t pull an Apple and just start off fresh every so often.
Jason: You can certainly use the MAKELONG macro yourself to pack the versions into a single integer in order to simplify the comparison. But sometimes people have need to check the sub-minor versions, too, and then you start to run out of space in a 32-bit integer. (For example, Windows XP Service Pack 1 is version 5.1.2600.1106.)
I’ve seen the very bug that Raymond points out on plenty of Microsoft’s own source code, internally, although in every case I saw, the problem got scrubbed out long before release (usually within days of the first checked-in, bogus, version comparison).
<p>Raymond: For example, Windows XP Service Pack 1 is version 5.1.2600.1106.</p>
<p>In this case, why not just compare the build number (2600)?</p>
That’s still not good enough, because Windows XP RTM is 5.1.2600.0 (same build number).
Why not 4.2 instead of 3.95?
It’s absolutely stunning how much poor quality software is out there. Just stunning.
What constantly frustrates me are applications that won’t run on newer operating systems… yet drivers that WILL allow themselves to be installed on newer operating systems.
Maybe the industry could come up with a highly visible "hall of shame" for these instances. (Hey, I can dream.)
So you quit shimming every program that had bugs, and stuck a shim between the API and the app.
=)
sorry, couldn’t help my self…
I’ve always liked Apple’s implementation of versioning: they return a 16-bit Binary Coded Decimal representation of the verson number. The fist two digit are the major number, the last two the minor and subminor, respectively. Thus if you want to require System 7.5.2 for example you write:
if (version < 0x0752) {
fail();
}
Which still works with 10.3.2 (0x1032). Although, it will break when they get past Mac OS 99.9.9. ;-)
> I’ve always liked Apple’s implementation
> 10.3.2 (0x1032).
What if minor or subminor version happen to be greater than 9?
10.10.3.4 > 10.1.3.4
10.1034 < 10.134
Obviously, under the Apple scheme they can’t be. :)
However, the general concept can be extened out to any number of BCD digits, and the invariant (old version < new version) will hold as long as the format of the BCD encoded number remains the same. That is, the number of digits allocated to each version, subversion, etc. doesn’t change.
Why does Windows XP return 1 as its minor version number instead of 10? Someone made a mistake, I take it?
"It just sucks how much you have to pander to the stupidest common denominator. It also is bad that you can’t pull an Apple and just start off fresh every so often. "
You can, its called .NET.
[Note: This entry has been edited by the blog maintainer.]
Things get even more exciting when you include service packs into the mix.
XP can be said to be a superset of Win2K. Therefore we could say VerXP > Ver2K. Likewise, XP-SP1 can be said to be a superset of XP-Gold, so VerXP_SP1 > VerXP.
But now the test: is WinXP-SP1 a superset of Win2K-SP4? In other words, is VerXP_SP1 > Ver2K_SP4? And the answer is… not necessarily.
Win2K-SP4 might contain some bug fixes and backported features (hardware support, etc) that will only arrive in the XP line as of XP-SP2. Therefore, WinXP-SP1 couldn’t be said to be a superset of Win2K-SP4. Whether VerXP_SP1 > Ver2K_SP4 is true depends entirely on the reason you are asking (bug fix, etc).
As such, version numbers are best thought of as describing *code lineages*. There is a main lineage where Win2K, WinXP, and Win2003 lie. But each major OS forms the base of a fork containing their service packs. Thus versioning could be modeled using trees. You can ask how far down a given branch you are, but that doesn’t tell you anything about the branches that aren’t between you and the root.
The guides on Javascript available in the Internet say, “Don’t check for versions; check for objects.” Which is essentially checking for capabilities. Which means
try {
LoadLibrary foo("foo.dll"); GetProcAddress<BarProc> barProc(foo, "BarProc"); baz = barProc(quux);
}
catch (LoadLibrary::Exception)
{
// something
}
catch (GetProcAddress::Exception)
{
// something else
}
I’m imagining some C++ wrappers around LoadLibrary and GetProcAddress here, but you get the idea.
I’ve always wondered why we don’t just reverse the version checking pattern: instead of the app asking the OS for its version number and (incorrectly) deciding if it is good enough, why doesn’t the app give the expected minimum version to the OS and it does the work to figure out if the current OS is good enough? Eg:
if (IsWindowsVersionAtLeast(3, 1) != TRUE)
{
printf("You need to upgrade");
exit(1);
}
Then version checking — rather than version reporting — becomes a base function of the OS.
It’s already there there. Just nobody uses it.
http://msdn.microsoft.com/library/en-us/sysinfo/base/verifying_the_system_version.asp
VerifyVersionInfo
Client: Requires Windows XP or Windows 2000 Professional.
Server: Requires Windows Server 2003 or Windows 2000 Server.
> if (IsWindowsVersionAtLeast(3, 1) != TRUE)
Is it moral to compare for equality against TRUE? :) I mean, the general convention for BOOL is that 0 is FALSE, and everything else is TRUE, and the TRUE define or constant is usually defined as some arbitrary non-FALSE value, like 1 or -1. 2 is as TRUE as 1, but will fail the comparison against 1.
Centaur’s right. Checking for equality against TRUE is asking for trouble.
This bug is pretty understandable. It’s a code path that can’t be tested. When a program was released under windows 3.1, there was no windows version greater than 3, so the code path never would have run, and hence never noticed.
Everyone makes mistakes, so I don’t really blame the devs of such a bug. It’s just one of those things.
Although you can probably avoid it by pushing your code into a function IsVersionOK(currentVersion), and unit testing that function. However, lack of unit testing usually comes from impossible deadlines and such imposed from above, and hence the managers, not the programmers, are most likely the reason why such code reaches the wild.
Shane, Application Verifier has a test called HighVersionLie to test for this sort of thing.
http://www.microsoft.com/windows/appcompatibility/appverifier.mspx
Why not have an enum for known versions.
public enum WindowsVersions
{
2000,
XP,
Server
}
BLAH, its not going to change at runtime. Saves all the parsing errors and counting.
[Note: Edited by blog maintainer. This is getting really boring really fast. Will "Jack"/"Ivana" please stop misrepresenting yourself as a Microsoft employee. This is your last warning.]
Re: capabilities:
Nobody likes these. Some Highly Placed Folks working on the Big Reset Thingie mentioned simply rejected the idea and said that they would only consider a version numbering based approach.
Oh well, maybe the Next Really Big Thing after this current Really Big Thing will get it right
I got thinking about how easy we have it sometimes in Linux. In a similar situation, we seem to favour fixing the app rather than the kernel. Then I thought some more, and remembered the painful disaster that is the version number checking in agpgart.
A version number is actually a list of integers, which we happen to write with dots between them, like 5.1.2600.1106. On one level, comparing version numbers by comparing strings like "5.1.2600.1106" seems as silly as comparing floating point numbers by comparing their string representations.
What you really want is a data structure (call it, oh, Version) that consists of a list of integers. Operations you’d want include converting to and from strings, comparing versions (by finding the leftmost numbers that differ), and asking what the version of the OS/library/whatever is.
Can you tell I’m up late fixing bugs in C++ code? :-) This sounds like a CS 101 assignment to me.
That’s why in the (D)HTML-world we atleast _try_ to do capabilities-checking, instead of browser-strings and scrary things like that.
Well, we try to do the right thing anyway, there are still cornercases that need extra checking.
What a boring title.
Normally, I’m talking about how to fix applications here, but I want to digress and instead talk about