diff --git a/doc/corrade-changelog.dox b/doc/corrade-changelog.dox index 16456f6df..cad989553 100644 --- a/doc/corrade-changelog.dox +++ b/doc/corrade-changelog.dox @@ -186,8 +186,6 @@ namespace Corrade { - Global state used by @ref PluginManager::Manager, @ref Utility::Debug and @ref Utility::Resource internals is no longer duplicated when Corrade is built statically and linked to more than one dynamic library or executable. - Currently that holds only for Linux and macOS, Windows support will come - next. - @ref PluginManager::Manager and @ref Utility::Debug now have the global state thread-local when the @ref CORRADE_BUILD_MULTITHREADED option is enabled (see also [mosra/corrade#65](https://github.com/mosra/corrade/issues/65)) diff --git a/src/Corrade/PluginManager/AbstractManager.cpp b/src/Corrade/PluginManager/AbstractManager.cpp index f4dad9ec3..f937ff9c8 100644 --- a/src/Corrade/PluginManager/AbstractManager.cpp +++ b/src/Corrade/PluginManager/AbstractManager.cpp @@ -64,6 +64,10 @@ using Corrade::Utility::Unicode::widen; #include "Corrade/PluginManager/configure.h" #endif +#if defined(CORRADE_TARGET_WINDOWS) && defined(CORRADE_BUILD_STATIC) && !defined(CORRADE_TARGET_WINDOWS_RT) +#include "Corrade/Utility/Implementation/WindowsWeakSymbol.h" +#endif + using namespace Corrade::Utility; namespace Corrade { namespace PluginManager { @@ -131,17 +135,17 @@ struct AbstractManager::State { const int AbstractManager::Version = CORRADE_PLUGIN_VERSION; -#ifndef CORRADE_BUILD_STATIC -/* (Of course) can't be in an unnamed namespace in order to export it below */ +#if !defined(CORRADE_BUILD_STATIC) || defined(CORRADE_TARGET_WINDOWS) +/* (Of course) can't be in an unnamed namespace in order to export it below + (except for Windows, where we do extern "C" so this doesn't matter) */ namespace { #endif -#if defined(CORRADE_BUILD_STATIC) && !defined(CORRADE_TARGET_WINDOWS) +#if !defined(CORRADE_BUILD_STATIC) || (defined(CORRADE_BUILD_STATIC) && !defined(CORRADE_TARGET_WINDOWS)) || defined(CORRADE_TARGET_WINDOWS_RT) +#ifdef CORRADE_BUILD_STATIC /* On static builds that get linked to multiple shared libraries and then used in a single app we want to ensure there's just one global symbol. On Linux - it's apparently enough to just export, macOS needs the weak attribute. - Windows not handled yet, as it needs a workaround using DllMain() and - GetProcAddress(). */ + it's apparently enough to just export, macOS needs the weak attribute. */ CORRADE_VISIBILITY_EXPORT #ifdef __GNUC__ __attribute__((weak)) @@ -156,6 +160,11 @@ CORRADE_VISIBILITY_EXPORT static plugin initializers are executed, which means we don't hit any static initialization order fiasco. */ Implementation::StaticPlugin* globalStaticPlugins = nullptr; +#else +/* On Windows the symbol is exported unmangled and then fetched via + GetProcAddress() to emulate weak linking. */ +extern "C" CORRADE_VISIBILITY_EXPORT Implementation::StaticPlugin* corradePluginManagerUniqueGlobalStaticPlugins = nullptr; +#endif #ifdef CORRADE_BUILD_MULTITHREADED CORRADE_THREAD_LOCAL @@ -165,7 +174,7 @@ CORRADE_THREAD_LOCAL static plugin registration *can't be* thread-local (it's written to from one thread but then only read from multiple) while the global plugin storage *has to be* thread-local as there's dependency management, refcounting etc. - going on. */ + going on. Windows handled differently below. */ CORRADE_VISIBILITY_EXPORT #ifdef __GNUC__ __attribute__((weak)) @@ -187,10 +196,46 @@ CORRADE_VISIBILITY_EXPORT initialization order fiasco. */ std::map* globalPlugins = nullptr; -#ifndef CORRADE_BUILD_STATIC +#if !defined(CORRADE_BUILD_STATIC) || defined(CORRADE_TARGET_WINDOWS) } #endif +/* Windows can't have a symbol both thread-local and exported, moreover there + isn't any concept of weak symbols. Exporting thread-local symbols can be + worked around by exporting a function that then returns a reference to a + non-exported thread-local symbol; and finally GetProcAddress() on + GetModuleHandle(nullptr) "emulates" the weak linking as it's guaranteed to + pick up the same symbol of the final exe independently of the DLL it was + called from. To avoid #ifdef hell in code below, the globalStaticPlugins / + globalPlugins are redefined to return a value from this uniqueness-ensuring + function. */ +#if defined(CORRADE_TARGET_WINDOWS) && defined(CORRADE_BUILD_STATIC) && !defined(CORRADE_TARGET_WINDOWS_RT) +extern "C" CORRADE_VISIBILITY_EXPORT std::map*& corradePluginManagerUniqueGlobalPlugins() { + return globalPlugins; +} + +namespace { + +Implementation::StaticPlugin*& windowsGlobalStaticPlugins() { + /* A function-local static to ensure it's only initialized once without any + race conditions among threads */ + static Implementation::StaticPlugin** const uniqueGlobals = reinterpret_cast(Utility::Implementation::windowsWeakSymbol("corradePluginManagerUniqueGlobalStaticPlugins")); + return *uniqueGlobals; +} + +std::map*& windowsGlobalPlugins() { + /* A function-local static to ensure it's only initialized once without any + race conditions among threads */ + static std::map*&(*const uniqueGlobals)() = reinterpret_cast*&(*)()>(Utility::Implementation::windowsWeakSymbol("corradePluginManagerUniqueGlobalPlugins")); + return uniqueGlobals(); +} + +} + +#define globalStaticPlugins windowsGlobalStaticPlugins() +#define globalPlugins windowsGlobalPlugins() +#endif + static_assert(std::is_pod::value, "static plugins shouldn't cause any global initialization / finalization to happen on their own"); diff --git a/src/Corrade/PluginManager/Test/GlobalStateAcrossLibrariesTest.cpp b/src/Corrade/PluginManager/Test/GlobalStateAcrossLibrariesTest.cpp index 5f33981ed..367bbba6a 100644 --- a/src/Corrade/PluginManager/Test/GlobalStateAcrossLibrariesTest.cpp +++ b/src/Corrade/PluginManager/Test/GlobalStateAcrossLibrariesTest.cpp @@ -51,12 +51,7 @@ void GlobalStateAcrossLibrariesTest::test() { /* Avoid accidentally loading the dynamic plugins as well */ PluginManager::Manager manager{"nonexistent"}; - { - #ifdef CORRADE_TARGET_WINDOWS - CORRADE_EXPECT_FAIL("Deduplication of global data across shared libraries isn't implemented on Windows yet."); - #endif - CORRADE_COMPARE(manager.pluginList(), std::vector{"Canary"}); - } + CORRADE_COMPARE(manager.pluginList(), std::vector{"Canary"}); } }}}} diff --git a/src/Corrade/PluginManager/Test/ManagerInitFiniTest.cpp b/src/Corrade/PluginManager/Test/ManagerInitFiniTest.cpp index 54ef1c90d..e4ec67e1a 100644 --- a/src/Corrade/PluginManager/Test/ManagerInitFiniTest.cpp +++ b/src/Corrade/PluginManager/Test/ManagerInitFiniTest.cpp @@ -85,23 +85,13 @@ void ManagerInitFiniTest::dynamicPlugin() { initialization is not called again. */ out.str({}); CORRADE_COMPARE(manager.load("InitFiniDynamic"), LoadState::Loaded); - { - #if defined(CORRADE_TARGET_WINDOWS) && defined(CORRADE_BUILD_STATIC) - CORRADE_EXPECT_FAIL("Deduplication of global data (Debug output redirection) across shared libraries isn't implemented on Windows yet."); - #endif - CORRADE_COMPARE(out.str(), "Dynamic plugin initialized\n"); - } + CORRADE_COMPARE(out.str(), "Dynamic plugin initialized\n"); /* Finalization is right before manager unloads them. Base finalization is not called yet. */ out.str({}); CORRADE_COMPARE(manager.unload("InitFiniDynamic"), LoadState::NotLoaded); - { - #if defined(CORRADE_TARGET_WINDOWS) && defined(CORRADE_BUILD_STATIC) - CORRADE_EXPECT_FAIL("Deduplication of global data (Debug output redirection) across shared libraries isn't implemented on Windows yet."); - #endif - CORRADE_COMPARE(out.str(), "Dynamic plugin finalized\n"); - } + CORRADE_COMPARE(out.str(), "Dynamic plugin finalized\n"); out.str({}); } diff --git a/src/Corrade/Utility/CMakeLists.txt b/src/Corrade/Utility/CMakeLists.txt index 5ea7901b6..fb5b22fa4 100644 --- a/src/Corrade/Utility/CMakeLists.txt +++ b/src/Corrade/Utility/CMakeLists.txt @@ -99,6 +99,12 @@ if(WITH_UTILITY) endif() endif() + # Functionality specific to static Windows builds + if(CORRADE_TARGET_WINDOWS AND NOT CORRADE_TARGET_WINDOWS_RT AND CORRADE_BUILD_STATIC) + list(APPEND CorradeUtility_SRCS Implementation/WindowsWeakSymbol.cpp) + list(APPEND CorradeUtility_PRIVATE_HEADERS Implementation/WindowsWeakSymbol.h) + endif() + # Objects shared between main and test library add_library(CorradeUtilityObjects OBJECT ${CorradeUtility_SRCS} @@ -189,6 +195,13 @@ if(NOT CMAKE_CROSSCOMPILING) if(CORRADE_TARGET_WINDOWS) # Needed for dealing with the design failure that's called "Unicode WINAPI" list(APPEND CorradeUtilityRc_SRCS Unicode.cpp) + + # Functionality specific to static Windows builds. Not really needed + # for corrade-rc as it doesn't load any plugins yet but easier than + # having a special handling. + if(NOT CORRADE_TARGET_WINDOWS_RT) + list(APPEND CorradeUtilityRc_SRCS Implementation/WindowsWeakSymbol.cpp) + endif() endif() # Having a tool used during a build depend on a shared library brings a lot diff --git a/src/Corrade/Utility/Debug.cpp b/src/Corrade/Utility/Debug.cpp index 17a331291..b7d4f7580 100644 --- a/src/Corrade/Utility/Debug.cpp +++ b/src/Corrade/Utility/Debug.cpp @@ -56,6 +56,10 @@ #include "Corrade/Containers/EnumSet.hpp" #include "Corrade/Utility/DebugStl.h" +#if defined(CORRADE_TARGET_WINDOWS) && defined(CORRADE_BUILD_STATIC) && !defined(CORRADE_TARGET_WINDOWS_RT) +#include "Corrade/Utility/Implementation/WindowsWeakSymbol.h" +#endif + namespace Corrade { namespace Utility { namespace { @@ -78,8 +82,10 @@ HANDLE streamOutputHandle(const std::ostream* s) { } -#ifndef CORRADE_BUILD_STATIC -/* (Of course) can't be in an unnamed namespace in order to export it below */ +#if !defined(CORRADE_BUILD_STATIC) || defined(CORRADE_TARGET_WINDOWS) +/* (Of course) can't be in an unnamed namespace in order to export it below + (except for Windows, where we do extern "C" so this doesn't matter, but we + don't want to expose the DebugGlobals symbols if not needed) */ namespace { #endif @@ -98,8 +104,7 @@ CORRADE_THREAD_LOCAL /* On static builds that get linked to multiple shared libraries and then used in a single app we want to ensure there's just one global symbol. On Linux it's apparently enough to just export, macOS needs the weak attribute. - Windows not handled yet, as it needs a workaround using DllMain() and - GetProcAddress(). */ + Windows handled differently below. */ CORRADE_VISIBILITY_EXPORT #ifdef __GNUC__ __attribute__((weak)) @@ -114,8 +119,35 @@ DebugGlobals debugGlobals{ #endif }; -#ifndef CORRADE_BUILD_STATIC +#if !defined(CORRADE_BUILD_STATIC) || defined(CORRADE_TARGET_WINDOWS) +} +#endif + +/* Windows can't have a symbol both thread-local and exported, moreover there + isn't any concept of weak symbols. Exporting thread-local symbols can be + worked around by exporting a function that then returns a reference to a + non-exported thread-local symbol; and finally GetProcAddress() on + GetModuleHandle(nullptr) "emulates" the weak linking as it's guaranteed to + pick up the same symbol of the final exe independently of the DLL it was + called from. To avoid #ifdef hell in code below, the debugGlobals are + redefined to return a value from this uniqueness-ensuring function. */ +#if defined(CORRADE_TARGET_WINDOWS) && defined(CORRADE_BUILD_STATIC) && !defined(CORRADE_TARGET_WINDOWS_RT) +extern "C" CORRADE_VISIBILITY_EXPORT DebugGlobals& corradeUtilityUniqueDebugGlobals() { + return debugGlobals; +} + +namespace { + +DebugGlobals& windowsDebugGlobals() { + /* A function-local static to ensure it's only initialized once without any + race conditions among threads */ + static DebugGlobals&(*const uniqueGlobals)() = reinterpret_cast(Implementation::windowsWeakSymbol("corradeUtilityUniqueDebugGlobals")); + return uniqueGlobals(); } + +} + +#define debugGlobals windowsDebugGlobals() #endif template Debug::Modifier Debug::colorInternal() { diff --git a/src/Corrade/Utility/Implementation/WindowsWeakSymbol.cpp b/src/Corrade/Utility/Implementation/WindowsWeakSymbol.cpp new file mode 100644 index 000000000..226c28ae3 --- /dev/null +++ b/src/Corrade/Utility/Implementation/WindowsWeakSymbol.cpp @@ -0,0 +1,43 @@ +/* + This file is part of Corrade. + + Copyright © 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, + 2017, 2018, 2019 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "WindowsWeakSymbol.h" + +#include + +#define WIN32_LEAN_AND_MEAN 1 +#define VC_EXTRALEAN +#include + +namespace Corrade { namespace Utility { namespace Implementation { + +void* windowsWeakSymbol(const char* name) { + /* FARPROC?! I want either a function pointer or a variable pointer */ + void* address = reinterpret_cast(GetProcAddress(GetModuleHandleA(nullptr), name)); + CORRADE_INTERNAL_ASSERT(address); + return address; +} + +}}} diff --git a/src/Corrade/Utility/Implementation/WindowsWeakSymbol.h b/src/Corrade/Utility/Implementation/WindowsWeakSymbol.h new file mode 100644 index 000000000..8799eeb11 --- /dev/null +++ b/src/Corrade/Utility/Implementation/WindowsWeakSymbol.h @@ -0,0 +1,40 @@ +#ifndef Corrade_Utility_WindowsWeakSymbol_h +#define Corrade_Utility_WindowsWeakSymbol_h +/* + This file is part of Corrade. + + Copyright © 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, + 2017, 2018, 2019 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include + +#if !defined(CORRADE_TARGET_WINDOWS) || !defined(CORRADE_BUILD_STATIC) || defined(CORRADE_TARGET_WINDOWS_RT) +#error this file is only meant to be used in non-RT Windows static builds +#endif + +namespace Corrade { namespace Utility { namespace Implementation { + +void* windowsWeakSymbol(const char* name); + +}}} + +#endif diff --git a/src/Corrade/Utility/Resource.cpp b/src/Corrade/Utility/Resource.cpp index dd9577de0..13b4839b5 100644 --- a/src/Corrade/Utility/Resource.cpp +++ b/src/Corrade/Utility/Resource.cpp @@ -43,10 +43,16 @@ #include "Corrade/Utility/FormatStl.h" #include "Corrade/Utility/Implementation/Resource.h" +#if defined(CORRADE_TARGET_WINDOWS) && defined(CORRADE_BUILD_STATIC) && !defined(CORRADE_TARGET_WINDOWS_RT) +#include "Corrade/Utility/Implementation/WindowsWeakSymbol.h" +#endif + namespace Corrade { namespace Utility { -#ifndef CORRADE_BUILD_STATIC -/* (Of course) can't be in an unnamed namespace in order to export it below */ +#if !defined(CORRADE_BUILD_STATIC) || defined(CORRADE_TARGET_WINDOWS) +/* (Of course) can't be in an unnamed namespace in order to export it below + (except for Windows, where we do extern "C" so this doesn't matter, but we + don't want to expose the ResourceGlobals symbols if not needed) */ namespace { #endif @@ -61,12 +67,11 @@ struct ResourceGlobals { std::map* overrideGroups; }; -#if defined(CORRADE_BUILD_STATIC) && !defined(CORRADE_TARGET_WINDOWS) +#if !defined(CORRADE_BUILD_STATIC) || (defined(CORRADE_BUILD_STATIC) && !defined(CORRADE_TARGET_WINDOWS)) || defined(CORRADE_TARGET_WINDOWS_RT) +#ifdef CORRADE_BUILD_STATIC /* On static builds that get linked to multiple shared libraries and then used in a single app we want to ensure there's just one global symbol. On Linux - it's apparently enough to just export, macOS needs the weak attribute. - Windows not handled yet, as it needs a workaround using DllMain() and - GetProcAddress(). */ + it's apparently enough to just export, macOS needs the weak attribute. */ CORRADE_VISIBILITY_EXPORT #ifdef __GNUC__ __attribute__((weak)) @@ -78,11 +83,36 @@ CORRADE_VISIBILITY_EXPORT resource initializers are executed, which means we don't hit any static initialization order fiasco. */ ResourceGlobals resourceGlobals{nullptr, nullptr}; +#else +/* On Windows the symbol is exported unmangled and then fetched via + GetProcAddress() to emulate weak linking. */ +extern "C" CORRADE_VISIBILITY_EXPORT ResourceGlobals corradeUtilityUniqueWindowsResourceGlobals{nullptr, nullptr}; +#endif -#ifndef CORRADE_BUILD_STATIC +#if !defined(CORRADE_BUILD_STATIC) || defined(CORRADE_TARGET_WINDOWS) } #endif +/* Windows don't have any concept of weak symbols, instead GetProcAddress() on + GetModuleHandle(nullptr) "emulates" the weak linking as it's guaranteed to + pick up the same symbol of the final exe independently of the DLL it was + called from. To avoid #ifdef hell in code below, the resourceGlobals are + redefined to return a value from this uniqueness-ensuring function. */ +#if defined(CORRADE_TARGET_WINDOWS) && defined(CORRADE_BUILD_STATIC) && !defined(CORRADE_TARGET_WINDOWS_RT) +namespace { + +ResourceGlobals& windowsResourceGlobals() { + /* A function-local static to ensure it's only initialized once without any + race conditions among threads */ + static ResourceGlobals* const uniqueGlobals = reinterpret_cast(Implementation::windowsWeakSymbol("corradeUtilityUniqueWindowsResourceGlobals")); + return *uniqueGlobals; +} + +} + +#define resourceGlobals windowsResourceGlobals() +#endif + struct Resource::OverrideData { const Configuration conf; std::map> data; diff --git a/src/Corrade/Utility/Test/GlobalStateAcrossLibrariesTest.cpp b/src/Corrade/Utility/Test/GlobalStateAcrossLibrariesTest.cpp index cf7a6e9cb..7f9ba048b 100644 --- a/src/Corrade/Utility/Test/GlobalStateAcrossLibrariesTest.cpp +++ b/src/Corrade/Utility/Test/GlobalStateAcrossLibrariesTest.cpp @@ -52,9 +52,6 @@ void GlobalStateAcrossLibrariesTest::debug() { { Debug redirectOutput{&out}; - #ifdef CORRADE_TARGET_WINDOWS - CORRADE_EXPECT_FAIL("Deduplication of global data across shared libraries isn't implemented on Windows yet."); - #endif CORRADE_COMPARE(debugOutputFromALibrary(), &out); } @@ -65,13 +62,7 @@ void GlobalStateAcrossLibrariesTest::resource() { /* The resource is complied into the the library, but the executable should see it too */ CORRADE_VERIFY(libraryHasATestResourceGroup()); - - { - #ifdef CORRADE_TARGET_WINDOWS - CORRADE_EXPECT_FAIL("Deduplication of global data across shared libraries isn't implemented on Windows yet."); - #endif - CORRADE_VERIFY(Utility::Resource::hasGroup("test")); - } + CORRADE_VERIFY(Utility::Resource::hasGroup("test")); } }}}}