diff --git a/CMakeLists.txt b/CMakeLists.txt index 623ee658..d58da081 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -17,6 +17,10 @@ endif() option(EQ_BUILD_TESTS "Build the base unit tests" ${eq_is_top_level}) option(EQ_USE_STD_ATOMIC "Build base::Atomic on top of std::atomic" OFF) +# A component build on Windows needs base as one DLL: PE gives every DLL its +# own copy of a static library's globals, so the Option/Feature registries and +# every other base singleton would otherwise split per module. +option(EQ_BASE_SHARED "Build base as a shared library" OFF) # The base headers use C++23 (deducing this, constexpr extensions). if(NOT DEFINED CMAKE_CXX_STANDARD OR CMAKE_CXX_STANDARD LESS 23) @@ -46,7 +50,24 @@ foreach(platform ${eq_excluded_platforms}) list(FILTER EQ_BASE_SOURCES EXCLUDE REGEX "/${platform}/") endforeach() -add_library(eq_base STATIC ${EQ_BASE_SOURCES}) +if(EQ_BASE_SHARED) + add_library(eq_base SHARED ${EQ_BASE_SOURCES}) + target_compile_definitions(eq_base PUBLIC BUILDING_DLL) + # BASE_EXPORT marks only part of the API; export the rest of the functions + # too. Data crosses the boundary only through BASE_EXPORT (dllimport) or + # accessors. + set_target_properties(eq_base PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON) + # That property only drives MSVC-style linkers; GNU ld's equivalent. Without + # it, ld exports only the dllexport-marked symbols once any exist. + # --exclude-libs keeps the statically linked runtime (libgcc's unwinder, + # libstdc++) out of the export table, where it would clash with every + # consumer's own copy. + if(MINGW) + target_link_options(eq_base PRIVATE -Wl,--export-all-symbols,--exclude-libs,ALL) + endif() +else() + add_library(eq_base STATIC ${EQ_BASE_SOURCES}) +endif() add_library(equilibrium::base ALIAS eq_base) target_include_directories(eq_base PUBLIC "${EQ_ROOT}" "${EQ_ROOT}/base") target_compile_definitions(eq_base PRIVATE diff --git a/base/containers/init_chain.h b/base/containers/init_chain.h index 5e19ab67..3bc54826 100644 --- a/base/containers/init_chain.h +++ b/base/containers/init_chain.h @@ -14,8 +14,12 @@ // visibility makes the root one symbol per process (ELF unique binding), so // all objects share one chain. An instantiation is only as visible as its // template argument, so every item type (OptionBase, Feature) carries the same -// annotation. PE has no such binding; there base has to be linked as a DLL for -// the same guarantee. +// annotation. +// +// PE has no such binding: every DLL keeps its own copy of an inline static. +// There the root lives out of line, in TItem::ChainRoot(), which base defines +// once; with base built as a DLL (EQ_BASE_SHARED) every module reaches the +// same one. #if defined(__GNUC__) || defined(__clang__) #define BASE_SHARED_REGISTRY __attribute__((visibility("default"))) #else @@ -47,10 +51,14 @@ class BASE_SHARED_REGISTRY InitChain { InitChain(const InitChain&) = delete; InitChain(InitChain&&) = delete; +#if defined(_WIN32) + STRONG_INLINE static InitChain*& root() noexcept { return TItem::ChainRoot(); } +#else STRONG_INLINE static InitChain*& root() noexcept { static InitChain* root{nullptr}; return root; } +#endif template STRONG_INLINE static mem_size VisitAll(const TFunctor functor, diff --git a/base/containers/init_chain_win.cc b/base/containers/init_chain_win.cc new file mode 100644 index 00000000..91c4aec5 --- /dev/null +++ b/base/containers/init_chain_win.cc @@ -0,0 +1,21 @@ +// Copyright (C) 2026 Vincent Hengel. +// For licensing information see LICENSE at the root of this distribution. +// The registry roots, defined once in base so every DLL shares them +// (base/containers/init_chain.h explains why PE needs this). + +#include +#include + +namespace base { + +InitChain*& Feature::ChainRoot() noexcept { + static InitChain* root{nullptr}; + return root; +} + +InitChain*& OptionBase::ChainRoot() noexcept { + static InitChain* root{nullptr}; + return root; +} + +} // namespace base diff --git a/base/debugging/debugging_win.cc b/base/debugging/debugging_win.cc deleted file mode 100644 index 7b1b9f94..00000000 --- a/base/debugging/debugging_win.cc +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (C) 2026 Vincent Hengel. -// For licensing information see LICENSE at the root of this distribution. - -#include "debugging.h" - -#if defined(OS_WIN) -#include -#endif - -namespace base { - -bool IsDebuggerAttached() { -#if defined(OS_WIN) - return ::IsDebuggerPresent() != 0; -#else - return false; -#endif -} - -} // namespace base diff --git a/base/feature.h b/base/feature.h index 355ad90f..11f04a3e 100644 --- a/base/feature.h +++ b/base/feature.h @@ -23,6 +23,11 @@ struct BASE_EXPORT BASE_SHARED_REGISTRY Feature : InitChain { inline operator bool() const { return enabled; } +#if defined(_WIN32) + // The chain root, out of line so every DLL shares base's (init_chain.h). + static InitChain*& ChainRoot() noexcept; +#endif + const char* name; Flags flags; bool enabled; diff --git a/base/option.h b/base/option.h index 6c84a083..6c4162e8 100644 --- a/base/option.h +++ b/base/option.h @@ -97,6 +97,11 @@ class BASE_EXPORT BASE_SHARED_REGISTRY OptionBase : public InitChain // Returns the number of characters written, null terminator excluded. virtual mem_size FormatValue(char* buffer, mem_size buffer_size) const = 0; +#if defined(_WIN32) + // The chain root, out of line so every DLL shares base's (init_chain.h). + static InitChain*& ChainRoot() noexcept; +#endif + const char* name() const { return name_; } const char* env() const { return env_; } const char* desc() const { return desc_; } diff --git a/base/win/minwin.h b/base/win/minwin.h index 213be7d3..3e54d59a 100644 --- a/base/win/minwin.h +++ b/base/win/minwin.h @@ -8,6 +8,15 @@ // and skip the hand-rolled forward declarations entirely. #if defined(__MINGW32__) || defined(__MINGW64__) #include +// The MSVC path below never includes , so no includer of this +// header sees its macros. Match that for the function-like ones that rename +// ordinary identifiers (a GetProp method becomes GetPropW in the files that +// see this header and stays GetProp in the ones that do not, and the link +// fails). Code that wants the Win32 function calls the W name. +#undef GetProp +#undef SetProp +#undef RemoveProp +#undef MemoryBarrier #else // MSVC: forward-declare instead of paying for