对WIN32 API引入的预处理器命名空间污染有一个简单的一次性解决方案吗?

时间:2010-11-06 04:32:56

标签: c++ winapi

众所周知,包括<windows.h>在C ++中污染所有命名空间,方法是每个Win32 API函数有一个预处理器#define,可以采用多字节或UTF-16输入。一个例子是:

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

我已经使用原生的Win32 API已经有好几年了,但我只是放弃了!在任何非平凡的项目中都有足够的名称冲突,使你的脸变蓝。 请噢,有人可以提出一个解决方案,不需要我#undef这样定义宏在事后根据具体情况进行定义吗?我想在此之前采取肯定行动错误。

我总是使用Unicode / UTF-16,所以在CreateFont的情况下,我会直接在我的代码中调用CreateFontW;我不会使用宏定义。是否有人对此有一个解决方案,比如带有#undef的完整标题与windows.h包含在一起?

一个遍及各处的示例错误,就是“GetMessage()”之类的通用名称被占用。

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

当你想要的只是你的类有一个名为void GetMessage()的成员函数时。太令人沮丧了。

2 个答案:

答案 0 :(得分:11)

最干净的解决方案不是直接在程序中调用Windows API函数,而是编写一个可以调用所需函数的抽象层。这样,您只需要在少量源文件中包含Windows API标头,并且宏更易于管理。

这还有一个额外的好处,即提高代码的可移植性,因为它只有一小部分依赖于Windows API本身,其余部分都将调用你的抽象层。

即使您不关心可移植性,抽象层仍然有很多好处:您的代码将更易于阅读和更易于维护,因为进行API调用的复杂性将封装在抽象层中(一些Windows API函数有很多你通常不关心的函数。

由于您使用的是C ++,因此如果您更喜欢使用异常,抽象层还可以允许您将Windows API错误代码转换为异常。

抽象层也有助于使代码更容易测试(模拟抽象层要比模拟部分或全部Windows API容易得多。)

答案 1 :(得分:4)

接受的答案非常好,我推荐的答案是可能的。不幸的是,这样的抽象层实现起来很繁琐,并没有解决整个问题。

  • 您必须虔诚地使用pimpl idiom来保留抽象层头文件中的所有WinAPI类型。 Windows标头应仅包含在 的实现(.cpp)文件中,而不应包含在标头中。
  • 在实践中,您最终必须创建许多自己的类,这些类捕获与基础WinAPI类型(例如,LOGFONT)基本相同的信息,因为抽象接口的客户端需要传递和接收它。您最终需要大量的样板代码来在您的抽象层类型和WinAPI类型之间进行转换。
  • 引入抽象层所需的大量重构可能是遗留代码库中的大量工作,特别是如果该代码在许多项目中共享的话。

抽象层的实现需要包含windows头。假设您为启用抽象层而引入的一个与平台无关的类型有一个名为GetFont的方法。依赖于它的与平台无关的代码将显示GetFont,因为该翻译单元中不会提及<windows.h>。但是在Windows上实现抽象层的实现文件(.cpp文件)将与GetFont预处理器宏发生冲突,因为它必须包含<windows.h>。因此,使用pimpl限制了问题的范围,但它并没有完全解决它。

我开始做的事情,包括使用良好抽象层的较新代码以及不能防止特定于平台的特定泄漏进入命名空间的旧代码,都是为WinAPI头创建包装器。我用#include <windows.h>替换了我控制的代码中的所有#include "apiwrappers/windows.h"行,这是对大致如下所示的文件的引用:

// 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
// ...

当我解决冲突时,我正在增加#undef的列表。我没有全面的提供。

对于其他WinAPI标题,我做了类似的事情,但其余的更简单。