Setting WINVER for MFC Applications

Setting WINVER for MFC Applications

Rate This
  • Comments 22

Pat Brenner

Hello, I’m Pat Brenner, a developer on the Visual C++ Libraries team, and I am the primary developer working on MFC.

I’ve realized over the course of the past several years that a number of developers (especially those using ATL and/or MFC) can be confused about the proper usage and setting of the WINVER #define (and/or the corresponding _WINNT_WIN32 and NTDDI_VERSION #defines). This setting is especially important when building an application that supports multiple Windows platforms, as MFC itself does. So I would like to suggest a best practice for how these #defines should be set when building an MFC application for multiple platforms.

Build time settings

At build time, you should not #define WINVER (or _WINNT_WIN32 or NTDDI_VERSION) to the lowest platform you want to support. This will keep you from using the latest APIs and structures available to you, and keep you from handling the latest Windows messages. Instead, you should #define WINVER (or _WINNT_WIN32 or NTDDI_VERSION) to the highest platform that you want to support. This will ensure that you have available the latest APIs, messages, and structures, so that your application can make use of all of the capabilities that the newest platforms have to offer. This is the way that MFC itself is built—this way we can add handlers for the latest Windows messages and use the latest Windows APIs. For example, MFC 10.0 (which shipped with Visual Studio 2010) supported Windows XP as the lowest platform, but also supported features only available in Windows 7, like live taskbar display of multiple MDI window contents.

Run time behavior and checking

At run-time, however, you need to ensure that your application will run on your lowest supported platform. This means that you cannot call an API available only on newer platforms directly, because calling the API directly will result in an “import not found” message on the older platform and your application will simply not start. Instead, you must dynamically load (via LoadLibrary/GetProcAddress) any API that is supported only on newer platforms. For example, MFC does this when using the D2D APIs. Here’s an example from the current version of MFC—see the InitD2D method of the _AFX_D2D_STATE class in module AFXRENDERTARGET.CPP provided with Visual Studio 11 Beta:

 

m_hinstDWriteDLL = AtlLoadSystemLibraryUsingFullPath(L"DWrite.dll");
if (m_hinstDWriteDLL != NULL)
{
    auto pfD2D1CreateFactory = AtlGetProcAddressFn(m_hinstDWriteDLL, DWriteCreateFactory);
    if (pfD2D1CreateFactory)
    {
        hr = (*pfD2D1CreateFactory)(writeFactoryType, __uuidof(IDWriteFactory), (IUnknown**)&m_pWriteFactory);
    }
}

 

Note the use of the AtlLoadSystemLibraryUsingFullPath helper (whose implementation is in ATLCORE.H), which will ensure that the “system” DLL is loaded only from the correct Windows library folder. Also note the use of the AtlGetProcAddressFn macro (whose definition is in ATLDEF.H), which will call GetProcAddress using the correct function prototype.

You also must ensure that you are not calling any Windows API with a structure that is larger than it expects, because it may simply fail. Make sure that you understand the structure size that the API is expecting on that platform and use that size. For example, MFC does this when sending tool-tip messages. Here’s a (slightly modified) example from the current version of MFC—see the HitTest method of the CToolTipCtrl class in module TOOLTIP.CPP, and the definition of the AFX_OLDTOOLTIPINFO structure in AFXIMPL.H, provided with Visual Studio 11 Beta:

 

TTHITTESTINFO hti = {};
hti.ti.cbSize = sizeof(AFX_OLDTOOLINFO);
hti.hwnd = pWnd->GetSafeHwnd();
hti.pt = pt;

if (::SendMessage(m_hWnd, TTM_HITTEST, 0, (LPARAM)&hti))
{
    Checked::memcpy_s(lpToolInfo, sizeof(*lpToolInfo), &hti.ti, sizeof(AFX_OLDTOOLINFO));
    return TRUE;
}

Cautions

Please note that you do have to be careful when using this approach. It is very easy to end up using an API that is only available on a later platform, only to find that when your customers try to run your application on an older platform, it won’t even run. When your application tries to load a non-existent API from a system DLL at startup (when the import table is being initialized), it will fail, and an error message to the effect of “import not found” will be issued. So make sure to test your application on all supported platforms to make sure that it is compatible.

It is only necessary to follow this recommended practice when building an application that is expected to support multiple Windows platforms. If your application only needs to support a single platform, you can set WINVER (or the corresponding other #defines) to the value that matches that platform. This way you are sure to use only the APIs and other features that are available on that platform.

I hope you find this information helpful. Please feel free to respond with any questions you may have.

 

Pat Brenner
Visual C++ Libraries Development

  • Man, it sounds like a total pain in the ass to support multiple versions of structures. Don't some API's assume larger sizes mean extended fields?

  • That makes no sense.  If you define WINVER to the lowest platform, you'll get errors when using APIs only available on higher platforms, and will *know* to load them via LoadLibrary instead after properly checking for the platform version.

  • Agree w/Mark.    It's not obvious what APIs/#defines were added over time, and the quickest way to discover that is to first set WINVER to your low-end target.  After that you could reset it to the high-end, but that remains dangerous as you add new code over time, you could accidently add a call to a 'new' api without realizing it.

  • Hmm... so you guys are defensing the decision of not to support XP by demonstrating how messy and painful it will be ? :-)

  • I think that promoting that as best practice is shooting yourself in the foot, not everyone can afford to specify "Only the latest platform", a lot of developers are required to support things a long way back and I think this method is going to cause issues as it will be extremely unlikely that you catch all the APIs on the relevant level.

    If you are saying that there is a hidden linker option that you can specify that verifies that only API calls available on version X are static and that there is no non-dynamic calls to stuff later than that then please share...

  • I agree with the consensus so far.  My customers are using XP forward and I need to be 100% sure that my app will run.  Setting WINVER to the lowest is the only guarantee I have.

    Moreover, none of the above-XP APIs offer features so revolutionary or useful that offering them to my users with more up to date Windows gives that much benefit.

    I think MFC could / should do more to encapsulate a solution to this issue if Microsoft really wants developers to be offering the newer API features to users.

  • Totally agree with everyone so far. Pat's proposal seems counter intuitive, hard work and prone to failure for those of us who must support legacy systems as well as newer ones.

  • Surely it's easier to do the reverse? i.e. set WINVER to the lowest platform you want to support, then manually define any structures and constants for API functions only available on newer platforms (e.g. copy+paste these from windows.h) and call the newer functions using GetProcAddress on those newer platforms.  Then you really don't have to worry about accidentally using new API functions that cause the "import not found" message on older platforms.

  • So we should set WINVER to the higher platform version, but then load those DLLs dynamically? But how would I know it's not supported on a previous version? This sounds so error prone. Because if you set the version to the lowest supported OS, then the compiler/linker tell you it's not available.

  • Also SystemParametersInfo SPI_GETNONCLIENTMETRICS really messes up due to all this WINVER stuff.  You have to subtract from the real size the size of iPaddedBorderWidth and pass in a fake size if you want any hope if it running on earlier operating systems.  It's been a nightmare we've been living with for 6 years now (since the release of Vista)

  • By the way I'm not kidding about this, they have this documented in the API help, and expect you to do it.  Who would ever think to do this, without reading the docs. Shows you how important reading docs for every single function you call is - msdn.microsoft.com/.../ff729175(v=vs.85).aspx

  • Hi all,

    Thanks for all the responses so far.  I see that most of you disagree with my recommendation, but I will stick to it.  This is exactly the scheme we use when building MFC itself--which as you know, has to support multiple operating system version--and I find that it works very well.  I agree that you can run into problems if you don't test on all your supported systems (see my Cautions section above). Supporting multiple platforms well is hard work no matter how you propose to enable it.  I just have found that this is the best way for my purposes, and I had conversations with several other senior developers in the Windows team who agreed with this practice.

  • This certainly wasn't considered the best practice when I worked at Microsoft.  It seems contrary to the immense effort Microsoft has historically put into backward compatibility.  We always set the macros to the oldest supported version.  Where ever you want to uses a newer API, you called LoadLibrary/GetProcAddress and locally declared any missing structures.  That kept the newer structure definitions from leaking back into the general purpose code and ensured nobody ever unknowingly called an unsupported API.

    The best advice along these lines that I've ever heard is to develop on the oldest version of the OS you plan to support, and test (primarily) on the most recent.  Unless the developers experience the pain points that their users will experience first hand, it's hard to understand the priorities of fixing these.  I used to work on a large team, and we had developers on a wide variety of the supported OSes and hardware.  This was a while ago, so we had some developers on Windows 3.1, some on NT4, some on 95, etc.  It was great for discovering and debugging platform-specific issues.

  • So, a naive question:

    If the "best practice" from Microsoft really is to actively *prevent* the compiler from detecting errors, shouldn't the compiler team step in and create some more powerful tools? Say, extend the /analyze switch to detect and flag all API calls to functions defined in higher versions of Windows than some specified value, allowing me to flag, for example, all API calls that rely on Vista or newer.

  • Option a: set the WINVER to the lowest value, and load functions that aren't available to you using GetProcAddress when the compiler tells you that it isn't available.

    Option b: set the WINVER to the highest value, and load functions that aren't available to you using GetProcAddress when you look up in the documentation and find that it's not available to you.

    Hrm, which is more error prone?  I guess it's Option b!  I'll stick with Option a thank you.

    Pat, can you explain what automated methods you have for doing Option b that the rest of us obviously don't know about?  I assume that you must have some.

Page 1 of 2 (22 items) 12