开发者

Is there an easy one-shot solution to the preprocessor namespace pollution introduced by the WIN32 API?

开发者 https://www.devze.com 2023-01-23 14:16 出处:网络
As we all know, including <windows.h> pollutes all namespaces in C++ by having one preprocessor #define per Win32 API function that can either take multibyte or UTF-16 input. An example is:

As we all know, including <windows.h> pollutes all namespaces in C++ by having one preprocessor #define per Win32 API function that can either take multibyte or UTF-16 input. An example is:

#ifdef UNICODE
#define CreateFont CreateFontW
#else
#define CreateFont CreateFontA
#endif

I've used the native Win32 API for quite a few years now, but I'm just about giving up! In any non-trivial project there are enough name collisions to make your face turn blue. Please, oh please can someone come up with a solution which doesn't require me to #undef such define macros on a case by case basis after the fact? I want to take affirmative action before this causes any bugs.

And I always use Unicode / UTF-16, so in the case of CreateFont, I would call CreateFontW directly in my code; I would not use the macro define. Is there someone who has a solution to this, like a complete header with #undef's to wrap together with windows.h includes?

An example error that cree开发者_JS百科ps up everywhere, is the fact that a generic name like "GetMessage()" is taken up.

font.cpp(78) : error C2039: 'GetMessageW' : is not a member of 'FontManager'

When all you want is your class to have a member function called void GetMessage().So frustrating.


The cleanest solution is not to call the Windows API functions directly in your program and instead to write an abstraction layer that can call the functions you need. This way, you only need to include the Windows API headers in a handful of source files and the macros are much more manageable.

This also has the added benefit of improving the portability of your code since only a small part of it will rely on the Windows API itself and all the rest of it will call your abstraction layer.

Even if you don't care about portability, an abstraction layer still has a lot of benefits: your code will be easier to read and more maintainable because the complexity of making the API calls will be encapsulated in the abstraction layer (some of the Windows API functions have a lot of functions that you don't usually care about).

Since you are using C++, an abstraction layer can also allow you to translate Windows API error codes into exceptions, if you prefer to use exceptions.

An abstraction layer also helps to make your code more easily testable (it's a lot easier to mock your abstraction layer than it is to mock parts of or all of the Windows API).


The accepted answer is very good, and the one I recommend whenever possible. Unfortunately, such an abstraction layer is tedious to implement and it doesn't solve the whole problem.

  • You have to use the pimpl idiom religiously to keep any and all WinAPI types out of the abstraction layer's header files. The Windows headers should be included only in implementation (.cpp) files and never in headers.
  • In practice, you end up having to make a lot of your own classes that capture essentially the same information as an underlying WinAPI type (e.g., a LOGFONT) because the client of the abstraction interface needs to pass and receive it. You end up needing a lot of boilerplate code to translate between your abstraction layer types and the WinAPI types.
  • The big refactoring necessary to introduce the abstraction layer can be a lot of work in a legacy codebase, especially if that code is shared among many projects.

The implementation of the abstraction layer requires including windows headers. Suppose one of the platform-independent types you introduced to enable an abstraction layer has a method called GetFont. The platform-independent code that relies on it will see GetFont because there will be no mention of <windows.h> in that translation unit. But the implementation file (the .cpp file) that implements the abstraction layer on Windows will have a conflict with the GetFont preprocessor macro because it must include <windows.h>. So using pimpl limits the scope of the problem, but it does not solve it completely.

What I've started doing, both in newer code which uses good abstraction layers as well as older code that wasn't good about preventing platform-specific leaks into the namespace, is to create wrappers for the WinAPI headers. I replace all the #include <windows.h> lines in the code I control with #include "apiwrappers/windows.h", which is a reference to a file that looks roughly like this:

// Prevents Windows headers from defining macros called min and max, which
// conflict with identifiers in the C++ standard library.
#ifndef NOMINMAX
#define NOMINMAX
#endif

// Distinguishes between different types of handles so that we get better
// error checking at compile time.
#ifndef STRICT
#define STRICT
#endif

#include <windows.h>  // the "real" <windows.h>

// Lowercase far and near are perfectly reasonable names for local variables
// and parameters, so we don't want redefinitions of them.
#undef far
#undef near

// But lots of other Win32 headers use FAR and NEAR, which are typically
// defined to far and near.  We need them to be empty.
#ifdef FAR
#undef FAR
#define FAR
#endif
#ifdef NEAR
#undef NEAR
#define NEAR
#endif

// Windows defines macros to select between the "ANSI" and "wide" versions
// of many API functions (e.g., GetMessage macro expands to GetMessageA or
// GetMessageW).  We undefine many of these here to avoid naming collisions.
// Call the specific -W functions explicitly instead of relying on these
// macros.
#undef GetFont
#undef GetMessage
// ...

I'm growing the list of #undefs as I resolve collisions. I don't have a comprehensive one to offer.

For other WinAPI headers, I do something similar, but the rest of them are much simpler.

0

精彩评论

暂无评论...
验证码 换一张
取 消