Intel TBB - 'InitializeCriticalSectionEx': 编译器错误,未找到标识符

3
我有一个依赖于OpenCV和TBB的VS(C ++)项目,因此我为每个库创建了属性表并将其包含在项目中。一切正常,代码编译成功。
昨天,我开始使用vcpkg软件包管理器。我通过vcpkg安装了OpenCV和TBB,并且似乎一切正常。我创建了一个空项目,包含了两者的头文件,并测试了新编译的库是否工作。验证后,我回到了我的主要项目,并删除了属性表,以便可以使用来自vcpkg的库。自从上次成功编译以来,我没有改变任何代码。
但是,当我现在尝试编译代码时,我会在main.cpp和子模块中两次收到以下错误:
tbb\critical_section.h(53): error C3861: 'InitializeCriticalSectionEx': identifier not found
有人知道这里发生了什么或为什么会出现此错误吗?
更新
我自己找到了错误。我添加了poco-libraries标签,因为实际上是TBB和Poco之间的冲突。

这可能取决于项目所针对的Windows版本。 - Bo Persson
@BoPersson,有点做到了。我已经发布了一个答案来详细解释问题。 - Timo
1个回答

8

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

考虑以下最小示例:

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

void main()
{   
}

这将抛出编译器错误。

追踪路径

当包含tbb.h时,tbb.h的第51行将包含critical_section.h。然而,ciritcal_section.hpp包含了machine/winwdows_api.h,它看起来像这样(不必要的内容被省略):

tbb/machine/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在检查_WIN32_WINNT宏之前被包含。这个宏在sdkddkver.h中定义(该文件被包含在windows.h中),如果它还没有被定义的话(在我的情况下,它被设置为Win10):

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.hPoco/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头文件的版本。
现在设想一下,我们项目中的第一个include是来自Poco的头文件。这意味着_WIN32_WINNT将被设置为Windows XP,并且windows.h将包括Windows XP的Windows头文件(在我看来这已经是一个不好的迹象)。
但别担心,情况会变得更糟。
如果我们将include层次结构向上追溯一个级别,就会到达Poco/Platform_WIN32.hPoco/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。我不知道为什么要这样做,也许有一个很好的原因,我不知道。
如果我们现在看一下最上面的最小示例,我们会发现Poco在TBB之前被包括。接下来会发生以下情况:
  1. 包括Poco头文件
  2. _WIN32_WINNT设置为Windows XP
  3. 包括Windows头文件(由于2),使用Windows XP版本
  4. _WIN32_WINNT重置为Windows 8.1
  5. 包括TBB头文件(Windows头文件已经被包括,所以TBB不需要在tbb/windows_api.h中再次包括它们)
  6. TBB通过_WIN32_WINNT检查Windows版本,并识别为Windows 8.1(由Poco设置)
  7. TBB认为InitializeCriticalSectionEx被定义了,因为Windows版本是8.1(或者是吗?Poco说:get rekt),而自Windows Vista以来就定义了InitializeCriticalSectionEx
  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正在处理这个问题。 更新可以在这里找到。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接