Skip to content

Commit 6afb1f0

Browse files
jll63claude
andcommitted
Add exe-owned registry variants to dynamic_loading test
Add a second axis to the dynamic_loading test variants: which module owns and exports the single shared registry-state symbol. Selected at compile time via the REGISTRY_IN_EXE macro -- registry.cpp exports the state unless REGISTRY_IN_EXE is set, in which case main.cpp (the executable) exports it and the shared libraries import it. CMake builds all four variants ({dll,exe}-owned x {default,indirect}); the exe-owned ones link the libraries against the executable's import library (ENABLE_EXPORTS). b2 builds only the two dll-owned variants: it does not expose an executable's import library to dependent DLLs, so the reverse linkage cannot be expressed on Windows. Also rename the default-registry variant suffix from "" to "_default". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 21b3afb commit 6afb1f0

4 files changed

Lines changed: 101 additions & 42 deletions

File tree

test/dynamic_loading/CMakeLists.txt

Lines changed: 62 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -10,44 +10,72 @@ else()
1010
endif()
1111

1212
# Build one set of {registry, method, overrider} shared libraries plus a test
13-
# executable. Each variant compiles the very same sources but with a different
14-
# default registry, selected by extra preprocessor definitions (ARGN) applied to
15-
# every target so all modules agree (ODR). Targets are suffixed and outputs go
16-
# to a per-variant directory so that main.cpp, which locates its sibling
17-
# libraries by filename fragment at runtime, never picks up the other variant.
18-
function(boost_openmethod_add_dynamic_loading_variant suffix)
13+
# executable. Each variant compiles the very same sources but with two knobs:
14+
#
15+
# owner - which module owns and exports the single shared registry-state
16+
# symbol: "dll" (lib_registry owns it; method/overrider/exe import it)
17+
# or "exe" (the executable owns it; all three libraries import it).
18+
# In the "exe" case the libraries must link *against* the executable
19+
# (reverse linkage); ENABLE_EXPORTS makes the exe produce the import
20+
# library / -rdynamic exports needed for that.
21+
# ARGN - extra preprocessor definitions (e.g. the default registry) applied
22+
# to every target so all modules agree (ODR).
23+
#
24+
# Targets are suffixed and outputs go to a per-variant directory so that
25+
# main.cpp, which locates its sibling libraries by filename fragment at runtime,
26+
# never picks up another variant's libraries.
27+
function(boost_openmethod_add_dynamic_loading_variant suffix owner)
1928
set(extra_defs ${ARGN})
2029
set(reg boost_openmethod-dl_test_registry${suffix})
2130
set(meth boost_openmethod-dl_test_method${suffix})
2231
set(ovr boost_openmethod-dl_test_overrider${suffix})
2332
set(exe boost_openmethod-test_dynamic_loading${suffix})
2433
set(outdir "${CMAKE_CURRENT_BINARY_DIR}/dynamic_loading${suffix}")
2534

26-
# lib_registry: exports the registry's static policy state
2735
add_library(${reg} SHARED registry.cpp)
28-
target_link_libraries(${reg} PUBLIC Boost::openmethod PRIVATE Boost::dll)
29-
30-
# lib_method: exports the speak method; imports registry state from lib_registry
3136
add_library(${meth} SHARED method.cpp)
32-
target_link_libraries(${meth} PUBLIC Boost::openmethod ${reg} PRIVATE Boost::dll)
33-
34-
# lib_overrider: adds a Dog overrider; imported at runtime via Boost.DLL
3537
add_library(${ovr} SHARED overrider.cpp)
36-
target_link_libraries(${ovr} PRIVATE Boost::openmethod ${meth} Boost::dll)
37-
38-
# Test executable: links against lib_method (and lib_registry transitively);
39-
# dynamically loads lib_overrider at runtime.
4038
add_executable(${exe} main.cpp)
41-
target_link_libraries(${exe}
42-
PUBLIC Boost::openmethod ${reg} ${meth}
43-
PRIVATE Boost::openmethod Boost::unit_test_framework Boost::dll)
44-
add_dependencies(${exe} ${reg} ${meth} ${ovr})
39+
40+
# ENABLE_EXPORTS must be set before any library links against the executable
41+
# (the exe-owned case below), as CMake validates that at the
42+
# target_link_libraries call. The properties are (re)applied to every target
43+
# in the foreach further down.
44+
set_target_properties(${exe} PROPERTIES ENABLE_EXPORTS ON)
45+
46+
if (owner STREQUAL "exe")
47+
# The executable owns and exports the registry state; the libraries
48+
# import it and therefore link against the executable.
49+
list(APPEND extra_defs REGISTRY_IN_EXE)
50+
target_link_libraries(${reg} PUBLIC Boost::openmethod ${exe} PRIVATE Boost::dll)
51+
target_link_libraries(${meth} PUBLIC Boost::openmethod ${exe} PRIVATE Boost::dll)
52+
target_link_libraries(${ovr} PRIVATE Boost::openmethod ${meth} ${exe} Boost::dll)
53+
target_link_libraries(${exe}
54+
PRIVATE Boost::openmethod Boost::unit_test_framework Boost::dll)
55+
else()
56+
# lib_registry owns and exports the registry state; method/overrider/exe
57+
# import it. lib_method links lib_registry; the exe links both and loads
58+
# lib_overrider at runtime.
59+
target_link_libraries(${reg} PUBLIC Boost::openmethod PRIVATE Boost::dll)
60+
target_link_libraries(${meth} PUBLIC Boost::openmethod ${reg} PRIVATE Boost::dll)
61+
target_link_libraries(${ovr} PRIVATE Boost::openmethod ${meth} Boost::dll)
62+
target_link_libraries(${exe}
63+
PUBLIC Boost::openmethod ${reg} ${meth}
64+
PRIVATE Boost::openmethod Boost::unit_test_framework Boost::dll)
65+
# The exe links/loads the libraries, so build them first.
66+
add_dependencies(${exe} ${reg} ${meth} ${ovr})
67+
endif()
68+
# In the exe-owned case the libraries link against the exe, so the build
69+
# order is the reverse (handled by the link dependency); we must not add
70+
# exe -> libraries here or we'd form a cycle. Either way, building `tests`
71+
# builds every module of the variant.
4572

4673
# Put all of this variant's outputs in one directory so Boost.DLL can locate
4774
# the shared libraries relative to the test executable at runtime.
48-
# ENABLE_EXPORTS is required so that symbols from each shared library are
49-
# visible to other shared libraries loaded at runtime (e.g. the overrider
50-
# can resolve symbols from the method/registry libraries).
75+
# ENABLE_EXPORTS is required so that symbols from each module are visible to
76+
# the shared libraries loaded at runtime (e.g. the overrider can resolve
77+
# symbols from the method/registry/exe, and an exe-owned registry state can
78+
# be imported by the dlopen'd libraries).
5179
# CXX_VISIBILITY_PRESET must be "default" so that the template static
5280
# variables used as the shared registry state are exported with DEFAULT ELF
5381
# visibility. With hidden visibility (as set by BoostRoot.cmake in the
@@ -69,17 +97,20 @@ function(boost_openmethod_add_dynamic_loading_variant suffix)
6997
endforeach()
7098

7199
if (TARGET tests)
72-
add_dependencies(tests ${exe})
100+
add_dependencies(tests ${reg} ${meth} ${ovr} ${exe})
73101
endif()
74102

75103
boost_openmethod_add_test(${exe})
76104
endfunction()
77105

78-
# Variant 1: the default registry.
79-
boost_openmethod_add_dynamic_loading_variant("")
80-
81-
# Variant 2: indirect_registry (default_registry + indirect_vptr policy),
82-
# selected via BOOST_OPENMETHOD_DEFAULT_REGISTRY.
106+
# Four variants: {registry owned by dll, by exe} x {default, indirect} registry.
107+
# indirect_registry (default_registry + indirect_vptr) is selected via
108+
# BOOST_OPENMETHOD_DEFAULT_REGISTRY.
109+
boost_openmethod_add_dynamic_loading_variant("_default" dll)
110+
boost_openmethod_add_dynamic_loading_variant(
111+
"_indirect" dll
112+
BOOST_OPENMETHOD_DEFAULT_REGISTRY=::boost::openmethod::indirect_registry)
113+
boost_openmethod_add_dynamic_loading_variant("_exereg_default" exe)
83114
boost_openmethod_add_dynamic_loading_variant(
84-
"_indirect"
115+
"_exereg_indirect" exe
85116
BOOST_OPENMETHOD_DEFAULT_REGISTRY=::boost::openmethod::indirect_registry)

test/dynamic_loading/Jamfile

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,19 @@ local VISIBILITY = <local-visibility>global ;
4444
# default registry, selected by extra requirements (e.g. a <define>) applied to
4545
# every target so all modules agree (ODR). Targets are suffixed and outputs go
4646
# to a per-variant <location-prefix> so that main.cpp, which locates its sibling
47-
# libraries by filename fragment at runtime, never picks up the other variant.
47+
# libraries by filename fragment at runtime, never picks up another variant's
48+
# libraries.
49+
#
50+
# Here lib_registry always owns and exports the single shared registry-state
51+
# symbol; method/overrider/exe import it. The CMake build additionally has
52+
# "exe-owned" variants (the executable owns the state and the libraries link
53+
# against it). Those are CMake-only: b2 does not expose an executable's import
54+
# library to dependent DLLs, so the reverse linkage they need cannot be
55+
# expressed on Windows. See test/dynamic_loading/CMakeLists.txt.
4856
rule make-variant ( suffix : extra-req * )
4957
{
5058
# lib_registry: owns and exports the registry's static policy state.
51-
# registry.cpp defines EXPORT_REGISTRY and INCLUDED_FROM itself.
59+
# registry.cpp defines EXPORT_REGISTRY (since REGISTRY_IN_EXE is unset).
5260
lib boost_openmethod-dl_test_registry$(suffix)
5361
: registry.cpp
5462
: <link>shared
@@ -59,7 +67,7 @@ rule make-variant ( suffix : extra-req * )
5967
;
6068

6169
# lib_method: exports the speak() open-method; imports registry from
62-
# lib_registry. method.cpp defines EXPORT_METHOD and INCLUDED_FROM itself.
70+
# lib_registry.
6371
lib boost_openmethod-dl_test_method$(suffix)
6472
: method.cpp
6573
: <link>shared
@@ -71,7 +79,6 @@ rule make-variant ( suffix : extra-req * )
7179
;
7280

7381
# lib_overrider: adds the Dog overrider; dynamically loaded at runtime.
74-
# overrider.cpp defines INCLUDED_FROM itself.
7582
lib boost_openmethod-dl_test_overrider$(suffix)
7683
: overrider.cpp
7784
: <link>shared
@@ -85,8 +92,8 @@ rule make-variant ( suffix : extra-req * )
8592
;
8693

8794
# Test executable:
88-
# sources (linked) : main.cpp, lib_registry, unit_test_framework, boost_dll
89-
# runtime-loaded : lib_method, lib_overrider
95+
# sources (linked) : main.cpp, lib_registry, unit_test_framework, boost_dll
96+
# runtime-loaded : lib_method, lib_overrider
9097
# All shared libraries are placed alongside the test executable via
9198
# <location-prefix>dynamic_loading$(suffix).test, so main.cpp can find them
9299
# by scanning dll::program_location().parent_path() on all platforms.
@@ -113,10 +120,8 @@ rule make-variant ( suffix : extra-req * )
113120
;
114121
}
115122

116-
# Variant 1: the default registry.
117-
make-variant "" : ;
118-
119-
# Variant 2: indirect_registry (default_registry + indirect_vptr policy),
120-
# selected via BOOST_OPENMETHOD_DEFAULT_REGISTRY.
123+
# Two variants (dll-owned registry state) x {default, indirect} registry. The
124+
# exe-owned variants are CMake-only (see the note above).
125+
make-variant _default : ;
121126
make-variant _indirect
122127
: <define>BOOST_OPENMETHOD_DEFAULT_REGISTRY=::boost::openmethod::indirect_registry ;

test/dynamic_loading/main.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@
88

99
#define INCLUDED_FROM "main.cpp"
1010

11+
// When the build asks for the executable to own the registry state
12+
// (REGISTRY_IN_EXE), this module exports it and the shared libraries import it.
13+
#if defined(REGISTRY_IN_EXE)
14+
#define EXPORT_REGISTRY
15+
#endif
16+
1117
#include "registry.hpp"
1218
#include "method.hpp"
1319

@@ -22,6 +28,14 @@
2228
using namespace boost::openmethod;
2329
namespace mp11 = boost::mp11;
2430

31+
#if defined(_WIN32) || defined(__CYGWIN__)
32+
#if defined(EXPORT_REGISTRY)
33+
static_assert(std::is_base_of_v<policies::dllexport, default_registry_dllvar>);
34+
#else
35+
static_assert(std::is_base_of_v<policies::dllimport, default_registry_dllvar>);
36+
#endif
37+
#endif
38+
2539
using policy_ids_fn = const void**();
2640

2741
BOOST_OPENMETHOD_CLASSES(Animal, Dog);

test/dynamic_loading/registry.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@
77
#pragma GCC diagnostic ignored "-Wunused-function"
88
#endif
99

10+
// This module owns and exports the registry state, unless the build asks for
11+
// the executable to own it (REGISTRY_IN_EXE), in which case this becomes a
12+
// client that imports the state.
13+
#if !defined(REGISTRY_IN_EXE)
1014
#define EXPORT_REGISTRY
15+
#endif
1116

1217
#include "registry.hpp"
1318
#include "classes.hpp"
@@ -19,8 +24,12 @@ namespace mp11 = boost::mp11;
1924

2025
#if defined(_WIN32) || defined(__CYGWIN__)
2126
#include <boost/config.hpp>
27+
#if defined(EXPORT_REGISTRY)
2228
static_assert(std::is_base_of_v<policies::dllexport, default_registry_dllvar>);
2329
#else
30+
static_assert(std::is_base_of_v<policies::dllimport, default_registry_dllvar>);
31+
#endif
32+
#else
2433
#include <boost/dll/alias.hpp>
2534
#endif
2635

0 commit comments

Comments
 (0)