3

我有一个依赖于 OpenCV 和 TBB 的 VS (C++) 项目,因此我为每个库创建了属性表并将它们包含在项目中。一切正常,代码已编译。

昨天,我开始使用vcpkg包管理器。我通过 vcpkg 安装了 OpenCV 和 TBB,一切似乎都正常。我创建了一个空项目,包含两者的标头并测试了新编译的库是否有效。验证后,我回到我的主项目并删除了属性表,所以我可以使用来自 vcpkg 的库。自上次成功编译以来,我没有以任何方式更改代码。

但是当我现在尝试编译代码时,我两次收到此错误(在 main.cpp 和子模块中)

tbb\critical_section.h(53):错误 C3861:“InitializeCriticalSectionEx”:找不到标识符

有谁知道这里发生了什么或为什么会发生此错误?

更新

我自己发现了错误。我正在添加 poco-libraries 标签,因为它实际上是 TBB 和 Poco 之间的冲突。

4

1 回答 1

8

我找到了问题的根源,它实际上与 TBB 无关,而是与Poco 库有关。

考虑最小的例子:

#include <Poco/Poco.h>
#include <tbb/tbb.h>

void main()
{   
}

这将引发编译器错误。

追踪路径

当包含tbb.h时,critical_section.h包含在tbb.h的第 51 行中。然而,ciritcal_section.hpp包括machine/winwdows_api.h看起来像这样(不必要的东西被剪掉了):

待定/机器/winwdows_api.h:

#if _WIN32 || _WIN64

#include <windows.h>

#if _WIN32_WINNT < 0x0600

#define InitializeCriticalSectionEx inlineInitializeCriticalSectionEx

inline BOOL WINAPI inlineInitializeCriticalSectionEx( LPCRITICAL_SECTION lpCriticalSection, DWORD dwSpinCount, DWORD )
{
    return InitializeCriticalSectionAndSpinCount( lpCriticalSection, dwSpinCount );
}
#endif

如您所见,在检查宏之前包含windows.h 。该宏在sdkddkver.h(包含在windows.h中)中定义,如果尚未定义(在我的情况下它设置为 Win10):_WIN32_WINNT

sdkddkver.h:

#if !defined(_WIN32_WINNT) && !defined(_CHICAGO_)
#define  _WIN32_WINNT   0x0A00
#endif

windows.h中,_WIN32_WINNT宏控制实际包含哪个版本的 windows 头文件。如果_WIN32_WINNT设置为 Windows Vista 之前的版本,InitializeCriticalSectionEx则未定义该功能。

machine/winwdows_api.h(如您在该文件的代码块中所见)通过简单地定义InitializeCriticalSectionEx调用适当替代函数的宏来捕获此问题。

到目前为止,一切都很好。

问题

万恶之源在于 Poco 库的Poco/UnWindows.h。包含 poco 标头时,有时会包含UnWindows.h 。

Poco/UnWindows.h(缩短):

#if defined(_WIN32_WINNT)
    #if (_WIN32_WINNT < 0x0501)
        #error Unsupported Windows version.
    #endif
#elif defined(NTDDI_VERSION)
    #if (NTDDI_VERSION < 0x05010100)
        #error Unsupported Windows version.
    #endif
#elif !defined(_WIN32_WINNT)
    #define _WIN32_WINNT 0x0501
    #define NTDDI_VERSION 0x05010100
#endif
#endif    

#include <windows.h>

预处理器检查是否_WIN32_WINNT已定义,如果没有,则将其设置为 0x0501,即 Windows XP。之后,包含windows.h。在上一章中,我提到_WIN32_WINNT控制实际包含哪个版本的 windows 头文件。

现在想象一下,我们项目中的第一个包含是来自 Poco 的标头。这意味着,这_WIN32_WINNT将设置为 Windows XP 并且windows.h将包含Windows XP的 windows 标头(imo 已经是一个不好的迹象)。

但别担心,情况会变得更糟。

如果我们向上跟踪包含层次结构,我们会到达Poco/Platform_WIN32.h

Poco/Platform_WIN32.h(缩短):

#include "Poco/UnWindows.h"
...
    #if defined (_WIN32_WINNT_WINBLUE)
        #ifdef _WIN32_WINNT
            #undef _WIN32_WINNT
        #endif
        #define _WIN32_WINNT _WIN32_WINNT_WINBLUE
...

有趣,不是吗?首先,它包含UnWindows.h,它设置_WIN32_WINNT并导致包含 Windows XP 标头,然后它重新定义_WIN32_WINNT为 Windows 8.1。我不知道为什么会这样,也许有一个很好的理由,idk。

如果我们现在看一下最顶部的最小示例,我们会看到 Poco 包含在 TBB 之前。现在发生的是:

  1. 包括 Poco 标头
  2. 设置_WIN32_WINNT为 Windows XP
  3. 包括 windows 标题(Windows XP 版本,因为 2)
  4. 重置_WIN32_WINNT为 Windows 8.1
  5. 包含 TBB 标头(windows 标头已经包含,因此 TBB 不需要在tbb/windows_api.h中再次包含它们)
  6. TBB 通过检查 Windows 版本_WIN32_WINNT并识别 Windows 8.1(由 Poco 设置)
  7. TBB 认为InitializeCriticalSectionEx是定义的,因为 Windows 版本是 8.1(或者它是?Poco 说:get rekt)并且InitializeCriticalSectionEx是从 Windows Vista 开始定义的。
  8. 不幸的是,Poco 确保加载了 Windows XP 头文件,因此编译器说:不。

解决方案

要么预先包含windows.h,要么预先设置_WIN32_WINNT自己:

#define _WIN32_WINNT 0x0A00    // either this
#include <Windows.h>           // or this

#include <Poco/Poco.h>
#include <tbb/tbb.h>

void main()
{   
}

也许 Poco 贡献者中的某个人可以在这里澄清一些事情。Poco 版本是 1.8.1-1,使用 x64(通过 vcpkg)构建。

更新

Poco 正在讨论这个问题。可在此处找到更新。

于 2018-04-26T11:53:30.420 回答