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

  • Maybe because of the "best practice" MS platform SDK includes are buggy and some API is marked as available only starting OS X while it is actually available starting OS X minus one.

  • JLO, or even worse, X + 1.

  • GregM: 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.

    Warning - not all functions are protected by WINVER #defines.

    StrFormatByteSizeEx is one example of a function which is not protected.

  • Rob, I know, i've found one of those myself.  I reported it in connect, and it was fixed in a future version.  Do you know if that one has been logged in connect?

  • I have checked the source files in VS2008 and many if not all of the WINVER defines are in the headers files, in structures and inline functions. I wonder how if the MFC is built targeting Windows Server 2003, there wouldn't be problems when loading the libraries in a different OS ie: Windows XP? Maybe this is another good reason to program in .NET: so we don't have to worry about these issues? There are not #defines in .NET.

  • BTW, I have found a serious bug in at least one of the collection classes of the MFC that yields wrong results, when building my application with optimizations enabled. This doesn't happen when I build the app with the optimizations disabled (ie: debug build). I have verified this with MFC 4.2 dynamically linked. I haven't tested yet what happens when compiled with the latest version of the MFC but I am very concerned.

  • This recommendation makes sense if a developer's top priority is to move quickly to take advantage of the latest, greatest, OS APIs, such that they're willing to expend a fair amount of effort supporting simultaneous old and new API usage within the same product.  I can see why it's in Microsoft's interests to move people to new APIs ASAP, so I can believe that this is recommended practice for MS teams.

    However, for most development shops, it doesn't make sense to spend a lot of effort developing features that only customers on recent OS versions can take advantage of.  It's much more cost effective to develop for the lowest common denominator, unless the new APIs offer something incredibly compelling.

Page 2 of 2 (22 items) 12