How to properly set target OS version when building a library on Windows using Visual C++ compiler

12,296

Solution 1

Your answers to your own questions are for the most part correct. Some clarifications and corrections:

The subsystem version is orthogonal to the target architecture. What the /subsystem documentation is saying is that the minimum subsystem version for x86 is 5.01 and the minimum subsystem version for x64 is 5.02. For console and Windows apps, the subsystem version is the same as the internal operating system version number. 5.01 is x86 Windows XP; 5.02 is x64 Windows XP. There are two different version numbers for Windows XP because the x64 Windows XP was released later than the x86 Windows XP. Newer OSes have the same version number for all architectures (e.g., Windows Vista is version 6.0 for both x86 and x64).

Note that by setting the subsystem version, you can restrict the set of operating systems on which your program will run. E.g., if you set the subsystem version to 6.2, your program will only run on Windows 8 and above. If you try to run the program on e.g. Windows 7, it will not run. (The same is true for DLLs: If you have a DLL that targets an OS newer than the OS on which you are running, the loader will not load the DLL, at least not for code execution.)

See Wikipedia's page "List of Microsoft Windows versions" for a list of operating system versions. Windows XP is the oldest version of Windows supported by Visual Studio 2013.

The Windows 8 SDK only supports development of software down to Windows Vista. If you want to set _WIN32_WINNT or WINVER to build for Windows XP, you'll need to use the Windows 7 SDK (Visual Studio 2013 will install both SDKs).

Unless your program is substantially different for each target operating system, it would probably be much simpler to build one binary that runs on the oldest operating system you want to support (Windows XP) and either delay-load or dynamically load (via LoadLibrary/GetProcAddress) any functionality that you want to use from newer operating systems, when that functionality is available.

Solution 2

Sorry, this is going to be pretty long :-(

1.How do I tell the compiler to target a specific OS version (i.e. XP, Vista, 7, 8, 8.1, etc.)?

Typically you don't tell the compiler to target a specific OS version. Instead you tailor the header files from the SDK using WINVER and _WIN32_WINNT. The header file sdkddkver.h provides useful named constants

If you set WINVER and _WIn32_WINNT high enough the header files will declare the functions that are only available in later OS versions (assuming you are using an SDK that's new enough to have those functions declared).

But now you have a program that calls functions that may not exist on older OS versions. If you are lucky there's some sort of compatibility shim to help you out. If you are unlucky the attempt to call an unknown function will just fail. Or do something much worse than fail. But that's opinion on my part I can't find something concrete to cite.

2.How do I tell the compiler to target a specific architecture (i.e. x86, x64, arm, etc)?

You don't. You use a different compiler for each target platform. If you look under C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\ you'll find the bin directory and seven sub directories:

amd64
amd64_arm
amd64_x86
arm
x86_amd64
x86_arm

You use the compilers from the appropriate directory to build code for that platform. On a Win32 version of Windows drop the ' (x86)' from the path above.

3.What happens if I use functions/enum values/structure members available only in Windows 8/7...

I'm not sure this is behavior is defined. If you set _WIn32_WINNT to _WIN32_WINNT_WIN8 you are pretty much saying "I intend to run this on Windows 8 and above". But that's my opinion not definite fact. It could be that I have not read the documentation carefully enough.

4.How do I make it so the when I compile for Windows XP, my code skips over things that are not present in Windows XP (Windows 7/8 functions and such)?

You have to code for it explicitly, I don't believe there's anything to handle this problem for you. You can do that at compile time using #ifdef and _WIN32_WINNT or you can do it at runtime by determining whether or not the function can be called and then calling the function or avoid calling the function as appropriate.

In the (unlikely) worst of all possible worlds there will have been a breaking change such that (for example) the Windows 8 format of a struct parameter is incompatible with an earlier implementation of the function. In which case you have to figure out which version of the struct to use when invoking the function - and the header will only give you one struct definition based on the _WIN32_WINNT value.

5.Does it matter which Windows SDK version I use when targeting different OS versions?

Probably. Older versions of the SDK may not contain function definitions for later OS versions. For example when the Vista version of the SDK was created most functions introduced in Windows 8 probably didn't exist.

Is it fine if I always use the latest SDK version even when targeting Windows XP?

That's probably the safest thing to do. If you use the latest SDK and set _WIN32_WINNT to _WIN32_WINNT_WINXP then probably the right thing will happen - assuming you want to run on XP.

I say "probably" because XP is something of a special case. It's unsupported. The latest SDK may or may not have been tested with _WIN32_WINNT set to an XP value.

1.I just need to set _WIN32_WINNT and WINVER defines...

That should get you a code file that will execute correctly on the OS version identified by the _WIN32_WINNT value. It will most likely also work on later OS versions (Microsoft try quite hard not to break existing programs when they bring out a newer OS version). It might or might not run correctly on OS versions older than the one identified by _Win32_WINNT - I wouldn't risk it.

•I need to use appropriate option when setting up compiler enviroment variables

Since you are using NMAKE I'm not sure of the answer to this one. The vcvarsall.bat file sets various environment variables that (amongst other things) can control which set of compilers are used (by controlling where MSBUILD looks for binaries like compilers). But I don't know whether NMAKE runs MSBUILD or pays attention to those environment variables or has its own different set of variables or what.

•I also need to specify appropriate /SUBSYSTEM value to the linker, i.e. /SUBSYSTEM:WINDOWS,5.02 or /SUBSYSTEM:WINDOWS,6.00 for x64

The SUBSYSTEM major/minor values don't have anything to do with platform type (ARM, x86, x64, etc.) directly. They specify the minimum OS level the code file will run on. So SUBSYSTEM 5.01 says "this code file will run on XP" whereas SUBSYSTEM 5.02 says "this code file will run on Windows Server 2003 but not on XP". Ideally you should match subsystem major/minor to _WIN32_WINNT to avoid the risk of a file running on an older OS version that can't support it.

At least that's what I remember - I don't have an XP environment I can use to confirm I'm remembering this correctly.

Share:
12,296
Cookie Cat
Author by

Cookie Cat

Updated on July 25, 2022

Comments

  • Cookie Cat
    Cookie Cat almost 2 years

    I'm building a cross-platform library using Visual C++ 2013 compiler with C++11 features on Windows platform specifically, using CMake (NMake generator) for build system. I'm using Windows 7.

    My library uses some of functions/enum values/structure members available only in Windows 8/7.

    I want to be able to build the library for Windows XP, Windows Vista, Windows 7 and Windows 8/8.1 OS versions and x86, x64 and arm architectures, i.e. not a single build that just targets Windows XP and works everywhere, but many different builds targeting specific OS'es, since newer OS versions have more of useful features my library could provide.

    My questions are:

    1. How do I tell the compiler to target a specific OS version (i.e. XP, Vista, 7, 8, 8.1, etc)?

    2. How do I tell the compiler to target a specific architecture (i.e. x86, x64, arm, etc)?

    3. What happens if I use functions/enum values/structure members available only in Windows 8/7 but build my library targeting Windows XP? Would compiler warn me that such things don't exist on Windows XP? Or would it actually compile but fail to run on the Windows XP system?

    4. How do I make it so the when I compile for Windows XP, my code skips over things that are not present in Windows XP (Windows 7/8 functions and such)?

    5. Does it matter which Windows SDK version I use when targeting different OS versions? I seem to have installed 8.1, 8.0 and 7.1 Windows SDKs. Is it fine if I always use the latest SDK version even when targeting Windows XP?

    Here are some answers I found, which I'm not sure are correct or complete:

    1. I just need to set _WIN32_WINNT and WINVER defines to appropriate values for the target system and that's it, I don't need to set anything in addition to that, my application will run on the specified system (i.e. Windows XP) with just that.

      • I need to use appropriate option when setting up compiler enviroment variables with "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat", i.e. "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat amd64" for 64 bit.
      • I also need to specify appropriate /SUBSYSTEM value to the linker, i.e. /SUBSYSTEM:WINDOWS,5.02 or /SUBSYSTEM:WINDOWS,6.00 for x64. But what is the difference between 5.02 and 6.00? Why two values specify the same thing (64-bitness)? Same for 5.01 and 6.00, why they both specify 32-bitness? I would emagine having a single value for 64/32 bit would be enough.

        • Those values (5.01, 5.02 and 6.00) look similar to platform values from WINVER from (1). Do they also set target OS, besides the architecture? But WINVER=502 from (1) is used to target Windows Server 2003, which was released in both 64-bit and 32-bit versions according to wikipedia, but in here 5.02 stands strictly for 64-bit, which doesn't make sense...
    2. Compiler would fail to compile because the WINVER define from (1) would exclude functions and stuff not present in targeting OS (Windows header files use that define to #ifdef things).

    3. I should #ifdef things based on WINVER in my own code, like Windows headers do, and provide alternatives for the missing functionality when required.

    4. Don't know.

    Note that I'm not using Visual Studio IDE, so telling me to set option X in the IDE is a bit meaningless.

    I'm coming from Linux development, so Windows things are a bit new to me.

  • Cookie Cat
    Cookie Cat almost 9 years
    When building for Windows XP, how do I tell Visual C++ compiler to use Windows 7 SDK instead of Windows 8 SDK, which seems to be the default one? Because besides specifying /SUBSYSTEM:WINDOWS,5.01 and setting _WIN32_WINNT and WINVER to _WIN32_WINNT_WINXP (0x0501) I also need to use the older SDK.
  • Cookie Cat
    Cookie Cat almost 9 years
    I need to use Windows 7 SDK because Windows 8 SDK doesn't claim to support Windows XP. I see that Visual C++ 2010 is the last one that defaults to Windows 7 SDK, but I have to use Visual C++ 2013 because of C++11, so I can't just use Visual C++ 2010, I need to make Visual C++ 2013 use Windows 7 SDK.
  • Cookie Cat
    Cookie Cat almost 9 years
    It seems that the scipt that I call to set up enviroment variables, C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat amd64, calls C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\bin\amd64, which in turn calls %VS120COMNTOOLS%VCVarsQueryRegistry.bat (which evaluates to C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\Tools\VCVarsQueryRegistry.bat), which hardcoded to set up variables to point to Windows 8 SDK. I don't see any switch to make it point to something else, Windows 7 SDK for example.
  • Cookie Cat
    Cookie Cat almost 9 years
    In the Windows 7 SDK install there is C:\Program Files\Microsoft SDKs\Windows\v7.1\Bin\SetEnv.Cmd, which seems to set enviroment variables to Windows 7 SDK, but it also seems to use Visual C++ 2010, which won't work for me... Maybe if I call this SetEnv.Cmd before/after calling C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat amd64, my Visual C++ 2013 would magically start using Windows 7 SDK?
  • Cookie Cat
    Cookie Cat almost 9 years
    Seems like setting the toolset to "v120_xp" solves it, but for that to work I have to use "Visual Studio 11" generator in CMake and then build the generated Visual Studio solution with devenv. Doesn't seem like CMake's NMake generator knows of toolsets. I guess I will be using the "Visual Studio 11" generator for now, but it would be a lot nicer to be able to specify the toolset when using NMake. Maybe I should call some extra .bat file or set some variable somewhere?