Intel TBB — «InitializeCriticalSectionEx»: идентификатор не найден, ошибка компилятора

У меня есть проект VS (C++), основанный на OpenCV и TBB, поэтому я создал листы свойств для каждой библиотеки и включил их в проект. Все заработало, код скомпилировался.

Вчера я начал использовать диспетчер пакетов vcpkg. Я установил OpenCV и TBB через vcpkg, и все заработало. Я создал пустой проект, включил заголовки обоих и проверил, работают ли новые скомпилированные библиотеки. Убедившись в этом, я вернулся к своему основному проекту и удалил листы свойств, чтобы использовать библиотеки из vcpkg. Я никак не менял код с момента последней успешной компиляции.

Но когда я пытаюсь скомпилировать код, я получаю эту ошибку два раза (в main.cpp и в подмодуле)

tbb\critical_section.h(53): ошибка C3861: «InitializeCriticalSectionEx»: идентификатор не найден

Кто-нибудь знает, что здесь происходит или почему возникает эта ошибка?

Обновлять

Я сам нашел ошибку. Я добавляю тег poco-libraries, потому что на самом деле это конфликт между TBB и Poco.


person Timo    schedule 26.04.2018    source источник


Ответы (1)


Я нашел источник проблемы, и на самом деле он не имеет ничего общего с TBB, а с библиотекой Poco.

Рассмотрим минимальный пример:

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

void main()
{   
}

Это вызовет ошибку компилятора.

Отслеживание пути

При включении tbb.h critical_section.h включается в строку 51 файла tbb.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/UnWindows.h библиотеки Poco. При включении заголовка 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 (что, по моему мнению, уже является плохим признаком).

Но не волнуйтесь, дальше будет хуже.

Если мы проследим иерархию включения на один уровень выше, мы достигнем 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 говорит: получите 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).

Обновлять

Поко в теме. Обновления можно найти здесь.

person Timo    schedule 26.04.2018