Checking file versions is surprisingly hard.


I was wandering around the web the other day and ran into this post.  In general I don’t have many issues with the post, until you get to the bottom of the article.  The author mentions that his code only runs on Win7 or newer so he helpfully included a check to make sure that his code only runs on WIn7:

// Example in C#.

internal bool SupportsTaskProgress() {
    if (System.Environment.OSVersion.Version.Major >= 6) {
        if (System.Environment.OSVersion.Version.Minor >= 1) {
            return true;
        }
    }
    return false;
}

This is a great example of why it’s so hard to write code that checks for versions.  The problem here is that this code is highly likely to fail to work on the next version of Windows (or whenever Windows 7.0 is released).  In that case SupportsTaskProgress will incorrectly return false.

 

Personally I wouldn’t even bother writing the SupportsTaskProgress function this way.  Instead I’d check for the “new TaskbarLib.TaskbarList()” call to return NULL and assume that if it returned NULL the API call wasn’t supported (the non COM interop equivalent would be to check for a failure on the call to CoCreateInstance).  That way the code would work even if (for some obscure reason) the taskbar logic was ported to a previous OS version.

 

If I simply HAD to keep the SupportsTaskProgress function, I’d rewrite it as:

// Example in C#.

internal bool SupportsTaskProgress() {
    if (System.Environment.OSVersion.Version.Major >= 6) {
        if (System.Environment.OSVersion.Version.Major == 6) {
if (System.Environment.OSVersion.Version.Minor >= 1) { return true;
}
return false; }
return true; } return false; }

That way it would only check for minor version being greater than 1 if the major version is 6.  I suspect that this code could be tightened up further as well.

 

This is a part of the reason that picking a version number for the OS is so complicated.

Comments (28)

  1. OK, I’ve seen people code like this and have never figured out why. Given that the && operator will stop evaluating as soon as one condition returns false, why not use that to combine the IF statements?

  2. SlackMaster K: I wanted to replicate the original style.  See my "I suspect that this code could be tightened up further as well" comment.

  3. Paul McLeish says:

    You could do:

    internal bool SupportsTaskProgress() {

      return System.Environment.OSVersion.Version >= new Version(6, 1)

    }

    That should be equivalent.

  4. jeffdav says:

    In the general case for Versions V, you have M.m.r.p (Major, minor, revision and patch—or whatever they’re called).  To compare two, you have to start with the major one and cascade:

    if (M1 > M2 ||

        (M1 == M2 &&

          (m1 > m2 ||

            (m1 == m2 &&

              (r1 > r2 ||

                (r1 == r2 &&

                   (p1 > p2))))))) {

       // V1 > v2

    }

    I’ve not tried to optimize it beyond that, as it doesn’t need to happen that often.

  5. BootBlock says:

    Hey all – I’m the "author" mentioned above.

    I won’t go into the reasons of the brain-fart with the code as the original post that contains it has been updated to explain why, but one thing I’d like to mention is (which is directly above the actual code in the original posting):

    "The comparisons haven’t been short-circuited due to reasons of clarity."

    The visitors of my site tend to be end users as opposed to programmers (although some are beginners); I’ve learnt in the past to keep code examples very simple otherwise I get "emails".

    I’ve now updated the code and also written it so it’s less readable for the visitors to my site. *cough*

  6. Ben says:

    We shouldn’t blame the task as being hard.  The concept here is easy.  It’s that the design of the API makes it hard.  So someone should fix the design and add a Win32 function that checks the version like Paul’s code shows.

    In general, APIs should make easy things easy and complex things hard, not the other way around.

  7. BootBlock: Read your email please.

  8. Clinton Pierce says:

    Slackmaster: Not exactly like this, but I’ve broken up conditionals like this just to make debugging easier.

    As I’m stepping through something particularly complicated in a debugger, it’s nice to know why I’m in (or not in) this code without having to count parens, consider precedence, and deal with the odd assignment/ternary embedded in an overworked conditional all the while doing whatever it takes to get the values of the variables IN the statement.

  9. Like Paul says. This is trivial in .net since Version implements a greater than operator and icomparable

  10. Churl says:

    Let .net do the "heavy lifting" for you:

    Version win7 = new Version("7.1");

    Version osVersion = new Version(System.Environment.OSVersion.Version.Major, System.Environment.OSVersion.Version.Minor)

    if(osVersion >= win7)

  11. andycadley says:

    Part of the problem is the rather daft Major/Minor version numbering scheme itself. If each new release was a x.0 release, then version detection code would be a cinch since you’d just do a straight comparison of the major version numbers.

    I’ve never entirely understood the rational behind overcomplicating this with an additonal Minor version. Was there really a worry we might run out of integers?

  12. Anon says:

    If I was doing it in C I’d be tempted to put the major version in the top bits of an integer and the minor version in the bottom bits and compare.

    E.g.

    #define PACK_VERSION(major, minor) ((major<<16)|minor)

    Then you can just compare integers. That said it feels wrong to do it this way rather than checking which functions are available. Actually, I’d rather just code to a subset of Win32 that has been around for ages because I’ve got less chance of breaking stuff on one OS.

  13. Dave Aronson says:

    A clearer way to check by version number, using a concept easily portable to any other OS or app:

    internal bool SupportsTaskProgress() {

       if (System.Environment.OSVersion.Version.Major > 6) return true;

       if (System.Environment.OSVersion.Version.Major < 6) return false;

       return System.Environment.OSVersion.Version.Minor >= 1;

    }

    If you prefer, change the last line to:

       if (seov.minor >= 1) return true;

       else return false;

    You can extend this general idea as needed to account for revision, patchlevel, etc.

    (Sorry if this isn’t kosher/canonical C#; I don’t actually know that, being mainly a plain old C-monkey.)

  14. Anon, the idea of packing major and minor into an int actually doesn’t work that well – we tried it in DOS years and years ago.

    I actually love the .Net solution (make Version an icomparable and then move the logic somewhere lower).  The cool thing is that it’s essentially available in unmanaged code via theVerifyVersionInfo API: http://msdn.microsoft.com/en-us/library/ms725492(VS.85).aspx

    But it doesn’t change my base comment: Don’t ever check windows versions.  Instead check for functionality being present or not.  

  15. Dada says:

    I somehow find it ironical that someone has trouble with comparing versions and can get COM right.. 😐

  16. dave says:

    >I suspect that this code could be tightened up further as well.

    It sure could:  😉

    bool result = (major == 6 && minor >= 1 || major > 6);

    (One of my pet peeves is using an if statement to evaluate a condition as true or false, and then using the result of the comparison to control which assignment statement to execute, one assigning true or one assigning false).

    As to the original claim that comparing version numbers is hard: without wishing to come across as all superior-sounding, it seems a trivial matter to me.   After all, it’s the basis of lexical ordering: "BA" is greater than "AA" based on the first letter, and "AB" is greater than "AA" based on the second letter because the first letters are equal.

  17. Koro says:

    "Don’t ever check windows versions.  Instead check for functionality being present or not."

    You can’t always do that.

    Do I want to add a __try/__except to catch delay-load exceptions around every UxTheme call or just do:

    g_bTheme=(g_bWinNT&&(g_nWinVer>0x00050001));

    Then check that flag before calling OpenThemeData?

    In some other cases too (all the Crypt Hash functions – trying to compute an MD5) the functions is documented as working fine in Win98 but it just fails – there is no way to know except of checking the version beforewards.

    At least, as implied earlier, I just pack the Windows version in a DWORD at program startup to avoid nasty version comparision errors.

  18. Koro: Why not use LoadLibrary/GetProcAddress on OpenThemeData and then have a boolean check?

  19. On my previous post , Koro made the following comment : “Don’t ever check windows versions.&#160; Instead

  20. Jonathan Allen says:

    Perhaps I missed something, but shouldn’t the check be…

    System.Environment.OSVersion.Version >= New Version(6, 1)

  21. Leo Davidson says:

    I agree with Ben. The Win32 API makes this too easy to get wrong. Too many people messed up with the old version-checking API so it seemed like a new one was added, but that one is ridiculously complex and error-prone as well. 🙁

    GetVersion, GetVersionEx and VerifyVersionInfo all suck. There should be an easy-to-call "Am I on X or greater?" function. Of course part of the problem with adding improved version functions is current code cannot assume they exist. 🙂

    VerifyVersionInfo, the newest of the three, not only makes you use crazy macros but requires you to do things like specify a service pack when you almost never care about that and just want to test which major version (2k, XP, Vista, 7) you are on. :-/ You’ve got to read, understand and remember a big block of text in MSDN to use what should be a very simple API.

    However, I also agree with Larry that you should almost always check for features and not check the version number at all.

    And the .Net way of doing things does seem good.

  22. Yuhong Bao says:

    Yep, wonder why Windows 95 and NT 4 and later reports version 3.95 to 16-bit apps marked for Windows versions earlier than 4.0? It is exactly because of bugs like this. In fact, what made it harder was that the Windows version returned by the 16-bit GetVersion() function was the opposite of the expected byte order. As I remember, the low byte was the major version and the high byte was the minor version, so Windows 3.1 was 0x0A03 and Windows 4.0 (95) was 0x0004. It should be obvious now that you have to swap the bytes first to do the comparison correctly, which was easy in x86 assembly (xchg xh,xl) but a bit harder to do in C since C do not provide a byte swap operator, so you had to use something like (version >> 8) | (version << 8).

  23. asf says:

    What I want to know is, why is the format of GetVersion() so stupid? 9x does not have build number, NT has build number, but you have to mask it off because the minor ver is also packed there even tho it has already been included in the other WORD (IIRC)

  24. Miral says:

    > Instead I’d check for the “new TaskbarLib.TaskbarList()” call to return NULL and assume that if it returned NULL the API call wasn’t supported (the non COM interop equivalent would be to check for a failure on the call to CoCreateInstance).

    Wait, would it actually return null or would it throw an exception?  (I haven’t really used COM from .NET, so I’m curious.)  The former would be a large surprise (it’s not really something that "new" is supposed to do), and the latter has negative performance consequences.  (Then again, you probably shouldn’t be allocating COM objects anywhere that perf matters anyway.)

  25. Miral says:

    Oh, and yeah, doing cascading compares like this in a readable fashion is really easy (as Dave showed above):

    static int Compare(MyType one, MyType two)

    {

     if (one.field1 < two.field1) { return -1; }

     if (one.field1 > two.field1) { return 1; }

     // repeat the above two lines for each field, in order of decreasing significance

     return 0;

    }

    It’s really surprising how many people get this wrong.  It’s even more surprising when someone gets this wrong even when the type being compared manually already has comparison operators, as Paul pointed out.

  26. Yuhong Bao says:

    BTW, if you use VerifyVersionInfo on Server 2003 RTM to check for XP SP2, does it return TRUE or FALSE?

  27. Yuhong Bao says:

    Here is a blog article by Raymond Chen on this:

    http://blogs.msdn.com/oldnewthing/archive/2004/02/13/72476.aspx

  28. Yuhong Bao says:

    "That way the code would work even if (for some obscure reason) the taskbar logic was ported to a previous OS version."

    I already mentioned in a comment that appears to be deleted how HeapSet/QueryInformation was backported to Win2000 in a hotfix.

    In this month’s security update, SetSearchPathMode was backported to Vista and XP, though not Win2000.

    What is sad was that the Windows SDK docs did not document these changes very well. For example, HeapSetInformation says that it was in Win2000 SP4, while it really ended up in a post-SP4 hotfix (actually it ended up in a time window that resulted in two versions, the older being only compatible with SP3, and the newer being compatible with SP4 as well), while HeapQueryInformation don’t say that it is compatible with Win2000 as well. I hope MS will do better with documenting SetSearchPathMode.