Search code examples
c++c++17tbbpoco-librariesvcpkg

Intel TBB - 'InitializeCriticalSectionEx': identifier not found compiler error


I have a VS (C++) project that relies on OpenCV and TBB, so I created property sheets for each library and included them in the project. Everything worked fine and the code compiled.

Yesterday, I have started using vcpkg package manager. I installed OpenCV and TBB via vcpkg and everything seemed to work. I created an empty project, included the headers of both and tested if the new compiled libraries work. After verifying that, I went back to my main project and removed the property sheets, so I can use the libraries from vcpkg. I did not change the code in any way since the last successful compilation.

But when I try to compile the code now I get this error two times (in main.cpp and in a submodule)

tbb\critical_section.h(53): error C3861: 'InitializeCriticalSectionEx': identifier not found

Does anybody know what is going on here or why this error occurs?

Update

I found the error myself. I'm adding the poco-libraries tag, because it's actually a conflict between TBB and Poco.


Solution

  • I found the source of the problem and it has actually nothing to do with TBB but with the Poco library.

    Consider the minimum example:

    #include <Poco/Poco.h>
    #include <tbb/tbb.h>
    
    void main()
    {   
    }
    

    This will throw an compiler error.

    Tracing down the path

    When including tbb.h, critical_section.h is included in line 51 of tbb.h. However, ciritcal_section.hpp includes machine/winwdows_api.h which looks like this (unnecessary stuff is cut out):

    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
    

    As you can see, windows.h is included before the check of the _WIN32_WINNT macro. This macro is defined in sdkddkver.h (which is included in windows.h), iff it's not already defined (in my case it's set to Win10):

    sdkddkver.h:

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

    In windows.h, the _WIN32_WINNT macro controls which version of the windows header files are actually included. If _WIN32_WINNT is set to an earlier version than Windows Vista, the function InitializeCriticalSectionEx is not defined.

    This issue is catched by machine/winwdows_api.h (as you can see in the code block of that file) by simply defining a macro InitializeCriticalSectionEx that calls an appropriate alternative function.

    So far so good.

    The problem

    The root of all evil lies in Poco/UnWindows.h of the Poco library. When including a poco header, at some point UnWindows.h will be included.

    Poco/UnWindows.h (shortened):

    #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>
    

    The preprocessor checks, if _WIN32_WINNT is already defined, and if not, sets it to 0x0501 which is Windows XP. After that, windows.h is included. In the previous chapter I mentioned that _WIN32_WINNT controls which version of the windows header files are actually included.

    Now imagine, the very first include in our project is a header from Poco. This means, that _WIN32_WINNT will be set to Windows XP and windows.h will include the windows headers of Windows XP (which imo is already a bad sign).

    But don't worry, it gets worse.

    If we trace the include hierarchy one level up, we reach Poco/Platform_WIN32.h.

    Poco/Platform_WIN32.h (shortened):

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

    Funny, isn't it? First, it includes UnWindows.h, which sets _WIN32_WINNT and causes Windows XP headers to be included, and next it redefines _WIN32_WINNT to be Windows 8.1. I have no clue why it does that, maybe there is a good reason, idk.

    If we now look at the minimum example at the very top we see that Poco is included before TBB. What now happens is:

    1. Include Poco headers
    2. Set _WIN32_WINNT to Windows XP
    3. Include windows headers (Windows XP version, because of 2)
    4. Reset _WIN32_WINNT to Windows 8.1
    5. Include TBB headers (windows headers are already included, so TBB doesn't need to include them again in tbb/windows_api.h)
    6. TBB checks the windows version via _WIN32_WINNT and recognizes Windows 8.1 (as set by Poco)
    7. TBB thinks InitializeCriticalSectionEx is defined, because the Windows version is 8.1 (or is it? Poco says: get rekt) and InitializeCriticalSectionEx is defined since Windows Vista.
    8. Unfortunately Poco ensured that the Windows XP headers are loaded, so compiler says: no.

    The solution

    Either include windows.h yourself beforehand, or set _WIN32_WINNT yourself beforehand:

    #define _WIN32_WINNT 0x0A00    // either this
    #include <Windows.h>           // or this
    
    #include <Poco/Poco.h>
    #include <tbb/tbb.h>
    
    void main()
    {   
    }
    

    Maybe someone of the Poco contributors can clarify some things here. The Poco version is 1.8.1-1 built with x64 (via vcpkg).

    Update

    Poco is on the issue. Updates can be found here.