【问题标题】:How do I resolve naming conflicts with headers that use the preprocessor to redefine common function names?如何解决与使用预处理器重新定义常用函数名称的标头的命名冲突?
【发布时间】:2018-11-16 02:02:16
【问题描述】:

编辑:我发现了一个类似的问题,答案基本上是 windows.h 不好,您必须重命名函数或 #undef 宏:Other's library #define naming conflict

但是,由于 LoadLibrary 在调试和发布版本下的冲突行为,我相信我的仍然不同。


我正在使用 Visual Studio 在 Windows 上进行编程,但在 windows.h 使用的预处理器指令及其包含的标头中遇到了一些特殊问题。

我们的项目在自己的命名空间MyProject::FileManager::CreateFile() 中有一个函数。包含 windows.h 后,由于链接器错误指出无法解析 MyProject::FileManager::CreateFileW(请注意函数名称末尾的 W),我们的代码无法编译。这不是静态函数,它是使用 file_manager.CreateFile(...) 调用的 FileManager 对象的成员函数。

在 Visual Studio 中突出显示该函数时,工具提示会显示以下内容:

#define CreateFile CreateFileW

我们很困惑,但只是将函数重命名为一种解决方法。但是后来我们在尝试从 Windows API 中使用的 LoadLibrary 函数遇到了类似的问题。在调试模式下编译,LoadLibrary 被定义为LoadLibraryW(),它以 LPCWSTR(宽字符串)作为参数。当我尝试在发布模式下构建时,这个函数现在被定义为LoadLibraryA(),它采用普通的 LPCSTR。这破坏了我们的构建,因为代码是在 LoadLibrary 采用 LPCWSTR 的假设下编写的。

那么,我的问题是,程序员应该如何处理这个问题?我应该用#ifdef 检查调试或发布模式来包装我对LoadLibrary 的调用吗?还是有更简单的解决方案?

另外,我在 github 上发现了一个有趣的头文件,它似乎是为了 #undef'ing 所有这些函数名称而创建的:

https://github.com/waTeim/poco/blob/master/include/Poco/UnWindows.h

【问题讨论】:

  • 这就是宏的危害,当你#include windows.h 时你无法真正逃避这些。无论如何,当客户端代码也包含它时,它往往会达到一个好的结局。但这很难保证。选择其他名称或使用#undef。
  • 如果您收到LoadLibraryA,请确保已定义UNICODE(参见第一个链接)或直接使用LoadLibraryW。
  • @EvilTeach -- 命名空间无济于事;这些是在 windows.h 中定义的宏,它们会感染一切。

标签: c++ windows


【解决方案1】:

我通常会做一些事情来解决这个问题:

  • 在特定于 Windows 的层中隔离所有 Windows 系统调用。例如,如果我正在使用文件系统 API,我通常会使用 win/filesystem.h 和 win/filesystem.cpp 来包装所有调用。 (这也是将 Win32 错误转换为 std::system_error 异常、删除不需要/过时/保留的参数以及通常使 Windows API 对 C++ 更友好的好地方。)
  • 避免使用特定于 Windows 的类型。允许像 DWORD、LPTSTR 和 BOOL 这样的定义渗透到代码的所有级别使得处理 Windows.h 变得更加困难。 (还有移植。)#include <Windows.h> 的唯一文件应该是您的包装 C++ 文件。
  • 避免自己使用 Windows 重定向宏。例如,您的包装层应该直接调用CreateFileW 或CreateFileA,而不是依赖宏。这样,您就无需依赖 Unicode/多字节项目设置。

示例

win/filsystem.h 可能包含如下定义:

namespace win32
{
  class FileHandle
  {
    void* raw_handle_;
  public:
    // The usual set of constructors, destructors, and accessors (usually move-only)
  };

  FileHandle CreateNewFile(std::wstring const& file_name);
  FileHandle OpenExistingFile(std::wstring const& file_name);
  // and so on...
}

代码的任何部分都可以包含此文件以访问文件系统 API。由于win/filesystem.h 本身不包含<Windows.h>,因此客户端代码将不受各种Win32 宏的污染。

【讨论】:

  • 能否请您详细说明如何隔离 Windows API 调用?我不确定这如何解决问题。预处理器不会继续传播这些宏吗?另外,我没有考虑你的第三点,这似乎是一个上帝的解决方案(只是避免宏)。
  • 它通过仅在需要的地方包含Windows.h 来解决问题。例如,win/filesystem.cpp 看到了 CreateFile 宏,但没有其他人看到。如果您在程序的其他地方有CreateFile 方法,它不受影响。此外,需要明确的是,包装层仅适应 Win32 API。它不包含任何业务逻辑或对业务对象的引用。
【解决方案2】:

这里的问题是 windows.h 试图支持两种不同的字符串模型:由单字节字符组成的字符串和以 unicode 编码的字符串(微软定义为两字节字符)。几乎所有的 Windows API 函数都有两种不同的版本,一种采用单字节字符串,另一种采用两字节字符串。您应该使用通用名称(例如CreateFile、LoadLibrary 等)编写代码,并让 windows.h 负责将这些名称映射到实际的 API 函数。对于单字节字符,这两个是CreateFileA 和LoadLibraryA;对于两字节字符,它们是CreateFileW 和LoadLibraryW。当然,还有更多。您可以在编译时通过在每个编译单元中定义宏 UNICODE 来选择模型。

顺便提一下,'A' 后缀代表“ANSI”,'W' 后缀代表“宽字符”。

当您编写试图将 Windows 依赖项隔离到少数源文件中的代码时,这尤其隐蔽。如果您编写的类有一个名为CreateFile 的成员函数,它将在不使用 windows.h 作为CreateFile 的源文件中以及在使用 windows.h 作为CreateFileA 的源文件中看到或CreateFileW。结果:链接器错误。

有几种方法可以解决这个问题:

  1. 在每个头文件中总是#include <windows.h>;这是一个编译器性能杀手,但它会起作用。 (windows.h 是早期针对 windows 的 C++ 编译器中预编译头文件的主要动力)

  2. 始终使用修改后的名称,CreateFileA 或 CreateFileW;这将起作用,但代价是在您进行 API 调用时失去了能够更改底层字符串模型的灵活性;这是否重要取决于您。

  3. 不要使用任何 Windows API 名称;如果您使用与 Windows 相同的命名约定,可能会很痛苦;如果您使用蛇形大小写(即所有带有下划线的小写字母来分隔单词),则一点也不痛苦。例如,create_file。或者,使用本地前缀或后缀:MyCreateFile 或 CreateFileMine。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-12-21
    • 1970-01-01
    • 2011-11-17
    • 2011-04-21
    • 1970-01-01
    • 2016-02-20
    • 2014-06-04
    • 2015-04-12
    相关资源
    最近更新 更多