Why does the Get­Version function report the major version in the low-order byte, and the minor version in the high-order byte?

Raymond Chen

Laura Butler wonders whose idea it was to have the Get­Version function report the major version in the low-order byte, and the minor version in the high-order byte. This is completely messed up: You clearly should put the major version in the high-order byte and the minor version in the low-order byte, so that you can do things like

if (HypotheticalBetterGetVersion() >= 0x030A) {
  // version is at least 3.10
}

Instead, as things stand today, the major version in the low-order byte, and the minor version in the high-order byte, so version 3.10 is reported as the value 0x0A03, which leads to mistakes like this:

// Code in italics is wrong
if (GetVersion() >= 0x0A03) {
  // incorrect check for version ≥ 3.10
}

Why is the version number reported in such a strange way?

Rewind to MS-DOS.

MS-DOS has a Get Version system call, and it returns the operating system version in the same reversed way, with the major version in the low-order byte, and the minor version in the high-order byte.

The thinking was that programs will almost always be checking the major version exclusively, because the only time you’d need to check the minor version is if you needed to check for some feature that was added in a minor version. But by definition, minor versions don’t add many features (right?), so checking for the minor version should be very rare.

During this era, programs were written in assembly language, and that’s the detail that shines light on the unusual format of the version number.

On the 8086, the 16-bit AX register can be treated as a 16-bit value, or it can be treated as two 8-bit values. If you treat it as two 8-bit values, then the high-order byte is called AH, and the low-order byte is called AL.

The 8086 instructions to compare the values in these registers are encoded as follows:

Instruction Encoding
CMP AL, 03h 3C 03
CMP AH, 03h 80 FC 03

Since almost everyone will be comparing the major version, we should put the major version in the location that is most efficient to consume, and that would be the AL register. Putting the value there permits a shorter instruction encoding than if the value had been in the AH register.

It saves a byte!

The IBM PC came in two memory configurations, 16KB and 64KB. If you had shelled out the big bucks for the 64KB version, that one byte is 0.0015% of your total memory. Scaled to a modern 8GB system, choosing this format for the version number saves 128KB.

Windows adopted the version numbering scheme from MS-DOS, and that’s why the Get­Version function returns the major and minor versions in reverse order.

17 comments

Discussion is closed. Login to edit/delete existing comments.

  • cricwood@nerdshack.com 0

    What a conincidence! I was using GetVersion just the other day, so all of you will be blessed with a pile of useless trivia.

    First of all, that major version check was only sufficient until maybe Windows XP(?) but incorporating the minor check was still pretty size-effective:

    // ror ax
    if(_bswap_ushort((USHORT)GetVersion()) >= 0x501) // Windows XP or later

    If you just wanted to detect major Windows releases, this served you well until Windows 10 appeared. Now you could keep that check, but in order to detect Windows 10 as 0xA00, you had to add a pretty huge application compatibility manifest to your executable (350 B, probably?). It would be unfair to count that against version detection because you should have done that anyway, but if you hadn’t this was your last call!

    And now Windows 10 is here to stay forever. And you can only differentiate individual releases by their build number. This could be done pretty size-effective in theory:

    bswap eax
    ror ax

    but I couldn’t get a single compiler to emit this code. The closest candidate was

    DWORD BbmM = GetVersion();
    DWORD OOmM = 0xFFFF & BbmM;
    DWORD MmOO = _bswap_ulong(OOmM);
    DWORD OOBb = BbmM >> 16;
    DWORD version = MmOO | OOBb;
    if(version >= 0x0A0047BA) // Windows 10 1903 or later

    … which is a magnitude larger, but 2019 bytes are a lot smaller than 1993 bytes!

    Snarky comment: The Windows 10 build number was 10240 in 2015 and is now 18362 (1903). Extrapolating this pace, GetVersion() and GetVersionEx() will break in 16 years (the highest bit in the build is reserved). EVERYBODY PANIC!

    – You have been thinking a lot about this function call, haven’t you?

    Yes.

    – Why not just use IsWindowsVersionOrGreater()?

    Come on, just look at the assembly it generates! The purity of my beautiful program will not be tainted by such wrappers! I’ll be darned if I include a 100-B wrapper function where I can also generate a 15-B call by typing six lines of incomprehensible bit twiddling! Zere vill be order in mein Burger!

    – But GetVersion() is REALLY deprecated and forbidden in SDL! And what are you trying to achieve anyway!

    I’m just trying to detect Dark Mode in Windows 10, which MS decided

    1) to not (yet) to port to native Win32 applications even though there are two theme APIs already and

    2) to not even offer a native API call to detect it.

    So I’m left clueless just like everyone else and I guess, if you’re in WinAPI, do like the WinAPIians do – use the most obscure unsupported way to re-invent the wheel there is 🙂

    • Jan Ringoš 0

      There are still more things that can be safely detected only by checking version number: A lot of APIs don’t document behavior when unknown flag is used on older version, so your safest bet is to check version/build, and use the flag only then.
      Why couldn’t they simply increment the minor version number, even if it needed another GUID in manifest, is beyond me. Very stupid marketing decission to keep with 10.0 if you ask me. At least Server 2019 could’ve been 10.1.
      And mentioning manifests, fear not, there’s now more mess glued on it instead of reusing existing stuff, maxVersionTested Id=”10.0.18298.0” in compatibility/application section. Not sure how much that actually affects though.

      • Jernej Simončič 0

        The reason for using GUIDs to indicate Windows version compatibility was to prevent programs from doing maxVersionTested_Id=”99.99.99999.99999″, because you can be certain that some would do exactly this.

    • Paradice . 0

      Hmm, I wonder if it’s the right choice to try and detect Dark Mode – should you instead just retrieve the Foreground & Background UIColors (which is painful enough in win32, acknowledged), but then use them to paint whatever you’re painting? ATM they’re pure white & black in dark mode, but that’s probably not guaranteed. 

      • cricwood@nerdshack.com 0

        I’m not sure what you mean with retrieving foreground & background colors. Dark mode affects Store apps only. All Win32 theme APIs (classic GetSysColor(), Vista’s GetThemeSysColor() and whatever there was for XP) keep returning the light colors no matter what you do.
        —-
        This is my current main reason for still sticking to Windows 7 with my main system – almost all programs use GetSysColor() well, so you get a perfect Dark Mode by switching to the Classic theme and customizing its colors. This worked even with websites in Internet Explorer years back (long before people talked about Dark Mode APIs in browsers or started programming their own dark modes into websites).
        But on Windows 8+, the Classic Theme is gone, all Win32 apps are always colored light, and my eyes burn.
        I understand that the Dark Mode caused lots of problems and hat to be rushed out:
        – all Common Controls need adjustment, and they only finished the few that Edge uses
        – undocumented flags had to be introduced to control style bits
        – plus all the usual compatibility problems we Old New Thing readers know oh so well
        I understand that it made no sense to add an API to Win32 apps at this point, and I still hope and pray that it comes eventually.
        But the one thing I just don’t understand, after having used a dark Classic theme on Windows XP / Vista / 7 for more than ten years with almost no problems, is: Why did MS abandon GetSysColors() and then re-invented the whole thing from scratch?!
        From the bottom of my heart I hope that I can read the answer on Raymond’s blog in ten or fifteen years and that it won’t be “politics” 🙂

    • John Elliott 0

      Have the Win32 launch a UWP app and screenscrape it to find out what colours it’s using? (I kid… well, mostly).

  • Ian Boyd 0

    And for those who don’t like old version checking API, they can use current version checking API. Which you should use anyway, since so many people get it wrong.

    We all remember the Java that mistook Windows 9 for Windows 95.

    And the  asp.net framework that mistook Internet Explorer 10 for ie 1.

    • David Walker 0

      “Mistook Windows 9 for Windows 95”???  What was Windows 9?  I never saw such a thing.

      • Jernej Simončič 0

        IIRC, there was one public beta (of what later became Windows 10) that had a 9.x version number, and identified itself as Windows 9.

  • Henrik Andersson 0

    I must be getting too used to this, because I instinctively guessed “because that’s what DOS did” when I read the title.

  • Keith Patrick 0

    Was any though given to having a comparison API instead of the built-in operators? (immediately, I’m asking myself questions like is this like CompareTo where you return -1, 0, 1?  Was anyone doing CompareTo like that in those days?)

  • Yuhong Bao 0

    This reminds me of the “Windows 3.95” debacle. I wonder how many software required Windows 3.1 in 1993 and 1994.

    • Jernej Simončič 0

      That was done because programs did comparisons like `if (version.major >= 3 && version.minor >=1)`

  • John Elliott 0

    The published MSDOS 2.x source code for the Get_Version function says:

    ; Outputs:
    ;       OEM number in BH
    ;       User number in BL:CX (24 bits)
    ;       Version number as AL.AH in binary
    ;       NOTE: On pre 1.28 DOSs AL will be zero

    And looking at the MS-DOS 1.25 source in the same release:

    COMMAND: ;Interrupt call entry point
            CMP     AH,MAXCOM
            JBE     SAVREGS
    BADCALL:
            MOV AL,0
    IRET:  IRET

    So… that suggests MSDOS 2.0 returned the major version in AL for backward compatibility: MSDOS 1.x would return AH=30h, AL=00h. And if you’re checking for ‘major version >= 2’ you don’t want to be special-casing ‘except if major version = 30h’.

  • John Elliott 0

    Thought I’d posted this before, but perhaps it got filtered as spam.

    I suspect, based on comments in the published MS-DOS 2.0 source, that MS-DOS (2.x+) returns the major version in AL for compatibility with MS-DOS 1, which doesn’t implement that function, and returns AL=0 leaving AH unchanged. In practice this means it returns 3000h. So if MS-DOS 2 had returned the major version in AH, there would have had to be special case checks for a major version of 30h; but by returning the major version in AL, checks for ‘AL >= 2’ will work as expected.

    • Raymond ChenMicrosoft employee 0

      Sorry, it got held for too many links. I fixed the formatting and approved it.

  • Daniel Marschall 0

    Hello, I have a question in regards Version-Information. I have looked at the version information of the SQL Server 2017 setup (original filename “SqlSetupBootstrapper.exe”) which has the version attribute “GoldenBits” with value “True”. I did not find any information about “GoldenBits”. I am curious what purpose it has, or if it is just some kind of easteregg 🙂

Feedback usabilla icon