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
0 comments