Skip to content

Commit

Permalink
Implement globals shared across DLLs on Windows.
Browse files Browse the repository at this point in the history
In the end was less painful than originally anticipated.
  • Loading branch information
mosra committed Oct 18, 2019
1 parent ed16b09 commit 7b98f19
Show file tree
Hide file tree
Showing 10 changed files with 227 additions and 50 deletions.
2 changes: 0 additions & 2 deletions doc/corrade-changelog.dox
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
61 changes: 53 additions & 8 deletions src/Corrade/PluginManager/AbstractManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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))
Expand All @@ -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
Expand All @@ -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))
Expand All @@ -187,10 +196,46 @@ CORRADE_VISIBILITY_EXPORT
initialization order fiasco. */
std::map<std::string, AbstractManager::Plugin*>* 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<std::string, AbstractManager::Plugin*>*& 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<Implementation::StaticPlugin**>(Utility::Implementation::windowsWeakSymbol("corradePluginManagerUniqueGlobalStaticPlugins"));
return *uniqueGlobals;
}

std::map<std::string, AbstractManager::Plugin*>*& windowsGlobalPlugins() {
/* A function-local static to ensure it's only initialized once without any
race conditions among threads */
static std::map<std::string, AbstractManager::Plugin*>*&(*const uniqueGlobals)() = reinterpret_cast<std::map<std::string, AbstractManager::Plugin*>*&(*)()>(Utility::Implementation::windowsWeakSymbol("corradePluginManagerUniqueGlobalPlugins"));
return uniqueGlobals();
}

}

#define globalStaticPlugins windowsGlobalStaticPlugins()
#define globalPlugins windowsGlobalPlugins()
#endif

static_assert(std::is_pod<Implementation::StaticPlugin>::value,
"static plugins shouldn't cause any global initialization / finalization to happen on their own");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,7 @@ void GlobalStateAcrossLibrariesTest::test() {

/* Avoid accidentally loading the dynamic plugins as well */
PluginManager::Manager<AbstractAnimal> 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<std::string>{"Canary"});
}
CORRADE_COMPARE(manager.pluginList(), std::vector<std::string>{"Canary"});
}

}}}}
Expand Down
14 changes: 2 additions & 12 deletions src/Corrade/PluginManager/Test/ManagerInitFiniTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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({});
}
Expand Down
13 changes: 13 additions & 0 deletions src/Corrade/Utility/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down Expand Up @@ -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
Expand Down
42 changes: 37 additions & 5 deletions src/Corrade/Utility/Debug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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

Expand All @@ -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))
Expand All @@ -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<DebugGlobals&(*)()>(Implementation::windowsWeakSymbol("corradeUtilityUniqueDebugGlobals"));
return uniqueGlobals();
}

}

#define debugGlobals windowsDebugGlobals()
#endif

template<Debug::Color c, bool bold> Debug::Modifier Debug::colorInternal() {
Expand Down
43 changes: 43 additions & 0 deletions src/Corrade/Utility/Implementation/WindowsWeakSymbol.cpp
Original file line number Diff line number Diff line change
@@ -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š <mosra@centrum.cz>
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 <Corrade/Utility/Assert.h>

#define WIN32_LEAN_AND_MEAN 1
#define VC_EXTRALEAN
#include <windows.h>

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<void*>(GetProcAddress(GetModuleHandleA(nullptr), name));
CORRADE_INTERNAL_ASSERT(address);
return address;
}

}}}
40 changes: 40 additions & 0 deletions src/Corrade/Utility/Implementation/WindowsWeakSymbol.h
Original file line number Diff line number Diff line change
@@ -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š <mosra@centrum.cz>
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 <Corrade/configure.h>

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

0 comments on commit 7b98f19

Please sign in to comment.