From c7c94dd244229cd41be556fad3f06056a36e142e Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 25 Jan 2026 21:15:34 -0500 Subject: [PATCH 01/74] BOOST_OPENMETHOD_DETAIL_HAS_STATIC_FN --- include/boost/openmethod/preamble.hpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index 39b82c6f..4f9c3e03 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -869,6 +870,17 @@ struct initialize_aux; } // namespace detail +#define BOOST_OPENMETHOD_DETAIL_HAS_STATIC_FN(FN) \ + template \ + struct BOOST_PP_CAT(has_, BOOST_PP_CAT(FN, _aux)) : std::false_type {}; \ + template \ + struct BOOST_PP_CAT(has_, BOOST_PP_CAT(FN, _aux))< \ + std::void_t()...))>, T, Args...> \ + : std::true_type {}; \ + template \ + constexpr bool BOOST_PP_CAT(has_, FN) = \ + BOOST_PP_CAT(has_, BOOST_PP_CAT(FN, _aux))::value + //! Methods, classes and policies. //! //! Methods exist in the context of a registry. Any class used as a method or From cbc4ca223ce9d19eabc099ca880985d2116b210a Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 25 Jan 2026 21:15:34 -0500 Subject: [PATCH 02/74] support dynamic loading on Windows --- CLAUDE.md | 356 ++++++++++++++++++ doc/modules/ROOT/examples/CMakeLists.txt | 6 +- .../examples/custom_rtti/1/custom_rtti.cpp | 12 +- .../examples/custom_rtti/2/custom_rtti.cpp | 12 +- .../ROOT/examples/shared_libs/CMakeLists.txt | 109 ++---- .../ROOT/examples/shared_libs/animals.hpp | 27 +- .../examples/shared_libs/dynamic_main.cpp | 152 ++++---- .../ROOT/examples/shared_libs/extensions.cpp | 18 +- include/boost/openmethod/core.hpp | 84 +++-- include/boost/openmethod/default_registry.hpp | 9 +- include/boost/openmethod/initialize.hpp | 212 ++++++++--- include/boost/openmethod/macros.hpp | 23 +- .../policies/default_error_handler.hpp | 33 +- .../openmethod/policies/fast_perfect_hash.hpp | 90 +++-- .../openmethod/policies/stderr_output.hpp | 24 +- .../boost/openmethod/policies/vptr_map.hpp | 23 +- .../boost/openmethod/policies/vptr_vector.hpp | 51 ++- include/boost/openmethod/preamble.hpp | 228 ++++++++--- test/CMakeLists.txt | 4 + test/dynamic_loading/CMakeLists.txt | 58 +++ test/dynamic_loading/classes.hpp | 18 + test/dynamic_loading/lib_method.cpp | 40 ++ test/dynamic_loading/lib_overrider.cpp | 42 +++ test/dynamic_loading/lib_registry.cpp | 36 ++ test/dynamic_loading/main.cpp | 86 +++++ test/dynamic_loading/method.hpp | 21 ++ test/dynamic_loading/registry.hpp | 20 + test/test_class_registration.cpp | 7 + test/test_compiler.cpp | 2 +- test/test_core.cpp | 4 +- test/test_dispatch.cpp | 11 - test/test_policies.cpp | 14 +- test/test_util.hpp | 16 +- 33 files changed, 1437 insertions(+), 411 deletions(-) create mode 100644 CLAUDE.md create mode 100644 test/dynamic_loading/CMakeLists.txt create mode 100644 test/dynamic_loading/classes.hpp create mode 100644 test/dynamic_loading/lib_method.cpp create mode 100644 test/dynamic_loading/lib_overrider.cpp create mode 100644 test/dynamic_loading/lib_registry.cpp create mode 100644 test/dynamic_loading/main.cpp create mode 100644 test/dynamic_loading/method.hpp create mode 100644 test/dynamic_loading/registry.hpp diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..d476a5f1 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,356 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +Boost.OpenMethod is a C++17 header-only library implementing open multi-methods (multiple dispatch). Unlike traditional virtual functions where dispatch occurs only on the first (`this`) parameter, open methods dispatch based on the runtime types of multiple arguments. + +**Key Characteristics:** +- C++17 required +- Header-only library +- Part of the Boost ecosystem +- Supports both CMake and Boost.Build (b2) + +## Build System + +### CMake Build + +**Basic build:** +```bash +mkdir build && cd build +cmake .. -DBOOST_SRC_DIR=/path/to/boost +cmake --build . +``` + +**Build with tests:** +```bash +cmake .. -DBOOST_OPENMETHOD_BUILD_TESTS=ON +cmake --build . --target tests +ctest +``` + +**Build with examples:** +```bash +cmake .. -DBOOST_OPENMETHOD_BUILD_TESTS=ON -DBOOST_OPENMETHOD_BUILD_EXAMPLES=ON +cmake --build . +``` + +**Important CMake options:** +- `BOOST_OPENMETHOD_BUILD_TESTS` - Enable tests (default: ON if root project) +- `BOOST_OPENMETHOD_BUILD_EXAMPLES` - Enable examples (requires tests enabled) +- `BOOST_OPENMETHOD_WARNINGS_AS_ERRORS` - Treat warnings as errors +- `BOOST_SRC_DIR` - Path to Boost source directory (default: `../..` or `$BOOST_SRC_DIR` env var) + +### Boost.Build (b2) + +**Build and test:** +```bash +b2 test +``` + +**Quick test (for CI):** +```bash +b2 test//quick +``` + +## Testing + +### Running All Tests (CMake) +```bash +cd build +ctest +``` + +### Running a Single Test (CMake) +```bash +cd build +ctest -R test_dispatch # Run specific test by name +# or directly +./boost_openmethod-test_dispatch +``` + +### Test Structure +- Test files: `test/test_*.cpp` - Standard unit tests using Boost.Test +- Compile-fail tests: `test/compile_fail_*.cpp` - Tests that should fail to compile +- Mixed build test: `test/mix_release_debug/` - Tests mixing debug/release builds +- Dynamic loading test: `test/dynamic_loading/` - Tests shared library support (requires Boost.DLL) +- 21+ test files covering dispatch, policies, virtual_ptr, RTTI, errors, etc. + +### Debug Mode Features +When building in Debug mode (`CMAKE_BUILD_TYPE=Debug`), runtime checks are automatically enabled via `BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS`. + +## Architecture + +### Layered Design + +The library is structured in three conceptual layers: + +1. **Preamble Layer** ([preamble.hpp](include/boost/openmethod/preamble.hpp)) + - Foundational types: `type_id`, `vptr_type`, `virtual_` + - Registry and policy framework + - Error types: `not_initialized`, `bad_call`, `no_overrider`, `ambiguous_call`, etc. + - No executable dispatch code + +2. **Core API** ([core.hpp](include/boost/openmethod/core.hpp)) + - `method` - Method implementation + - `virtual_ptr` - "Wide pointer" combining object pointer + v-table pointer + - Dispatch algorithms: `resolve_uni()` (single dispatch), `resolve_multi_*()` (multiple dispatch) + - Override registration via `override_impl<>` + - Class registration via `use_classes<>` + +3. **Macro Layer** ([macros.hpp](include/boost/openmethod/macros.hpp)) + - `BOOST_OPENMETHOD(name, params, return_type)` - Declare method + - `BOOST_OPENMETHOD_OVERRIDE(name, params, return_type)` - Declare overrider + - `BOOST_OPENMETHOD_CLASSES(classes...)` - Register class hierarchy + - Generates static registrar objects for automatic registration + +### Key Concepts + +**Open Methods**: Functions where dispatch depends on runtime types of multiple parameters, not just the first. + +**Virtual Parameters**: Parameters marked with `virtual_` or `virtual_ptr` that participate in dispatch. + +**Registries**: Template-parameterized contexts holding classes, methods, and policies. Default: `boost::openmethod::default_registry`. + +**Policies**: Pluggable components controlling behavior: +- `rtti` - Type identification (std_rtti, static_rtti, custom) +- `vptr` - V-table storage (vptr_vector, vptr_map) +- `type_hash` - Type ID hashing (fast_perfect_hash with hash_fn function object) +- `error_handler` - Error handling strategy (default_error_handler, throw_error_handler) +- `output` - Diagnostic output destination (stderr_output) +- `attributes` - Visibility/DLL decoration (dllexport, dllimport, local) + +**Dispatch Mechanisms**: +- Single dispatch: Direct v-table lookup `vtbl[slot]` +- Multi-dispatch: Stride-based indexing through multi-dimensional dispatch tables + +**virtual_ptr**: A "wide pointer" combining object pointer with v-table pointer +for efficient dispatch. Key for enabling dispatch on non-polymorphic or smart +pointer types. + +### Component Interaction + +``` +User Code → Macros → Core API → Preamble → Policies + ↓ + Static Registration +``` + +Static initializers generated by macros call core API functions to register +classes, methods, and overriders. The `initialize()` function builds dispatch +tables before first use. + +## Code Conventions + +### Formatting +The project uses clang-format with an LLVM-based style: +- `AlignAfterOpenBracket: AlwaysBreak` +- `AllowShortFunctionsOnASingleLine: false` +- No short blocks, if statements, or loops on single lines + +### Compiler Requirements +Tests require these C++17 features (checked by Boost.Build): +- auto nontype template params +- deduction guides +- fold expressions +- if constexpr +- inline variables +- structured bindings +- ``, ``, `` headers + +## Common Development Patterns + +### Working with Shared Libraries / DLL Support + +**Overview**: The library supports shared library usage on Windows with proper dllexport/dllimport decoration. + +**Key Pattern - Decoratable Static Variables**: +All policy static variables use `BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(name)` macro (in `preamble.hpp`) to enable DLL decoration. This generates three specializations of `static_`: +- Default (no attributes) +- `BOOST_SYMBOL_EXPORT` when registry has dllexport attributes +- `BOOST_SYMBOL_IMPORT` when registry has dllimport attributes + +The attributes are selected via `get_attributes` which uses ADL to call `boost_openmethod_declspec(Guide)`. + +**Affected Policies** (actual symbol names from `nm -D`): +- `stderr_output::fn::os` - Output stream (via `static_os`) +- `default_error_handler::fn::handler` - Error handler function (via `static_handler`) +- `fast_perfect_hash::fn::hash_fn` - Hash factors struct (via `static_hash_fn`) +- `vptr_map::fn::vptrs` - V-table pointer map (via `static_vptrs`) +- `vptr_vector::fn::vptr_vector_vptrs` / `vptr_vector_indirect_vptrs` - V-table vectors +- Registry state itself: `static_st>::st` - class/method/overrider lists + +**`declspec` types** (in `preamble.hpp`): +- `dllexport` — marks the owning library (exports static variables) +- `dllimport` — marks client libraries (imports from the owning library) +- `declspec_none` — no-op; use on non-Windows where dllexport/dllimport are unnecessary + +**Example Usage**: +```cpp +// In header shared between library and client +#if !defined(_MSC_VER) +#define MY_API boost::openmethod::declspec_none +#elif defined(MY_LIBRARY_EXPORTS) +#define MY_API boost::openmethod::dllexport +#else +#define MY_API boost::openmethod::dllimport +#endif + +namespace boost::openmethod { + MY_API boost_openmethod_declspec(default_registry_attributes); +} + +BOOST_OPENMETHOD(my_method, (virtual_ptr), void, MY_API); +``` + +**Critical: Exporting the Registry State**: +`static_st>::st` (the list of registered classes/methods/overriders) is only +instantiated and exported from a shared library when that library compiles code that actually +registers classes or methods (i.e., `BOOST_OPENMETHOD_CLASSES` or `BOOST_OPENMETHOD` macros). +If a library is meant to "own" the registry state, it must include a header that triggers these +registrations — it is not enough to just declare `boost_openmethod_declspec`. + +See `doc/modules/ROOT/examples/shared_libs/` and `test/dynamic_loading/` for complete examples. + +**Dynamic Loading Test** (`test/dynamic_loading/`): +Verifies shared state across libraries. Each library exports a single `dl_XXX_get_policy_ids() -> const void**` (null-terminated array of policy state addresses) plus `dl_XXX_get_method_fn()` where applicable. The array is built at first call using: +```cpp +namespace mp11 = boost::mp11; // alias, not 'using namespace' — avoids detail:: ambiguity +// ... +mp11::mp_for_each([&](auto p) { + using P = decltype(p); + if constexpr (detail::has_id>) { + ids[i++] = default_registry::policy

::id(); + } +}); +``` +Files: +- `registry.hpp` — sets up `REGISTRY_API` macro and `boost_openmethod_declspec` declaration +- `method.hpp` — sets up `METHOD_API` macro and declares the `speak` method +- `classes.hpp` — class definitions + `BOOST_OPENMETHOD_CLASSES` (included by lib_registry with dllexport to force `st` export) +- `lib_registry.cpp` — compiled with `REGISTRY_API=dllexport`, exports all registry statics +- `lib_method.cpp` — compiled with `METHOD_API=dllexport`, imports registry from lib_registry +- `lib_overrider.cpp` — dynamically loaded at runtime, adds a Dog overrider +- `main.cpp` — loads lib_overrider, compares all `id()` arrays element-by-element, calls `initialize()`, tests dispatch + +### Custom RTTI +When `` is unavailable or insufficient, use static_rtti or implement custom RTTI. See `doc/modules/ROOT/examples/custom_rtti/` and policies in `include/boost/openmethod/policies/`. + +### Multiple Registries +Registries are completely independent. Use separate registries to: +- Isolate method sets +- Apply different policies to different method families +- Enable coexistence of incompatible configurations + +Registry type must be specified consistently across related methods and classes. + +## File Organization + +- `include/boost/openmethod/` - Public headers + - `core.hpp`, `macros.hpp`, `preamble.hpp` - Main headers + - `initialize.hpp` - Dispatch table construction + - `default_registry.hpp` - Default policy configuration + - `detail/` - Internal implementation details + - `policies/` - Policy implementations + - `interop/` - Interoperability with other systems +- `test/` - Unit tests and compile-fail tests +- `doc/modules/ROOT/examples/` - Example programs +- `doc/modules/ROOT/pages/` - AsciiDoc documentation + +## Dependencies (Boost Libraries) + +Required: +- Boost.Assert +- Boost.Config +- Boost.Core +- Boost.DynamicBitset +- Boost.MP11 (metaprogramming) +- Boost.Preprocessor + +For testing: +- Boost.Test +- Boost.SmartPtr + +For examples: +- Boost.DLL (shared library examples) + +## Development Workflow + +1. Make changes to headers in `include/boost/openmethod/` +2. Build tests: `cmake --build build --target tests` +3. Run tests: `cd build && ctest` +4. For changes affecting examples: enable `BOOST_OPENMETHOD_BUILD_EXAMPLES` +5. Submit PRs against the `develop` branch + +## Important Implementation Details + +### Static Registration +Classes, methods, and overriders register automatically via static constructors. This happens before `main()`. The `initialize()` function must be called before first method invocation to build dispatch tables. + +### Dispatch Table Construction +The `initialize()` function: +1. Collects registered classes and overriders +2. Builds class hierarchy using provided inheritance relationships +3. Constructs dispatch tables using perfect hashing +4. Validates configuration (in debug mode or with runtime_checks policy) + +### Virtual Pointer Mechanics +`virtual_ptr` stores both object pointer and v-table pointer. It can be constructed from: +- Raw pointers (requires prior `use_classes` registration) +- Smart pointers (std::unique_ptr, std::shared_ptr, boost::intrusive_ptr) +- References +- Other virtual_ptr instances + +The v-table pointer enables O(1) method dispatch. + +### Policy Static Variables Pattern + +When adding static variables to policies: + +1. **Declare the variable storage** in `detail` namespace using `BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(variable_name)` (defined in `preamble.hpp`) +2. **Use type alias** in policy's `fn` class: `using var_storage = detail::static_variable_name` +3. **Access via storage**: `var_storage::variable_name` instead of direct static member +4. **Definition is generated** by the macro: `template Type detail::static_variable_name::variable_name;` + +This pattern ensures static variables can be properly decorated with dllexport/dllimport for shared library usage. + +5. **Add an `id()` function** to the policy's `fn` class returning the address of the first byte of the state. Its presence is detectable via `detail::has_id

` (a variable template generated by `BOOST_OPENMETHOD_DETAIL_HAS_STATIC_FN(has_id, id)` in `preamble.hpp`): +```cpp +static auto id() -> const void* { + return &static_::variable_name; +} +``` +For policies with two possible state variables (e.g., `vptr_vector`), use `if constexpr` to select the active one: +```cpp +static auto id() -> const void* { + if constexpr (Registry::has_indirect_vptr) { + return &static_::vptr_vector_indirect_vptrs; + } else { + return &static_::vptr_vector_vptrs; + } +} +``` +For `stderr_output`, which inherits its state, use the class name: `&fn::os`. + +**Example from fast_perfect_hash**: +```cpp +// In detail namespace +struct hash_fn { + std::size_t mult, shift, min_value, max_value; + auto operator()(type_id type) const -> std::size_t { + return (mult * reinterpret_cast(type)) >> shift; + } +}; +BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(hash_fn); // generates static_hash_fn + +// In policy +template +class fn { + using factors_storage = detail::static_hash_fn; +public: + static auto hash(type_id type) -> std::size_t { + return factors_storage::hash_fn(type); // Use via storage + } +}; +``` diff --git a/doc/modules/ROOT/examples/CMakeLists.txt b/doc/modules/ROOT/examples/CMakeLists.txt index 6d1f85b4..722c3198 100644 --- a/doc/modules/ROOT/examples/CMakeLists.txt +++ b/doc/modules/ROOT/examples/CMakeLists.txt @@ -26,6 +26,8 @@ foreach (cpp ${cpp_files}) add_dependencies(tests ${test_target}) endforeach() +add_subdirectory(shared_libs) + function(boost_openmethod_add_step_by_step dir) set(add_test "") if(ARGC GREATER 1) @@ -60,7 +62,3 @@ boost_openmethod_add_step_by_step(ambiguities OFF) boost_openmethod_add_step_by_step(core_api) boost_openmethod_add_step_by_step(custom_rtti) boost_openmethod_add_step_by_step(virtual_ptr_alt) - -if (NOT WIN32) - add_subdirectory(shared_libs) -endif() diff --git a/doc/modules/ROOT/examples/custom_rtti/1/custom_rtti.cpp b/doc/modules/ROOT/examples/custom_rtti/1/custom_rtti.cpp index 891a03f7..15875eb1 100644 --- a/doc/modules/ROOT/examples/custom_rtti/1/custom_rtti.cpp +++ b/doc/modules/ROOT/examples/custom_rtti/1/custom_rtti.cpp @@ -54,18 +54,22 @@ struct custom_rtti : boost::openmethod::policies::rtti { template static auto static_type() { if constexpr (is_polymorphic) { - return reinterpret_cast(T::static_type); + return reinterpret_cast( + static_cast(T::static_type)); } else { - return reinterpret_cast(0); + return reinterpret_cast( + static_cast(0)); } } template static auto dynamic_type(const T& obj) { if constexpr (is_polymorphic) { - return reinterpret_cast(obj.type); + return reinterpret_cast( + static_cast(obj.type)); } else { - return reinterpret_cast(0); + return reinterpret_cast( + static_cast(0)); } } }; diff --git a/doc/modules/ROOT/examples/custom_rtti/2/custom_rtti.cpp b/doc/modules/ROOT/examples/custom_rtti/2/custom_rtti.cpp index 9c9b37c0..5ebb8259 100644 --- a/doc/modules/ROOT/examples/custom_rtti/2/custom_rtti.cpp +++ b/doc/modules/ROOT/examples/custom_rtti/2/custom_rtti.cpp @@ -50,18 +50,22 @@ struct custom_rtti : boost::openmethod::policies::deferred_static_rtti { template static auto static_type() { if constexpr (is_polymorphic) { - return reinterpret_cast(T::static_type); + return reinterpret_cast( + static_cast(T::static_type)); } else { - return reinterpret_cast(0); + return reinterpret_cast( + static_cast(0)); } } template static auto dynamic_type(const T& obj) { if constexpr (is_polymorphic) { - return reinterpret_cast(obj.type); + return reinterpret_cast( + static_cast(obj.type)); } else { - return reinterpret_cast(0); + return reinterpret_cast( + static_cast(0)); } } }; diff --git a/doc/modules/ROOT/examples/shared_libs/CMakeLists.txt b/doc/modules/ROOT/examples/shared_libs/CMakeLists.txt index 64aaabf5..228185aa 100644 --- a/doc/modules/ROOT/examples/shared_libs/CMakeLists.txt +++ b/doc/modules/ROOT/examples/shared_libs/CMakeLists.txt @@ -7,88 +7,55 @@ message(STATUS "Boost.OpenMethod: building shared library examples") add_compile_definitions(BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS) -# All targets output to the same directory so that executables can locate shared -# libraries via boost::dll::program_location().parent_path(). -set(shared_libs_output_dir "${CMAKE_CURRENT_BINARY_DIR}") - -# Helper: add a CTest fixture that builds a CMake target, so that `ctest` alone -# (without a prior `cmake --build`) still works. -function(openmethod_shared_libs_build_fixture target) - add_test(NAME ${target}-build - COMMAND "${CMAKE_COMMAND}" --build "${CMAKE_BINARY_DIR}" - --target ${target} --config $) - set_tests_properties(${target}-build PROPERTIES FIXTURES_SETUP ${target}-fixture) -endfunction() - # ------------------------------------------------------------------------------ # static linking -add_library(boost_openmethod-shared SHARED extensions.cpp) -target_link_libraries(boost_openmethod-shared Boost::openmethod) -set_target_properties(boost_openmethod-shared PROPERTIES - ENABLE_EXPORTS ON - OUTPUT_NAME shared - LIBRARY_OUTPUT_DIRECTORY "${shared_libs_output_dir}" - RUNTIME_OUTPUT_DIRECTORY "${shared_libs_output_dir}" -) +# add_library(boost_openmethod-shared SHARED extensions.cpp) +# target_link_libraries(boost_openmethod-shared Boost::openmethod) +# set_target_properties(boost_openmethod-shared PROPERTIES +# ENABLE_EXPORTS ON +# OUTPUT_NAME shared +# ) -add_executable(boost_openmethod-static static_main.cpp) -target_link_libraries(boost_openmethod-static Boost::openmethod Boost::dll boost_openmethod-shared) -set_target_properties(boost_openmethod-static PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${shared_libs_output_dir}" -) -openmethod_shared_libs_build_fixture(boost_openmethod-static) -add_test(NAME boost_openmethod-static - COMMAND "${shared_libs_output_dir}/boost_openmethod-static") -set_tests_properties(boost_openmethod-static PROPERTIES - FIXTURES_REQUIRED boost_openmethod-static-fixture) +# add_executable(boost_openmethod-static static_main.cpp) +# target_link_libraries(boost_openmethod-static Boost::openmethod Boost::dll boost_openmethod-shared) +# add_test(NAME boost_openmethod-static COMMAND boost_openmethod-static) # ------------------------------------------------------------------------------ # dynamic loading, direct virtual_ptrs add_executable(boost_openmethod-dynamic dynamic_main.cpp) -set_target_properties(boost_openmethod-dynamic PROPERTIES - ENABLE_EXPORTS ON - RUNTIME_OUTPUT_DIRECTORY "${shared_libs_output_dir}" -) +set_target_properties(boost_openmethod-dynamic PROPERTIES ENABLE_EXPORTS ON) target_link_libraries(boost_openmethod-dynamic Boost::openmethod Boost::dll) -add_dependencies(boost_openmethod-dynamic boost_openmethod-shared) -if (NOT WIN32) - openmethod_shared_libs_build_fixture(boost_openmethod-shared) - openmethod_shared_libs_build_fixture(boost_openmethod-dynamic) - add_test(NAME boost_openmethod-dynamic - COMMAND "${shared_libs_output_dir}/boost_openmethod-dynamic") - set_tests_properties(boost_openmethod-dynamic PROPERTIES - FIXTURES_REQUIRED "boost_openmethod-shared-fixture;boost_openmethod-dynamic-fixture") -endif() -# ------------------------------------------------------------------------------ -# dynamic loading, indirect virtual_ptrs +add_library(boost_openmethod-shared SHARED extensions.cpp) +target_link_libraries(boost_openmethod-shared PRIVATE Boost::openmethod boost_openmethod-dynamic) +set_target_properties(boost_openmethod-shared PROPERTIES ENABLE_EXPORTS ON) -add_library(boost_openmethod-indirect_shared SHARED indirect_extensions.cpp) -target_compile_definitions( - boost_openmethod-indirect_shared PUBLIC BOOST_OPENMETHOD_DEFAULT_REGISTRY=indirect_registry) -target_link_libraries(boost_openmethod-indirect_shared PRIVATE Boost::openmethod Boost::dll) -set_target_properties(boost_openmethod-indirect_shared PROPERTIES - ENABLE_EXPORTS ON - OUTPUT_NAME indirect_shared - LIBRARY_OUTPUT_DIRECTORY "${shared_libs_output_dir}" - RUNTIME_OUTPUT_DIRECTORY "${shared_libs_output_dir}" -) +add_test(NAME boost_openmethod-dynamic_shared COMMAND boost_openmethod-dynamic) -add_executable(boost_openmethod-indirect indirect_main.cpp) -target_compile_definitions( - boost_openmethod-indirect PUBLIC BOOST_OPENMETHOD_DEFAULT_REGISTRY=indirect_registry) -set_target_properties(boost_openmethod-indirect PROPERTIES - ENABLE_EXPORTS ON - RUNTIME_OUTPUT_DIRECTORY "${shared_libs_output_dir}" +# NOTE: build the following target (or ALL) when working on this. Just building +# boost_openmethod-dynamic will *not* build the DLL. +add_custom_target(boost_openmethod-shared-all ALL + DEPENDS boost_openmethod-dynamic boost_openmethod-shared ) -target_link_libraries(boost_openmethod-indirect PRIVATE Boost::openmethod Boost::dll) -add_dependencies(boost_openmethod-indirect boost_openmethod-indirect_shared) -if (NOT WIN32) - openmethod_shared_libs_build_fixture(boost_openmethod-indirect) - add_test(NAME boost_openmethod-indirect - COMMAND "${shared_libs_output_dir}/boost_openmethod-indirect") - set_tests_properties(boost_openmethod-indirect PROPERTIES - FIXTURES_REQUIRED boost_openmethod-indirect-fixture) -endif() + +# ------------------------------------------------------------------------------ +# dynamic loading, indirect virtual_ptrs + +# add_library(boost_openmethod-indirect_shared SHARED indirect_extensions.cpp) +# target_compile_definitions( +# boost_openmethod-indirect_shared PUBLIC BOOST_OPENMETHOD_DEFAULT_REGISTRY=indirect_registry) +# target_link_libraries(boost_openmethod-indirect_shared PRIVATE Boost::openmethod Boost::dll) +# set_target_properties(boost_openmethod-indirect_shared PROPERTIES +# ENABLE_EXPORTS ON +# OUTPUT_NAME indirect_shared +# ) + +# add_executable(boost_openmethod-indirect indirect_main.cpp) +# target_compile_definitions( +# boost_openmethod-indirect PUBLIC BOOST_OPENMETHOD_DEFAULT_REGISTRY=indirect_registry) +# set_target_properties(boost_openmethod-indirect PROPERTIES ENABLE_EXPORTS ON) +# target_link_libraries(boost_openmethod-indirect PRIVATE Boost::openmethod Boost::dll) +# add_dependencies(boost_openmethod-indirect boost_openmethod-indirect_shared) +# add_test(NAME boost_openmethod-indirect COMMAND boost_openmethod-indirect) diff --git a/doc/modules/ROOT/examples/shared_libs/animals.hpp b/doc/modules/ROOT/examples/shared_libs/animals.hpp index e725355c..e0ac8aba 100644 --- a/doc/modules/ROOT/examples/shared_libs/animals.hpp +++ b/doc/modules/ROOT/examples/shared_libs/animals.hpp @@ -11,6 +11,26 @@ // tag::content[] // animals.hpp +#include + +#ifdef BOOST_CLANG +#pragma clang diagnostic ignored "-Wundefined-var-template" +#endif + +#ifdef BOOST_GCC +//#pragma GCC diagnostic ignored "-Wundefined-var-template" +#endif + +#ifdef LIBRARY_NAME +#define ANIMALS_API boost::openmethod::dllexport +#else +#define ANIMALS_API boost::openmethod::dllimport +#endif + +namespace boost::openmethod { + ANIMALS_API boost_openmethod_declspec(default_registry_attributes); +} + #include #include @@ -18,11 +38,16 @@ struct Animal { virtual ~Animal() {} }; struct Herbivore : Animal {}; struct Carnivore : Animal {}; +struct Cow : Herbivore {}; +struct Wolf : Carnivore {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Herbivore, Cow, Carnivore, Wolf); + BOOST_OPENMETHOD( meet, ( boost::openmethod::virtual_ptr, boost::openmethod::virtual_ptr), - std::string); + std::string, ANIMALS_API); // end::content[] #endif diff --git a/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp b/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp index b74ea6dc..2654d88a 100644 --- a/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp +++ b/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp @@ -5,9 +5,10 @@ #ifndef LIBRARY_NAME #ifdef _MSC_VER -#define LIBRARY_NAME "shared.dll" +#define LIBRARY_NAME "boost_openmethod-shared.dll" #else -#define LIBRARY_NAME "libshared.so" +#define LIBRARY_NAME "libboost_openmethod-shared.so" + #endif #endif @@ -23,12 +24,24 @@ #include #include -using namespace boost::openmethod::aliases; +using namespace boost::openmethod; + +static_assert(!std::is_same_v< + BOOST_OPENMETHOD_TYPE( + meet, (virtual_ptr, virtual_ptr), + std::string)::declspec, + void>); + +static_assert(std::is_same_v< + BOOST_OPENMETHOD_TYPE( + meet, (virtual_ptr, virtual_ptr), + std::string)::declspec, + dllexport>); -struct Cow : Herbivore {}; -struct Wolf : Carnivore {}; +static_assert(!std::is_same_v); +static_assert(std::is_same_v); -BOOST_OPENMETHOD_CLASSES(Animal, Herbivore, Cow, Wolf, Carnivore); +BOOST_OPENMETHOD_CLASSES(Herbivore, Cow, Carnivore, Wolf); BOOST_OPENMETHOD_OVERRIDE( meet, (virtual_ptr, virtual_ptr), std::string) { @@ -39,64 +52,73 @@ BOOST_OPENMETHOD_OVERRIDE( int main() { // end::load[] // end::unload[] - std::cout << "Before loading the shared library.\n"; - - boost::openmethod::initialize(); - - std::cout << "cow meets wolf -> " - << meet(*std::make_unique(), *std::make_unique()) - << "\n"; // greet - std::cout << "wolf meets cow -> " - << meet(*std::make_unique(), *std::make_unique()) - << "\n"; // greet - - // to be continued... - // end::before[] - // tag::load[] - // ... - - std::cout << "\nAfter loading the shared library.\n"; - - boost::dll::shared_library lib( - boost::dll::program_location().parent_path() / LIBRARY_NAME, - boost::dll::load_mode::rtld_now); - boost::openmethod::initialize(); - - std::cout << "cow meets wolf -> " - << meet(*std::make_unique(), *std::make_unique()) - << "\n"; // run - std::cout << "wolf meets cow -> " - << meet(*std::make_unique(), *std::make_unique()) - << "\n"; // hunt - - auto make_tiger = lib.get("make_tiger"); - std::cout << "cow meets tiger -> " - << meet( - *std::make_unique(), - *std::unique_ptr(make_tiger())) - << "\n"; // hunt - // end::load[] - - // tag::unload[] - // ... - - std::cout << "\nAfter unloading the shared library.\n"; - - lib.unload(); - boost::openmethod::initialize(); - - std::cout << "cow meets wolf -> " - << meet(*std::make_unique(), *std::make_unique()) - << "\n"; // greet - std::cout << "wolf meets cow -> " - << meet(*std::make_unique(), *std::make_unique()) - << "\n"; // greet - // tag::before[] - // tag::load[] - // tag::unload[] + + try { + std::cout << "Before loading the shared library.\n"; + + boost::openmethod::initialize(trace::from_env()); + BOOST_ASSERT(default_registry::static_vptr != nullptr); + + std::cout << "cow meets wolf -> " + << meet(*std::make_unique(), *std::make_unique()) + << "\n"; // greet + std::cout << "wolf meets cow -> " + << meet(*std::make_unique(), *std::make_unique()) + << "\n"; // greet + + // to be continued... + // end::before[] + // tag::load[] + // ... + + std::cout << "\nAfter loading the shared library.\n"; + + boost::dll::shared_library lib( + boost::dll::program_location().parent_path() / LIBRARY_NAME, + boost::dll::load_mode::rtld_now); + boost::openmethod::initialize(trace::from_env()); + + std::cout << "cow meets wolf -> " + << meet(*std::make_unique(), *std::make_unique()) + << "\n"; // run + std::cout << "wolf meets cow -> " + << meet(*std::make_unique(), *std::make_unique()) + << "\n"; // hunt + + auto make_tiger = lib.get("make_tiger"); + std::cout << "cow meets tiger -> " + << meet( + *std::make_unique(), + *std::unique_ptr(make_tiger())) + << "\n"; // hunt + // end::load[] + + // tag::unload[] + // ... + + std::cout << "\nAfter unloading the shared library.\n"; + + lib.unload(); + boost::openmethod::initialize(trace::from_env()); + + std::cout << "cow meets wolf -> " + << meet(*std::make_unique(), *std::make_unique()) + << "\n"; // greet + std::cout << "wolf meets cow -> " + << meet(*std::make_unique(), *std::make_unique()) + << "\n"; // greet + // tag::before[] + // tag::load[] + // tag::unload[] + + // end::before[] + // end::load[] + // end::unload[] + + } catch (const std::exception& ex) { + std::cerr << "Exception: " << ex.what() << '\n'; + return 1; + } return 0; -} -// end::before[] -// end::load[] -// end::unload[] +} \ No newline at end of file diff --git a/doc/modules/ROOT/examples/shared_libs/extensions.cpp b/doc/modules/ROOT/examples/shared_libs/extensions.cpp index ab21f041..3bdf807d 100644 --- a/doc/modules/ROOT/examples/shared_libs/extensions.cpp +++ b/doc/modules/ROOT/examples/shared_libs/extensions.cpp @@ -7,10 +7,23 @@ // extensions.cpp #include "animals.hpp" -using namespace boost::openmethod::aliases; +using namespace boost::openmethod; + +static_assert(std::is_same_v); + +static_assert(std::is_same_v< + BOOST_OPENMETHOD_TYPE( + meet, (virtual_ptr, virtual_ptr), + std::string)::declspec, + dllimport>); BOOST_OPENMETHOD_OVERRIDE( - meet, (virtual_ptr, virtual_ptr), std::string) { + meet, (virtual_ptr a, virtual_ptr b), std::string) { + auto p = BOOST_OPENMETHOD_TYPE( + meet, (virtual_ptr, virtual_ptr), + std::string)::next; + BOOST_ASSERT(p); + BOOST_ASSERT(p(a, b) == "greet"); return "run"; } @@ -28,6 +41,7 @@ extern "C" { __declspec(dllexport) #endif auto make_tiger() -> Animal* { + BOOST_ASSERT(default_registry::static_vptr != nullptr); return new Tiger; } } diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index 7c2d3837..d3793731 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -360,7 +360,7 @@ struct use_class_aux> } // coverity[uninit] - zero-initialized static storage - Registry::classes.push_back(*this); + Registry::static_::st.classes.push_back(*this); } void resolve_type_ids() { @@ -370,7 +370,7 @@ struct use_class_aux> } ~use_class_aux() { - Registry::classes.remove(*this); + Registry::static_::st.classes.remove(*this); } }; @@ -1872,8 +1872,8 @@ struct virtual_traits, Registry> { //! @return A lvalue reference to a `virtual_ptr` to the same object, cast //! to `Derived::element_type`. template - static auto - cast(const virtual_ptr& ptr) -> decltype(auto) { + static auto cast(const virtual_ptr& ptr) + -> decltype(auto) { return ptr.template cast(); } @@ -1918,8 +1918,8 @@ struct virtual_traits&, Registry> { //! @return A lvalue reference to a `virtual_ptr` to the same object, cast //! to `Derived::element_type`. template - static auto - cast(const virtual_ptr& ptr) -> decltype(auto) { + static auto cast(const virtual_ptr& ptr) + -> decltype(auto) { return ptr.template cast< typename std::remove_reference_t::element_type>(); } @@ -1927,9 +1927,12 @@ struct virtual_traits&, Registry> { // ============================================================================= // Method +auto boost_openmethod_declspec(...) -> void; namespace detail { +BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(fn); + template struct select_overrider_virtual_type_aux { using type = void; @@ -2161,10 +2164,14 @@ class method; template< typename Id, typename... Parameters, typename ReturnType, class Registry> class method - : public detail::method_base { + : public detail::method_base, + public detail::static_fn< + method> { template struct override_aux; + friend struct detail::static_fn; + // Aliases used in implementation only. Everything extracted from template // arguments is capitalized like the arguments themselves. using RegistryType = Registry; @@ -2183,7 +2190,9 @@ class method //! //! The only instance of `method`. Its `operator()` is used to call //! the method. - static method fn; + //static method fn; + // `fn` cannot be `inline static` becaused of MSVC (19.43) bug causing + // a "no appropriate default constructor available". //! Call the method //! @@ -2229,6 +2238,10 @@ class method //! `Fn` must be a function that is an overrider of the method. //! //! @tparam Fn A function that is an overrider of the method. + + // 'next' does not need any special treatment for Windows DLLs, because it + // may be called only from within the overrider, registered with a registrar + // in the same module. template static FunctionPointer next; @@ -2297,8 +2310,8 @@ class method template auto resolve_multi_first( - const ArgType& arg, - const MoreArgTypes&... more_args) const -> detail::word; + const ArgType& arg, const MoreArgTypes&... more_args) const + -> detail::word; template< std::size_t VirtualArg, typename MethodArgList, typename ArgType, @@ -2323,8 +2336,9 @@ class method void resolve(); // virtual if Registry contains has_deferred_static_rtti - static BOOST_NORETURN auto fn_not_implemented( - detail::remove_virtual_... args) -> ReturnType; + static BOOST_NORETURN auto + fn_not_implemented(detail::remove_virtual_... args) + -> ReturnType; static BOOST_NORETURN auto fn_ambiguous(detail::remove_virtual_... args) -> ReturnType; @@ -2332,8 +2346,8 @@ class method auto Overrider, typename OverriderReturn, typename... OverriderParameters> struct thunk { - static auto - fn(detail::remove_virtual_... arg) -> ReturnType; + static auto fn(detail::remove_virtual_... arg) + -> ReturnType; using OverriderVirtualParameters = detail::overrider_virtual_types< DeclaredParameters, mp11::mp_list, Registry>; @@ -2363,17 +2377,17 @@ class method }; }; -template< - typename Id, typename... Parameters, typename ReturnType, class Registry> -method - method::fn; - template< typename Id, typename... Parameters, typename ReturnType, class Registry> template typename method::FunctionPointer method::next; +// template< +// typename Id, typename... Parameters, typename ReturnType, class Registry> +// method +// method::fn; + template< typename Id, typename... Parameters, typename ReturnType, class Registry> template @@ -2406,7 +2420,7 @@ method::method() { // zero-initalized static variable // coverity[uninit_use] - Registry::methods.push_back(*this); + Registry::static_::st.methods.push_back(*this); } template< @@ -2426,7 +2440,7 @@ void method::resolve_type_ids() { template< typename Id, typename... Parameters, typename ReturnType, class Registry> method::~method() { - Registry::methods.remove(*this); + Registry::static_::st.methods.remove(*this); } // ----------------------------------------------------------------------------- @@ -2441,8 +2455,9 @@ method::operator()( using namespace detail; auto pf = resolve(parameter_traits::peek(args)...); - return pf(std::forward::type>( - args)...); + return pf( + std::forward::type>( + args)...); } template< @@ -2472,8 +2487,9 @@ BOOST_FORCEINLINE template< typename Id, typename... Parameters, typename ReturnType, class Registry> template -BOOST_FORCEINLINE auto method::vptr( - const ArgType& arg) const -> vptr_type { +BOOST_FORCEINLINE auto +method::vptr(const ArgType& arg) const + -> vptr_type { if constexpr (detail::is_virtual_ptr) { return arg.vptr(); } else { @@ -2486,8 +2502,8 @@ template< template BOOST_FORCEINLINE auto method::resolve_uni( - const ArgType& arg, - const MoreArgTypes&... more_args) const -> detail::word { + const ArgType& arg, const MoreArgTypes&... more_args) const + -> detail::word { using namespace detail; using namespace policies; @@ -2506,8 +2522,8 @@ template< template BOOST_FORCEINLINE auto method::resolve_multi_first( - const ArgType& arg, - const MoreArgTypes&... more_args) const -> detail::word { + const ArgType& arg, const MoreArgTypes&... more_args) const + -> detail::word { using namespace detail; using namespace boost::mp11; @@ -2564,8 +2580,8 @@ method::resolve_multi_next( template< typename Id, typename... Parameters, typename ReturnType, class Registry> template -inline auto -method::has_next() -> bool { +inline auto method::has_next() + -> bool { if (next == fn_not_implemented) { return false; } @@ -2751,7 +2767,7 @@ method::override_impl< // zero-initalized static variable // coverity[uninit_use] if (overrider_info::method) { - BOOST_ASSERT(overrider_info::method == &fn); + BOOST_ASSERT(overrider_info::method == &method::fn); return; } @@ -2763,7 +2779,7 @@ method::override_impl< #pragma GCC diagnostic pop #endif - overrider_info::method = &fn; + overrider_info::method = &method::fn; if constexpr (!Registry::has_deferred_static_rtti) { resolve_type_ids(); @@ -2778,7 +2794,7 @@ method::override_impl< this->vp_begin = vp_type_ids; this->vp_end = vp_type_ids + Arity; - fn.overriders.push_back(*this); + method::fn.overriders.push_back(*this); } template< diff --git a/include/boost/openmethod/default_registry.hpp b/include/boost/openmethod/default_registry.hpp index 38ad1b96..178c8157 100644 --- a/include/boost/openmethod/default_registry.hpp +++ b/include/boost/openmethod/default_registry.hpp @@ -15,6 +15,8 @@ namespace boost::openmethod { +struct default_registry_attributes {}; + //! Default registry. //! //! `default_registry` is a predefined @ref registry, and the default value of @@ -37,9 +39,10 @@ namespace boost::openmethod { //! including those pulled from libraries. struct default_registry : registry< - policies::std_rtti, policies ::vptr_vector, - policies::fast_perfect_hash, policies::default_error_handler, - policies::stderr_output + policies::std_rtti, policies::fast_perfect_hash, + policies::vptr_vector, policies::default_error_handler, + policies::stderr_output, + policies::attributes_guide #ifdef BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS , policies::runtime_checks diff --git a/include/boost/openmethod/initialize.hpp b/include/boost/openmethod/initialize.hpp index b9d56722..2d38a63d 100644 --- a/include/boost/openmethod/initialize.hpp +++ b/include/boost/openmethod/initialize.hpp @@ -59,6 +59,43 @@ struct aggregate_reports, mp11::mp_list<>, Void> { struct type : Reports... {}; }; +// Policy initialization helpers + +#if defined(_MSC_VER) && _MSC_VER <= 1950 +template +struct has_initialize_aux : std::false_type {}; + +template +struct has_initialize_aux< + std::void_t...>)>, + T, Args...> : std::true_type {}; + +template +constexpr bool has_initialize = has_initialize_aux::value; +#else +BOOST_OPENMETHOD_DETAIL_HAS_STATIC_FN(initialize); +#endif + +// Call initialize on a single policy if it has the function +template +void initialize_policy(const Context& ctx, const Options& options) { + using PolicyFn = typename Policy::template fn; + if constexpr (has_initialize) { + PolicyFn::initialize(ctx, options); + } +} + +template +struct initialize_policies; + +template +struct initialize_policies> { + template + static void fn(const Context& ctx, const Options& options) { + (initialize_policy(ctx, options), ...); + } +}; + inline void merge_into(boost::dynamic_bitset<>& a, boost::dynamic_bitset<>& b) { if (b.size() < a.size()) { b.resize(a.size()); @@ -93,8 +130,7 @@ struct generic_compiler { }; struct class_ { - bool is_abstract = false; - std::vector type_ids; + std::vector ci; std::vector transitive_bases; std::vector direct_bases; std::vector direct_derived; @@ -105,22 +141,13 @@ struct generic_compiler { std::size_t first_slot = 0; std::size_t mark = 0; // temporary mark to detect cycles std::vector vtbl; - vptr_type* static_vptr; auto is_base_of(class_* other) const -> bool { return transitive_derived.find(other) != transitive_derived.end(); } - auto vptr() const -> const vptr_type& { - return *static_vptr; - } - - auto type_id_begin() const { - return type_ids.begin(); - } - - auto type_id_end() const { - return type_ids.end(); + auto is_abstract() const -> bool { + return ci[0]->is_abstract; } }; @@ -185,12 +212,80 @@ struct generic_compiler { std::deque classes; + class const_class_iterator { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = const detail::class_info*; + using difference_type = std::ptrdiff_t; + using pointer = const detail::class_info**; + using reference = const detail::class_info*&; + + const_class_iterator() = default; + + const_class_iterator( + std::deque::const_iterator class_iter, + std::deque::const_iterator class_end) + : class_iter_(class_iter), class_end_(class_end) { + if (class_iter_ != class_end_) { + ci_iter_ = class_iter_->ci.begin(); + advance_to_valid(); + } + } + auto operator->() const -> const detail::class_info* { + return *ci_iter_; + } + auto operator*() const -> const detail::class_info* { + return *ci_iter_; + } + + auto operator++() -> const_class_iterator& { + ++ci_iter_; + advance_to_valid(); + return *this; + } + + auto operator++(int) -> const_class_iterator { + const_class_iterator tmp = *this; + ++(*this); + return tmp; + } + + auto operator==(const const_class_iterator& other) const -> bool { + if (class_iter_ != other.class_iter_) { + return false; + } + if (class_iter_ == class_end_) { + return true; + } + return ci_iter_ == other.ci_iter_; + } + + auto operator!=(const const_class_iterator& other) const -> bool { + return !(*this == other); + } + + private: + void advance_to_valid() { + while (class_iter_ != class_end_ && + ci_iter_ == class_iter_->ci.end()) { + ++class_iter_; + if (class_iter_ != class_end_) { + ci_iter_ = class_iter_->ci.begin(); + } + } + } + + std::deque::const_iterator class_iter_; + std::deque::const_iterator class_end_; + std::vector::const_iterator ci_iter_; + }; + auto classes_begin() const { - return classes.begin(); + return const_class_iterator(classes.begin(), classes.end()); } auto classes_end() const { - return classes.end(); + return const_class_iterator(classes.end(), classes.end()); } std::vector methods; @@ -248,7 +343,7 @@ template auto operator<<(trace_stream& tr, const generic_compiler::class_& cls) -> trace_stream& { if constexpr (Compiler::has_trace) { - tr << type_name(cls.type_ids[0]); + tr << type_name(cls.ci[0]->type); } return tr; @@ -298,7 +393,20 @@ auto operator<<(trace_stream& tr, const spec_name& sn) } template -struct range; +struct range { + range(Iterator first, Iterator last) : first(first), last(last) { + } + + Iterator first, last; + + auto begin() const -> Iterator { + return first; + } + + auto end() const -> Iterator { + return last; + } +}; template auto write_range(trace_stream& tr, range range, F fn) -> auto& { @@ -425,8 +533,8 @@ struct registry::compiler : detail::generic_compiler { static void select_dominant_overriders( std::vector& dominants, std::size_t& pick, std::size_t& remaining); - static auto - is_more_specific(const overrider* a, const overrider* b) -> bool; + static auto is_more_specific(const overrider* a, const overrider* b) + -> bool; static auto is_base(const overrider* a, const overrider* b) -> bool; std::tuple options; @@ -473,7 +581,7 @@ template void registry::compiler::initialize() { compile(); install_global_tables(); - registry::initialized = true; + registry::static_::st.initialized = true; } #ifdef _MSC_VER @@ -542,7 +650,7 @@ void registry::compiler::augment_classes() { // The standard does not guarantee that there is exactly one // type_info object per class. However, it guarantees that the // type_index for a class has a unique value. - for (auto& cr : registry::classes) { + for (auto& cr : registry::static_::st.classes) { if constexpr (has_deferred_static_rtti) { static_cast(cr).resolve_type_ids(); } @@ -550,21 +658,28 @@ void registry::compiler::augment_classes() { { indent _(tr); ++tr << type_name(cr.type) << ": " - << range{cr.first_base, cr.last_base} << "\n"; + << range{cr.first_base, cr.last_base} + << ", type = " << cr.type << ", &vptr = " << &cr.vptr() + << "\n"; } auto& rtc = class_map[rtti::type_index(cr.type)]; if (rtc == nullptr) { rtc = &classes.emplace_back(); - rtc->is_abstract = cr.is_abstract; - rtc->static_vptr = cr.static_vptr; } - if (std::find( - rtc->type_ids.begin(), rtc->type_ids.end(), cr.type) == - rtc->type_ids.end()) { - rtc->type_ids.push_back(cr.type); + bool new_type_id = true; + + for (auto ci : rtc->ci) { + if (ci->type == cr.type) { + new_type_id = false; + break; + } + } + + if (new_type_id) { + rtc->ci.push_back(&cr); } } } @@ -572,7 +687,7 @@ void registry::compiler::augment_classes() { // All known classes now have exactly one associated class_* in the // map. Collect the bases. - for (auto& cr : registry::classes) { + for (auto& cr : registry::static_::st.classes) { auto rtc = class_map[rtti::type_index(cr.type)]; for (auto& base : range{cr.first_base, cr.last_base}) { @@ -701,14 +816,14 @@ void registry::compiler::augment_methods() { using namespace policies; using namespace detail; - methods.resize(registry::methods.size()); + methods.resize(registry::static_::st.methods.size()); ++tr << "Methods:\n"; indent _(tr); auto meth_iter = methods.begin(); - for (auto& meth_info : registry::methods) { + for (auto& meth_info : registry::static_::st.methods) { if constexpr (has_deferred_static_rtti) { static_cast(meth_info).resolve_type_ids(); } @@ -842,8 +957,8 @@ void registry::compiler::augment_methods() { if (!vp->is_base_of(overrider.vp[param_index])) { missing_base error; - error.base = overrider.vp[param_index]->type_ids[0]; - error.derived = vp->type_ids[0]; + error.base = overrider.vp[param_index]->ci[0]->type; + error.derived = vp->ci[0]->type; if constexpr (has_error_handler) { error_handler::error(error); @@ -1052,7 +1167,7 @@ void registry::compiler::build_dispatch_tables() { auto& group = dim_group[mask]; group.classes.push_back(covariant_class); group.has_concrete_classes = group.has_concrete_classes || - !covariant_class->is_abstract; + !covariant_class->is_abstract(); ++tr << "-> mask: " << mask << "\n"; } @@ -1083,7 +1198,7 @@ void registry::compiler::build_dispatch_tables() { for (auto cls : group.classes) { indent _(tr); - ++tr << type_name(cls->type_ids[0]) << "\n"; + ++tr << type_name(cls->ci[0]->type) << "\n"; auto& entry = cls->vtbl[m.slots[dim] - cls->first_slot]; entry.method_index = &m - &methods[0]; entry.vp_index = dim; @@ -1152,7 +1267,7 @@ void registry::compiler::build_dispatch_table( << "\n"; indent _(tr); for (auto cls : range{group.classes.begin(), group.classes.end()}) { - ++tr << type_name(cls->type_ids[0]) << "\n"; + ++tr << type_name(cls->ci[0]->type) << "\n"; } } @@ -1367,7 +1482,9 @@ void registry::compiler::write_global_data() { ++tr << "Initializing v-tables at " << gv_iter << "\n"; for (auto& cls : classes) { - *cls.static_vptr = gv_iter - cls.first_slot; + for (auto& ci : cls.ci) { + *ci->static_vptr = gv_iter - cls.first_slot; + } ++tr << rflush(4, gv_iter - gv_first) << " " << gv_iter << " vtbl for " << cls << " slots " << cls.first_slot << "-" @@ -1407,11 +1524,9 @@ void registry::compiler::write_global_data() { ++tr << rflush(4, dispatch_data_size) << " " << gv_iter << " end\n"; - if constexpr (has_vptr) { - vptr::initialize(*this, options); - } + detail::initialize_policies::fn(*this, options); - new_dispatch_data.swap(dispatch_data); + new_dispatch_data.swap(static_::st.dispatch_data); } template @@ -1630,14 +1745,7 @@ inline auto initialize(Options&&... options) { namespace detail { -template -struct has_finalize_aux : std::false_type {}; - -template -struct has_finalize_aux< - std::void_t>()))>, - Policy, Options...> : std::true_type {}; +BOOST_OPENMETHOD_DETAIL_HAS_STATIC_FN(finalize); } // namespace detail @@ -1647,13 +1755,13 @@ auto registry::finalize(Options... opts) -> void { std::tuple options(opts...); // gcc-8 doesn't like CTAD here mp11::mp_for_each([&options](auto policy) { using fn = typename decltype(policy)::template fn; - if constexpr (detail::has_finalize_aux::value) { + if constexpr (detail::has_finalize&>) { fn::finalize(options); } }); - dispatch_data.clear(); - initialized = false; + static_::dispatch_data.clear(); + static_::initialized = false; } //! Release resources held by registry. diff --git a/include/boost/openmethod/macros.hpp b/include/boost/openmethod/macros.hpp index 3a574ca9..f011056d 100644 --- a/include/boost/openmethod/macros.hpp +++ b/include/boost/openmethod/macros.hpp @@ -25,16 +25,27 @@ struct enable_forwarder< template struct va_args; -template -struct va_args { +template +struct va_args { using return_type = ReturnType; - using registry = Registry; + using registry = + std::conditional_t, Other, macro_default_registry>; + using declspec_type = + std::conditional_t, Other, void>; +}; + +template +struct va_args { + using return_type = ReturnType; + using registry = std::conditional_t, T, U>; + using declspec_type = std::conditional_t, T, U>; }; template struct va_args { using return_type = ReturnType; using registry = macro_default_registry; + using declspec_type = registry::declspec; }; template @@ -64,8 +75,14 @@ inline constexpr bool method_not_found = false; ::boost::openmethod::detail::va_args<__VA_ARGS__>::return_type ARGS, \ ::boost::openmethod::detail::va_args<__VA_ARGS__>::registry> +#define BOOST_OPENMETHOD_DETAIL_STORAGE_CLASS(NAME, ARGS, ...) \ + auto boost_openmethod_declspec( \ + BOOST_OPENMETHOD_TYPE(NAME, ARGS, __VA_ARGS__) &) \ + -> ::boost::openmethod::detail::va_args<__VA_ARGS__>::declspec_type; + #define BOOST_OPENMETHOD(NAME, ARGS, ...) \ struct BOOST_OPENMETHOD_ID(NAME); \ + BOOST_OPENMETHOD_DETAIL_STORAGE_CLASS(NAME, ARGS, __VA_ARGS__); \ template \ typename ::boost::openmethod::detail::enable_forwarder< \ void, BOOST_OPENMETHOD_TYPE(NAME, ARGS, __VA_ARGS__), \ diff --git a/include/boost/openmethod/policies/default_error_handler.hpp b/include/boost/openmethod/policies/default_error_handler.hpp index 342fc7b5..df056e96 100644 --- a/include/boost/openmethod/policies/default_error_handler.hpp +++ b/include/boost/openmethod/policies/default_error_handler.hpp @@ -11,7 +11,15 @@ #include #include -namespace boost::openmethod::policies { +namespace boost::openmethod { + +namespace detail { + +BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(handler); + +} // namespace detail + +namespace policies { //! Calls a std::function with the error. //! @@ -77,6 +85,9 @@ struct default_error_handler : error_handler { //! The type of the error handler function object. using function_type = std::function; + using static_ = + detail::static_handler; + //! Calls a function with the error object, wrapped in an @ref //! error_variant. //! @@ -84,6 +95,8 @@ struct default_error_handler : error_handler { //! @param error The error object. template static auto error(const Error& error) -> void { + auto handler = static_::handler ? static_::handler + : default_handler; handler(error_variant(error)); } @@ -96,7 +109,13 @@ struct default_error_handler : error_handler { //! @return The previous function. // coverity[auto_causes_copy] static auto set(function_type new_handler) -> function_type { - return std::exchange(handler, std::move(new_handler)); + auto prev = std::exchange( + static_::handler, std::move(new_handler)); + return prev ? prev : default_handler; + } + + static auto id() -> const void* { + return &static_::handler; } //! The default error handler function. @@ -115,16 +134,10 @@ struct default_error_handler : error_handler { Registry::output::os << "\n"; } } - - private: - static function_type handler; }; }; -template -typename default_error_handler::fn::function_type - default_error_handler::fn::handler = default_handler; - -} // namespace boost::openmethod::policies +} // namespace policies +} // namespace boost::openmethod #endif diff --git a/include/boost/openmethod/policies/fast_perfect_hash.hpp b/include/boost/openmethod/policies/fast_perfect_hash.hpp index e91f8f86..550e9e8b 100644 --- a/include/boost/openmethod/policies/fast_perfect_hash.hpp +++ b/include/boost/openmethod/policies/fast_perfect_hash.hpp @@ -10,6 +10,7 @@ #include #include +#include #ifdef _MSC_VER #pragma warning(push) #pragma warning(disable : 4702) // unreachable code @@ -30,6 +31,19 @@ using uintptr = std::size_t; constexpr uintptr uintptr_max = (std::numeric_limits::max)(); #endif +struct hash_fn { + std::size_t mult; + std::size_t shift; + std::size_t min_value; + std::size_t max_value; + + auto operator()(type_id type) const -> std::size_t { + return (mult * reinterpret_cast(type)) >> shift; + } +}; + +BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(hash_fn); + template std::vector fast_perfect_hash_control; @@ -73,20 +87,16 @@ struct fast_perfect_hash : type_hash { //! @tparam Registry The registry containing this policy template class fn { - static std::size_t mult; - static std::size_t shift; - static std::size_t min_value; - static std::size_t max_value; - + using static_ = detail::static_hash_fn; static void check(std::size_t index, type_id type); template - static void initialize( + static void initialize_aux( const InitializeContext& ctx, std::vector& buckets, const std::tuple& options); public: - //! Find the hash factors + //! Finds the hash factors //! //! Attempts to find suitable values for the multiplication factor `M` //! and the shift amount `S` to that do not result in collisions for the @@ -100,16 +110,22 @@ struct fast_perfect_hash : type_hash { //! @return A pair containing the minimum and maximum hash values. template static auto - initialize(const Context& ctx, const std::tuple& options) { + initialize(const Context& ctx, const std::tuple& options) + -> void { if constexpr (Registry::has_runtime_checks) { - initialize( + initialize_aux( ctx, detail::fast_perfect_hash_control, options); } else { std::vector buckets; - initialize(ctx, buckets, options); + initialize_aux(ctx, buckets, options); } + } - return std::pair{min_value, max_value}; + //! Returns the hash range + //! + //! @return A pair containing the minimum and maximum hash values. + static auto hash_range() -> std::pair { + return std::pair{static_::hash_fn.min_value, static_::hash_fn.max_value}; } //! Hash a type id @@ -127,8 +143,7 @@ struct fast_perfect_hash : type_hash { //! @return The hash value BOOST_FORCEINLINE static auto hash(type_id type) -> std::size_t { - auto index = - (mult * reinterpret_cast(type)) >> shift; + auto index = static_::hash_fn(type); if constexpr (Registry::has_runtime_checks) { check(index, type); @@ -146,24 +161,16 @@ struct fast_perfect_hash : type_hash { static auto finalize(const std::tuple&) -> void { detail::fast_perfect_hash_control.clear(); } + + static auto id() -> const void* { + return &static_::hash_fn; + } }; }; -template -std::size_t fast_perfect_hash::fn::mult; - -template -std::size_t fast_perfect_hash::fn::shift; - -template -std::size_t fast_perfect_hash::fn::min_value; - -template -std::size_t fast_perfect_hash::fn::max_value; - template template -void fast_perfect_hash::fn::initialize( +void fast_perfect_hash::fn::initialize_aux( const InitializeContext& ctx, std::vector& buckets, const std::tuple& options) { (void)options; @@ -184,11 +191,11 @@ void fast_perfect_hash::fn::initialize( std::uniform_int_distribution uniform_dist; - for (std::size_t pass = 0; pass < 4; ++pass, ++M) { - shift = 8 * sizeof(type_id) - M; + for (std::size_t pass = 0; pass < 5; ++pass, ++M) { + static_::hash_fn.shift = 8 * sizeof(type_id) - M; auto hash_size = 1 << M; - min_value = (std::numeric_limits::max)(); - max_value = (std::numeric_limits::min)(); + static_::hash_fn.min_value = (std::numeric_limits::max)(); + static_::hash_fn.max_value = (std::numeric_limits::min)(); if constexpr (InitializeContext::template has_option) { ctx.tr << " trying with M = " << M << ", " << hash_size @@ -198,21 +205,21 @@ void fast_perfect_hash::fn::initialize( std::size_t attempts = 0; buckets.resize(hash_size); - while (attempts < 100000) { + while (attempts < 1'00'000) { std::fill( buckets.begin(), buckets.end(), type_id(detail::uintptr_max)); ++attempts; ++total_attempts; - mult = uniform_dist(rnd) | 1; + static_::hash_fn.mult = uniform_dist(rnd) | 1; for (auto iter = ctx.classes_begin(); iter != ctx.classes_end(); ++iter) { for (auto type_iter = iter->type_id_begin(); type_iter != iter->type_id_end(); ++type_iter) { auto type = *type_iter; - auto index = (detail::uintptr(type) * mult) >> shift; - min_value = (std::min)(min_value, index); - max_value = (std::max)(max_value, index); + auto index = static_::hash_fn(type); + static_::hash_fn.min_value = (std::min)(static_::hash_fn.min_value, index); + static_::hash_fn.max_value = (std::max)(static_::hash_fn.max_value, index); if (detail::uintptr(buckets[index]) != detail::uintptr_max) { @@ -224,9 +231,10 @@ void fast_perfect_hash::fn::initialize( } if constexpr (InitializeContext::template has_option) { - ctx.tr << " found " << mult << " after " << total_attempts - << " attempts; span = [" << min_value << ", " - << max_value << "]\n"; + ctx.tr << " found " << static_::hash_fn.mult << " after " + << total_attempts << " attempts; span = [" + << static_::hash_fn.min_value << ", " << static_::hash_fn.max_value + << "]\n"; } return; @@ -248,7 +256,7 @@ void fast_perfect_hash::fn::initialize( template void fast_perfect_hash::fn::check(std::size_t index, type_id type) { - if (index < min_value || index > max_value || + if (index < static_::hash_fn.min_value || index > static_::hash_fn.max_value || detail::fast_perfect_hash_control[index] != type) { if constexpr (Registry::has_error_handler) { @@ -263,8 +271,8 @@ void fast_perfect_hash::fn::check(std::size_t index, type_id type) { template auto fast_perfect_hash::search_error::write(Stream& os) const -> void { - os << "could not find hash factors after " << attempts << "s using " - << buckets << " buckets\n"; + os << "could not find hash factors after " << attempts + << " attempts using up to " << buckets << " buckets\n"; } } // namespace policies diff --git a/include/boost/openmethod/policies/stderr_output.hpp b/include/boost/openmethod/policies/stderr_output.hpp index 68db1fb8..c41f0668 100644 --- a/include/boost/openmethod/policies/stderr_output.hpp +++ b/include/boost/openmethod/policies/stderr_output.hpp @@ -9,7 +9,15 @@ #include #include -namespace boost::openmethod::policies { +namespace boost::openmethod { + +namespace detail { + +BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(os); + +} // namespace detail + +namespace policies { //! @ref Writes to the C standard error stream. //! @@ -17,15 +25,17 @@ namespace boost::openmethod::policies { struct stderr_output : output { //! An OutputFn metafunction. template - struct fn { + struct fn : detail::static_os { //! A @ref LightweightOuputStream. - static detail::ostderr os; + // static detail::ostderr os; // now inherited from static_os + + static auto id() -> const void* { + return &fn::os; + } }; }; -template -detail::ostderr stderr_output::fn::os; - -} // namespace boost::openmethod::policies +} // namespace policies +} // namespace boost::openmethod #endif diff --git a/include/boost/openmethod/policies/vptr_map.hpp b/include/boost/openmethod/policies/vptr_map.hpp index c26e5de2..6e2cab4c 100644 --- a/include/boost/openmethod/policies/vptr_map.hpp +++ b/include/boost/openmethod/policies/vptr_map.hpp @@ -12,6 +12,12 @@ namespace boost::openmethod { +namespace detail { + +BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(vptrs); + +} // namespace detail + namespace policies { //! Stores v-table pointers in a map keyed by `type_id`s. @@ -33,7 +39,8 @@ class vptr_map : public vptr { class fn { using Value = std::conditional_t< Registry::has_indirect_vptr, const vptr_type*, vptr_type>; - static inline typename MapFn::template fn vptrs; + using static_ = detail::static_vptrs< + typename MapFn::template fn, Registry>; public: //! Stores the v-table pointers. @@ -45,7 +52,7 @@ class vptr_map : public vptr { template static void initialize(const Context& ctx, const std::tuple&) { - decltype(vptrs) new_vptrs; + decltype(static_::vptrs) new_vptrs; for (auto iter = ctx.classes_begin(); iter != ctx.classes_end(); ++iter) { @@ -60,7 +67,7 @@ class vptr_map : public vptr { } } - vptrs.swap(new_vptrs); + static_::vptrs.swap(new_vptrs); } //! Returns a reference to a v-table pointer for an object. @@ -80,10 +87,10 @@ class vptr_map : public vptr { template static auto dynamic_vptr(const Class& arg) -> const vptr_type& { auto type = Registry::rtti::dynamic_type(arg); - auto iter = vptrs.find(type); + auto iter = static_::vptrs.find(type); if constexpr (Registry::has_runtime_checks) { - if (iter == vptrs.end()) { + if (iter == static_::vptrs.end()) { if constexpr (Registry::has_error_handler) { missing_class error; error.type = type; @@ -111,7 +118,11 @@ class vptr_map : public vptr { //! @param options A tuple of option objects. template static auto finalize(const std::tuple&) -> void { - vptrs.clear(); + static_::vptrs.clear(); + } + + static auto id() -> const void* { + return &static_::vptrs; } }; }; diff --git a/include/boost/openmethod/policies/vptr_vector.hpp b/include/boost/openmethod/policies/vptr_vector.hpp index 1b1a2768..20a5aafc 100644 --- a/include/boost/openmethod/policies/vptr_vector.hpp +++ b/include/boost/openmethod/policies/vptr_vector.hpp @@ -15,11 +15,8 @@ namespace boost::openmethod { namespace detail { -template -inline std::vector vptr_vector_vptrs; - -template -inline std::vector vptr_vector_indirect_vptrs; +BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(vptr_vector_vptrs); +BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(vptr_vector_indirect_vptrs); } // namespace detail @@ -52,6 +49,13 @@ struct vptr_vector : vptr { typename Registry::template policy; static constexpr auto has_type_hash = !std::is_same_v; + using static_ = std::conditional_t< + Registry::has_indirect_vptr, + detail::static_vptr_vector_indirect_vptrs< + std::vector, Registry>, + detail::static_vptr_vector_vptrs< + std::vector, Registry>>; + //! Stores the v-table pointers. //! //! If `Registry` contains a @ref type_hash policy, its `initialize` @@ -63,13 +67,14 @@ struct vptr_vector : vptr { //! @param ctx A Context object. //! @param options A tuple of option objects. template - static auto initialize( - const Context& ctx, const std::tuple& options) -> void { + static auto + initialize(const Context& ctx, const std::tuple& options) + -> void { std::size_t size; (void)options; if constexpr (has_type_hash) { - auto [_, max_value] = type_hash::initialize(ctx, options); + auto [_, max_value] = type_hash::hash_range(); size = max_value + 1; } else { size = 0; @@ -86,9 +91,9 @@ struct vptr_vector : vptr { } if constexpr (Registry::has_indirect_vptr) { - detail::vptr_vector_indirect_vptrs.resize(size); + static_::vptr_vector_indirect_vptrs.resize(size); } else { - detail::vptr_vector_vptrs.resize(size); + static_::vptr_vector_vptrs.resize(size); } for (auto iter = ctx.classes_begin(); iter != ctx.classes_end(); @@ -104,11 +109,10 @@ struct vptr_vector : vptr { } if constexpr (Registry::has_indirect_vptr) { - detail::vptr_vector_indirect_vptrs[index] = + static_::vptr_vector_indirect_vptrs[index] = &iter->vptr(); } else { - detail::vptr_vector_vptrs[index] = - iter->vptr(); + static_::vptr_vector_vptrs[index] = iter->vptr(); } } } @@ -144,10 +148,9 @@ struct vptr_vector : vptr { std::size_t max_index = 0; if constexpr (Registry::has_indirect_vptr) { - max_index = - detail::vptr_vector_indirect_vptrs.size(); + max_index = static_::vptr_vector_indirect_vptrs.size(); } else { - max_index = detail::vptr_vector_vptrs.size(); + max_index = static_::vptr_vector_vptrs.size(); } if (index >= max_index) { @@ -163,9 +166,9 @@ struct vptr_vector : vptr { } if constexpr (Registry::has_indirect_vptr) { - return *detail::vptr_vector_indirect_vptrs[index]; + return *static_::vptr_vector_indirect_vptrs[index]; } else { - return detail::vptr_vector_vptrs[index]; + return static_::vptr_vector_vptrs[index]; } } @@ -179,9 +182,17 @@ struct vptr_vector : vptr { using namespace policies; if constexpr (Registry::has_indirect_vptr) { - detail::vptr_vector_indirect_vptrs.clear(); + static_::vptr_vector_indirect_vptrs.clear(); + } else { + static_::vptr_vector_vptrs.clear(); + } + } + + static auto id() -> const void* { + if constexpr (Registry::has_indirect_vptr) { + return &static_::vptr_vector_indirect_vptrs; } else { - detail::vptr_vector_vptrs.clear(); + return &static_::vptr_vector_vptrs; } } }; diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index 4f9c3e03..ceca8fd2 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -3,6 +3,7 @@ #include +#include #include #include #include @@ -19,6 +20,9 @@ namespace boost::openmethod { +// ----------------------------------------------------------------------------- +// word + namespace detail { union word { @@ -38,6 +42,9 @@ union word { } // namespace detail +// ----------------------------------------------------------------------------- +// public aliases + //! Alias to v-table pointer type. //! //! `vptr_type` is an alias to the type of a v-table pointer. @@ -50,6 +57,9 @@ using vptr_type = const detail::word*; //! `std::type_info` object), or as an opaque integer type. using type_id = const void*; +// ----------------------------------------------------------------------------- +// virtual types and traits + //! Decorator for virtual parameters. //! //! `virtual_` marks a formal parameter of a method as virtual. It is a @em @@ -71,7 +81,7 @@ struct virtual_; template struct virtual_traits; -// ----------------------------------------------------------------------------- +// ============================================================================= // Error handling //! Base class for all OpenMethod errors. @@ -285,23 +295,8 @@ struct final_error : openmethod_error { namespace detail { -struct empty {}; - -template -struct range { - range(Iterator first, Iterator last) : first(first), last(last) { - } - - Iterator first, last; - - auto begin() const -> Iterator { - return first; - } - - auto end() const -> Iterator { - return last; - } -}; +// ============================================================================= +// generic registrars // ----------------------------------------------------------------------------- // class info @@ -312,8 +307,8 @@ struct class_info : static_list::static_link { type_id *first_base, *last_base; bool is_abstract{false}; - auto vptr() const { - return static_vptr; + auto vptr() const -> const vptr_type& { + return *static_vptr; } auto type_id_begin() const { @@ -370,12 +365,15 @@ struct deferred_overrider_info : overrider_info { virtual void resolve_type_ids() = 0; }; -struct unspecified {}; - } // namespace detail +// ============================================================================= +// initialize options + #ifdef __MRDOCS__ +struct unspecified {}; + //! Blueprint for a lightweight output stream (exposition only). //! //! Classes used as output streams in policies must provide the operations @@ -396,6 +394,9 @@ struct LightweightOutputStream { #endif +// ----------------------------------------------------------------------------- +// n2216 + //! N2216 ambiguity resolution. //! //! If `n2216` is present in @ref initialize\'s `Options`, additional steps are @@ -413,6 +414,9 @@ struct LightweightOutputStream { //! the same program. struct n2216 {}; +// ----------------------------------------------------------------------------- +// trace + //! Enable `initialize` tracing. //! //! If `trace` is passed to @ref initialize, tracing code is added to various @@ -453,6 +457,9 @@ inline trace trace::from_env() { #endif } +// ============================================================================= +// policies + //! Namespace for policies. //! //! Classes with snake case names are "blueprints", i.e. exposition-only classes @@ -563,6 +570,9 @@ struct RttiFn { #endif +// ----------------------------------------------------------------------------- +// rtti + //! Policy for manipulating type information. //! //! `rtti` policies are responsible for type information acquisition and dynamic @@ -602,6 +612,9 @@ struct rtti { }; }; +// ----------------------------------------------------------------------------- +// deferred rtti + //! Policy for deferred type id collection. //! //! Some custom RTTI systems rely on static constructors to assign type ids. @@ -611,6 +624,9 @@ struct rtti { //! of type ids to be deferred until the first call to @ref update. struct deferred_static_rtti : rtti {}; +// ----------------------------------------------------------------------------- +// error handler + #ifdef __MRDOCS__ //! Blueprint for @ref error_handler metafunctions (exposition only). template @@ -640,6 +656,9 @@ struct error_handler { using category = error_handler; }; +// ----------------------------------------------------------------------------- +// vptr + #ifdef __MRDOCS__ //! Blueprint for `vptr` metafunctions (exposition only). @@ -705,6 +724,9 @@ struct indirect_vptr final { struct fn {}; }; +// ----------------------------------------------------------------------------- +// type_hash + #ifdef __MRDOCS__ //! Blueprint for @ref type_hash metafunctions (exposition only). //! @@ -764,6 +786,9 @@ struct OutputFn { #endif +// ----------------------------------------------------------------------------- +// output + //! Policy for writing diagnostics and trace. //! //! If an `output` policy is present, the default error handler uses it to write @@ -781,6 +806,9 @@ struct output { using category = output; }; +// ----------------------------------------------------------------------------- +// runtime_checks + //! Policy for post-initialize runtime checks. //! //! If this policy is present, performs the following checks: @@ -796,32 +824,56 @@ struct runtime_checks final { } // namespace policies +// ----------------------------------------------------------------------------- +// registry and policy helpers + namespace detail { struct registry_base {}; +template +struct registry_state { + static_list classes; + static_list methods; + bool initialized; + std::vector dispatch_data; +}; + template constexpr bool is_registry = std::is_base_of_v; template constexpr bool is_not_void = !std::is_same_v; -template< - class Registry, class Index, - class Size = mp11::mp_size> +template +struct find_first_derived_of_aux; + +template +using find_first_derived_of = + typename find_first_derived_of_aux::type; + +template +struct find_first_derived_of_aux, Default> { + using type = Default; +}; + +template +struct find_first_derived_of_aux, Default> { + using type = std::conditional_t< + std::is_base_of_v, First, + find_first_derived_of, Default>>; +}; + +template struct get_policy_aux { - using type = typename mp11::mp_at< - typename Registry::policy_list, Index>::template fn; + using type = typename Policy::template fn; }; -template -struct get_policy_aux { +template +struct get_policy_aux { using type = void; }; -using class_catalog = detail::static_list; -using method_catalog = detail::static_list; - template struct with_aux; @@ -881,6 +933,74 @@ struct initialize_aux; constexpr bool BOOST_PP_CAT(has_, FN) = \ BOOST_PP_CAT(has_, BOOST_PP_CAT(FN, _aux))::value +// ----------------------------------------------------------------------------- +// import/export + +struct declspec {}; +struct dllexport : declspec {}; +struct dllimport : declspec {}; +struct declspec_none : declspec {}; + +namespace detail { + +#define BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(ID, ...) \ + template \ + struct BOOST_PP_CAT(static_, ID) { \ + using declspec = void; \ + static Type ID; \ + }; \ + \ + template \ + Type BOOST_PP_CAT(static_, ID)::ID __VA_ARGS__; \ + \ + template \ + struct BOOST_PP_CAT(static_, ID)< \ + Type, Guide, \ + std::enable_if_t, dllexport>>> { \ + using declspec = dllexport; \ + static BOOST_SYMBOL_EXPORT Type ID __VA_ARGS__; \ + }; \ + \ + template \ + Type BOOST_PP_CAT(static_, ID)< \ + Type, Guide, \ + std::enable_if_t, dllexport>>>:: \ + ID; \ + \ + template \ + struct BOOST_PP_CAT(static_, ID)< \ + Type, Guide, \ + std::enable_if_t, dllimport>>> { \ + using declspec = dllimport; \ + static BOOST_SYMBOL_IMPORT Type ID; \ + } + +template +using get_attributes = + decltype(boost_openmethod_declspec(std::declval())); + +BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(st); + +BOOST_OPENMETHOD_DETAIL_HAS_STATIC_FN(id); + +} // namespace detail + +namespace policies { + +struct attributes { + using category = attributes; +}; + +template +struct attributes_guide final : attributes { + using guide_type = GuideType; + + template + struct fn {}; +}; + +} // namespace policies + //! Methods, classes and policies. //! //! Methods exist in the context of a registry. Any class used as a method or @@ -931,21 +1051,27 @@ struct initialize_aux; //! //! @see @ref policies template -class registry : detail::registry_base { - static detail::class_catalog classes; - static detail::method_catalog methods; +class registry : public detail::registry_base { template friend struct detail::use_class_aux; template friend class method; - static std::vector dispatch_data; - static bool initialized; + using static_ = detail::static_st< + detail::registry_state>, + typename detail::find_first_derived_of< + policies::attributes, mp11::mp_list, + policies::attributes_guide>>::guide_type>; public: //! The type of this registry. using registry_type = registry; + using declspec = typename static_::declspec; + + static const void* id() { + return static_cast(&static_::st.classes); + } template struct compiler; @@ -971,7 +1097,7 @@ class registry : detail::registry_base { //! //! @tparam Class A registered class. template - static vptr_type static_vptr; + inline static vptr_type static_vptr; //! List of policies selected in a registry. //! @@ -990,11 +1116,7 @@ class registry : detail::registry_base { //! @tparam A policy. template using policy = typename detail::get_policy_aux< - registry, - mp11::mp_find_if_q< - policy_list, - mp11::mp_bind_front_q< - mp11::mp_quote_trait, Category>>>::type; + registry, detail::find_first_derived_of>::type; //! Add or replace policies. //! @@ -1052,26 +1174,10 @@ class registry : detail::registry_base { !std::is_same_v, void>; }; -template -detail::class_catalog registry::classes; - -template -detail::method_catalog registry::methods; - -template -std::vector registry::dispatch_data; - -template -bool registry::initialized; - -template -template -vptr_type registry::static_vptr; - template void registry::require_initialized() { if constexpr (registry::has_runtime_checks) { - if (!initialized) { + if (!static_::st.initialized) { if constexpr (registry::has_error_handler) { error_handler::error(not_initialized()); } @@ -1103,6 +1209,8 @@ auto final_error::write(Stream& os) const { Registry::rtti::type_name(dynamic_type, os); } +struct default_registry_attributes; + } // namespace boost::openmethod #ifdef _MSC_VER diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 06c2573e..18d77aec 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -104,3 +104,7 @@ openmethod_compile_fail_test( compile_fail_repeated_inheritance "repeated inheritance") openmethod_compile_fail_test( compile_fail_override_method_not_found "cannot find 'speak' method that accepts the same arguments as the overrider") + +if (TARGET Boost::dll) + add_subdirectory(dynamic_loading) +endif() diff --git a/test/dynamic_loading/CMakeLists.txt b/test/dynamic_loading/CMakeLists.txt new file mode 100644 index 00000000..10b62c6f --- /dev/null +++ b/test/dynamic_loading/CMakeLists.txt @@ -0,0 +1,58 @@ +# Copyright (c) 2018-2025 Jean-Louis Leroy +# Distributed under the Boost Software License, Version 1.0. +# See accompanying file LICENSE_1_0.txt +# or copy at http://www.boost.org/LICENSE_1_0.txt) + +# lib_registry: exports the default registry's static policy state +add_library(dl_test_registry SHARED EXCLUDE_FROM_ALL lib_registry.cpp) +target_compile_definitions( + dl_test_registry PRIVATE BOOST_OPENMETHOD_DYNAMIC_LOADING_REGISTRY_EXPORTS) +target_link_libraries(dl_test_registry PUBLIC Boost::openmethod PRIVATE Boost::dll) + +# lib_method: exports the speak method; imports registry state from lib_registry +add_library(dl_test_method SHARED EXCLUDE_FROM_ALL lib_method.cpp) +target_compile_definitions( + dl_test_method PRIVATE BOOST_OPENMETHOD_DYNAMIC_LOADING_METHOD_EXPORTS) +target_link_libraries(dl_test_method PUBLIC Boost::openmethod dl_test_registry PRIVATE Boost::dll) + +# lib_overrider: adds a Dog overrider; imported at runtime via Boost.DLL +add_library(dl_test_overrider SHARED EXCLUDE_FROM_ALL lib_overrider.cpp) +target_link_libraries(dl_test_overrider PRIVATE Boost::openmethod dl_test_method Boost::dll) + +# Test executable: links against lib_method (and lib_registry transitively); +# dynamically loads lib_overrider at runtime. +add_executable( + boost_openmethod-test_dynamic_loading EXCLUDE_FROM_ALL main.cpp) +target_link_libraries( + boost_openmethod-test_dynamic_loading + PRIVATE Boost::openmethod Boost::unit_test_framework Boost::dll) + +# Put all outputs in the same directory so Boost.DLL can locate the shared +# libraries relative to the test executable at runtime. +# ENABLE_EXPORTS is required so that symbols from each shared library are +# visible to other shared libraries loaded at runtime (e.g. dl_test_overrider +# can resolve symbols from dl_test_method/dl_test_registry). +# CXX_VISIBILITY_PRESET must be "default" so that the template static variables +# used as the shared registry state (policy statics) are exported with DEFAULT +# ELF visibility. With hidden visibility (as set by BoostRoot.cmake in the +# super-project build) they become UNIQUE HIDDEN and are not deduplicated by +# the dynamic linker, breaking cross-DSO state sharing. +foreach( + target dl_test_registry dl_test_method dl_test_overrider + boost_openmethod-test_dynamic_loading) + set_target_properties( + ${target} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + ENABLE_EXPORTS ON + CXX_VISIBILITY_PRESET default + VISIBILITY_INLINES_HIDDEN OFF) +endforeach() + +add_test( + NAME boost_openmethod-test_dynamic_loading + COMMAND boost_openmethod-test_dynamic_loading) +add_dependencies( + tests boost_openmethod-test_dynamic_loading dl_test_overrider) diff --git a/test/dynamic_loading/classes.hpp b/test/dynamic_loading/classes.hpp new file mode 100644 index 00000000..11c8486b --- /dev/null +++ b/test/dynamic_loading/classes.hpp @@ -0,0 +1,18 @@ +// Copyright (c) 2018-2025 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#pragma once + +#include "registry.hpp" + +#include + +struct Animal { + virtual ~Animal() = default; +}; + +struct Dog : Animal {}; + +BOOST_OPENMETHOD_CLASSES(Animal, Dog); diff --git a/test/dynamic_loading/lib_method.cpp b/test/dynamic_loading/lib_method.cpp new file mode 100644 index 00000000..b1571c88 --- /dev/null +++ b/test/dynamic_loading/lib_method.cpp @@ -0,0 +1,40 @@ +// Copyright (c) 2018-2025 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +// BOOST_OPENMETHOD_DYNAMIC_LOADING_METHOD_EXPORTS is set via CMake compile +// definition, making METHOD_API expand to dllexport in method.hpp. +#include "method.hpp" +#include + +using namespace boost::openmethod; +namespace mp11 = boost::mp11; + +using Method = BOOST_OPENMETHOD_TYPE(speak, (virtual_ptr), std::string); + +constexpr auto n_policies = mp11::mp_size::value; + +static auto get_policy_ids() -> const void** { + static const void* ids[n_policies + 1]; + static bool init = [] { + std::size_t i = 0; + mp11::mp_for_each([&](auto p) { + using P = decltype(p); + if constexpr (detail::has_id>) { + ids[i++] = default_registry::policy

::id(); + } + }); + ids[i] = nullptr; + return true; + }(); + (void)init; + return ids; +} + +static auto get_method_fn() -> const void* { + return static_cast(&Method::fn); +} + +BOOST_DLL_ALIAS(get_policy_ids, dl_method_get_policy_ids) +BOOST_DLL_ALIAS(get_method_fn, dl_method_get_method_fn) diff --git a/test/dynamic_loading/lib_overrider.cpp b/test/dynamic_loading/lib_overrider.cpp new file mode 100644 index 00000000..7485d1e9 --- /dev/null +++ b/test/dynamic_loading/lib_overrider.cpp @@ -0,0 +1,42 @@ +// Copyright (c) 2018-2025 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#include "method.hpp" +#include + +using namespace boost::openmethod; +namespace mp11 = boost::mp11; + +BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), std::string) { + return "woof"; +} + +using Method = BOOST_OPENMETHOD_TYPE(speak, (virtual_ptr), std::string); + +constexpr auto n_policies = mp11::mp_size::value; + +static auto get_policy_ids() -> const void** { + static const void* ids[n_policies + 1]; + static bool init = [] { + std::size_t i = 0; + mp11::mp_for_each([&](auto p) { + using P = decltype(p); + if constexpr (detail::has_id>) { + ids[i++] = default_registry::policy

::id(); + } + }); + ids[i] = nullptr; + return true; + }(); + (void)init; + return ids; +} + +static auto get_method_fn() -> const void* { + return static_cast(&Method::fn); +} + +BOOST_DLL_ALIAS(get_policy_ids, dl_overrider_get_policy_ids) +BOOST_DLL_ALIAS(get_method_fn, dl_overrider_get_method_fn) diff --git a/test/dynamic_loading/lib_registry.cpp b/test/dynamic_loading/lib_registry.cpp new file mode 100644 index 00000000..e94f0b08 --- /dev/null +++ b/test/dynamic_loading/lib_registry.cpp @@ -0,0 +1,36 @@ +// Copyright (c) 2018-2025 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +// BOOST_OPENMETHOD_DYNAMIC_LOADING_REGISTRY_EXPORTS is set via CMake compile +// definition, making REGISTRY_API expand to dllexport in registry.hpp (and +// transitively in classes.hpp). Including classes.hpp forces +// static_st>::st to be instantiated and exported from +// this shared library, so other libraries can import it. +#include "classes.hpp" +#include + +using namespace boost::openmethod; +namespace mp11 = boost::mp11; + +constexpr auto n_policies = mp11::mp_size::value; + +static auto get_policy_ids() -> const void** { + static const void* ids[n_policies + 1]; + static bool init = [] { + std::size_t i = 0; + mp11::mp_for_each([&](auto p) { + using P = decltype(p); + if constexpr (detail::has_id>) { + ids[i++] = default_registry::policy

::id(); + } + }); + ids[i] = nullptr; + return true; + }(); + (void)init; + return ids; +} + +BOOST_DLL_ALIAS(get_policy_ids, dl_registry_get_policy_ids) diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp new file mode 100644 index 00000000..b4109d8d --- /dev/null +++ b/test/dynamic_loading/main.cpp @@ -0,0 +1,86 @@ +// Copyright (c) 2018-2025 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#define BOOST_TEST_MODULE dynamic_loading +#include + +#include "method.hpp" + +#include +#include +#include + +using namespace boost::openmethod; +namespace mp11 = boost::mp11; + +constexpr auto n_policies = mp11::mp_size::value; + +using policy_ids_fn = const void**(); +using method_fn_fn = const void*(); + +BOOST_AUTO_TEST_CASE(test_shared_state) { + namespace dll = boost::dll; + auto lib_dir = dll::program_location().parent_path(); + + // Load all three shared libraries via Boost.DLL. + // Use rtld_global for registry and method so their symbols (fn, policy + // statics) are visible globally and win over any locally-instantiated + // copies when the overrider library is subsequently loaded. +#ifdef _WIN32 + constexpr auto global_mode = dll::load_mode::append_decorations; +#else + constexpr auto global_mode = + dll::load_mode::append_decorations /*| dll::load_mode::rtld_global*/; +#endif + dll::shared_library registry_lib(lib_dir / "dl_test_registry", global_mode); + dll::shared_library method_lib(lib_dir / "dl_test_method", global_mode); + dll::shared_library overrider_lib( + lib_dir / "dl_test_overrider", dll::load_mode::append_decorations); + + auto& registry_get_policy_ids = + registry_lib.get_alias("dl_registry_get_policy_ids"); + auto& method_get_policy_ids = + method_lib.get_alias("dl_method_get_policy_ids"); + auto& method_get_method_fn = + method_lib.get_alias("dl_method_get_method_fn"); + auto& overrider_get_policy_ids = + overrider_lib.get_alias("dl_overrider_get_policy_ids"); + auto& overrider_get_method_fn = + overrider_lib.get_alias("dl_overrider_get_method_fn"); + + // Build local policy id array for comparison + const void* local_ids[n_policies + 1]; + { + std::size_t i = 0; + mp11::mp_for_each([&](auto p) { + using P = decltype(p); + if constexpr (detail::has_id>) { + local_ids[i++] = default_registry::policy

::id(); + } + }); + local_ids[i] = nullptr; + } + + const void** registry_ids = registry_get_policy_ids(); + const void** method_ids = method_get_policy_ids(); + const void** overrider_ids = overrider_get_policy_ids(); + + // All libraries must share the same policy static variables + for (std::size_t i = 0; local_ids[i] != nullptr; ++i) { + BOOST_TEST(registry_ids[i] == local_ids[i]); + BOOST_TEST(method_ids[i] == local_ids[i]); + BOOST_TEST(overrider_ids[i] == local_ids[i]); + } + + // All libraries must see the same method object + BOOST_TEST(method_get_method_fn() == overrider_get_method_fn()); + + // Build dispatch tables (including the Dog overrider from the loaded lib) + boost::openmethod::initialize(); + + // Verify dispatch via the method + Dog dog; + BOOST_TEST(speak(dog) == "woof"); +} diff --git a/test/dynamic_loading/method.hpp b/test/dynamic_loading/method.hpp new file mode 100644 index 00000000..5f14f930 --- /dev/null +++ b/test/dynamic_loading/method.hpp @@ -0,0 +1,21 @@ +// Copyright (c) 2018-2025 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#pragma once + +#include "classes.hpp" + +#if !defined(_MSC_VER) +#define METHOD_API boost::openmethod::declspec_none +#elif defined(BOOST_OPENMETHOD_DYNAMIC_LOADING_METHOD_EXPORTS) +#define METHOD_API boost::openmethod::dllexport +#else +#define METHOD_API boost::openmethod::dllimport +#endif + +#include + +BOOST_OPENMETHOD( + speak, (boost::openmethod::virtual_ptr), std::string, METHOD_API); diff --git a/test/dynamic_loading/registry.hpp b/test/dynamic_loading/registry.hpp new file mode 100644 index 00000000..beb619fe --- /dev/null +++ b/test/dynamic_loading/registry.hpp @@ -0,0 +1,20 @@ +// Copyright (c) 2018-2025 Jean-Louis Leroy +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt +// or copy at http://www.boost.org/LICENSE_1_0.txt) + +#pragma once + +#if !defined(_MSC_VER) +#define REGISTRY_API boost::openmethod::declspec_none +#elif BOOST_OPENMETHOD_DYNAMIC_LOADING_REGISTRY_EXPORTS +#define REGISTRY_API boost::openmethod::dllexport +#else +#define REGISTRY_API boost::openmethod::dllimport +#endif + +#include + +namespace boost::openmethod { +REGISTRY_API boost_openmethod_declspec(default_registry_attributes); +} diff --git a/test/test_class_registration.cpp b/test/test_class_registration.cpp index 55b39f8f..119409a7 100644 --- a/test/test_class_registration.cpp +++ b/test/test_class_registration.cpp @@ -22,6 +22,13 @@ struct Animal { struct Dog : Animal {}; struct Bulldog : Dog {}; +BOOST_AUTO_TEST_CASE(registry_own_state) { + using r1 = test_registry_<__COUNTER__>; + using r2 = test_registry_<__COUNTER__>; + static_assert(!std::is_same_v); + BOOST_CHECK_NE(r1::id(), r2::id()); +} + namespace TEST_NS { struct registry : test_registry_<__COUNTER__>::with< diff --git a/test/test_compiler.cpp b/test/test_compiler.cpp index a6e77323..5908d358 100644 --- a/test/test_compiler.cpp +++ b/test/test_compiler.cpp @@ -22,7 +22,7 @@ using overrider = detail::generic_compiler::overrider; auto operator<<(std::ostream& os, const class_* cls) -> std::ostream& { return os - << reinterpret_cast(cls->type_ids[0])->name(); + << reinterpret_cast(cls->ci[0]->type)->name(); } std::string empty = "{}"; diff --git a/test/test_core.cpp b/test/test_core.cpp index ab5e90b1..2523632b 100644 --- a/test/test_core.cpp +++ b/test/test_core.cpp @@ -126,8 +126,8 @@ static_assert( is_virtual, mp11::mp_list, b, virtual_>>>>, mp11::mp_list>); -struct registry1 : default_registry::with> {}; -struct registry2 : default_registry::with> {}; +using registry1 = test_registry_<__COUNTER__>; +using registry2 = test_registry_<__COUNTER__>; struct non_polymorphic_inplace_vptr {}; diff --git a/test/test_dispatch.cpp b/test/test_dispatch.cpp index 5cd36396..5c803e4b 100644 --- a/test/test_dispatch.cpp +++ b/test/test_dispatch.cpp @@ -335,17 +335,6 @@ BOOST_AUTO_TEST_CASE(simple) { BOOST_TEST( times(diag, 2) == string_pair(DIAGONAL_SCALAR, MATRIX_SCALAR)); } - - if constexpr (std::is_same_v) { - BOOST_TEST( - !detail::vptr_vector_vptrs.empty()); - finalize(); - static_assert(detail::has_finalize_aux< - void, test_registry::policy, - std::tuple<>>::value); - BOOST_TEST( - detail::vptr_vector_vptrs.empty()); - } } } // namespace TEST_NS diff --git a/test/test_policies.cpp b/test/test_policies.cpp index 41317cea..79f2f61d 100644 --- a/test/test_policies.cpp +++ b/test/test_policies.cpp @@ -36,8 +36,8 @@ static_assert(detail::is_registry); struct not_a_policy {}; static_assert(!detail::is_registry); -struct registry1 : default_registry::with> {}; -struct registry2 : default_registry::with> {}; +using registry1 = test_registry_<__COUNTER__>; +using registry2 = test_registry_<__COUNTER__>; struct foo { using category = foo; @@ -70,3 +70,13 @@ BOOST_AUTO_TEST_CASE(test_registry) { BOOST_TEST(®istry2::static_vptr != ®istry1::static_vptr); // BOOST_TEST(®istry2::dispatch_data != ®istry1::dispatch_data); } + +static_assert(has_initialize< + vptr_vector::fn, registry1::compiler>, + std::tuple<>>); +static_assert(!has_initialize< + std_rtti::fn, registry1::compiler>, + std::tuple<>>); +static_assert(has_initialize< + fast_perfect_hash::fn, + registry1::compiler>, std::tuple<>>); diff --git a/test/test_util.hpp b/test/test_util.hpp index 5562a620..ddbdabdb 100644 --- a/test/test_util.hpp +++ b/test/test_util.hpp @@ -11,19 +11,19 @@ #include #include -template -struct unique final { - using category = unique; +struct unique_category { + using category = unique_category; +}; + +template +struct unique final : unique_category { template struct fn {}; }; template -struct test_registry_ : boost::openmethod::default_registry::with< - unique>, Policies...> { - using registry_type = boost::openmethod::default_registry::with< - unique>, Policies...>; -}; +struct test_registry_ + : boost::openmethod::default_registry::with, Policies...> {}; #define TEST_NS BOOST_PP_CAT(test, __COUNTER__) From 189a37d8a5ac0b17d8d472bd396b4b15fac67539 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 8 Mar 2026 17:47:17 -0400 Subject: [PATCH 03/74] support dynamic loading on Windows --- .../ROOT/examples/shared_libs/animals.hpp | 2 +- include/boost/openmethod/core.hpp | 4 +- include/boost/openmethod/default_registry.hpp | 2 +- .../policies/default_error_handler.hpp | 13 +-- .../openmethod/policies/fast_perfect_hash.hpp | 42 +++++--- .../openmethod/policies/stderr_output.hpp | 5 +- .../boost/openmethod/policies/vptr_map.hpp | 4 +- .../boost/openmethod/policies/vptr_vector.hpp | 6 +- include/boost/openmethod/preamble.hpp | 100 ++++++++++-------- test/dynamic_loading/CMakeLists.txt | 6 +- test/dynamic_loading/classes.hpp | 6 -- test/dynamic_loading/lib_method.cpp | 46 ++++---- test/dynamic_loading/lib_overrider.cpp | 33 ++---- test/dynamic_loading/lib_registry.cpp | 39 +++---- test/dynamic_loading/main.cpp | 80 ++++++++++++-- test/dynamic_loading/method.hpp | 29 +++-- test/dynamic_loading/registry.hpp | 45 ++++++-- test/test_runtime_errors.cpp | 3 +- 18 files changed, 283 insertions(+), 182 deletions(-) diff --git a/doc/modules/ROOT/examples/shared_libs/animals.hpp b/doc/modules/ROOT/examples/shared_libs/animals.hpp index e0ac8aba..9ea98f1c 100644 --- a/doc/modules/ROOT/examples/shared_libs/animals.hpp +++ b/doc/modules/ROOT/examples/shared_libs/animals.hpp @@ -28,7 +28,7 @@ #endif namespace boost::openmethod { - ANIMALS_API boost_openmethod_declspec(default_registry_attributes); + ANIMALS_API boost_openmethod_declspec(default_registry&); } #include diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index d3793731..f49b2d62 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -2166,11 +2166,11 @@ template< class method : public detail::method_base, public detail::static_fn< - method> { + Registry, method> { template struct override_aux; - friend struct detail::static_fn; + friend struct detail::static_fn; // Aliases used in implementation only. Everything extracted from template // arguments is capitalized like the arguments themselves. diff --git a/include/boost/openmethod/default_registry.hpp b/include/boost/openmethod/default_registry.hpp index 178c8157..2e5aa468 100644 --- a/include/boost/openmethod/default_registry.hpp +++ b/include/boost/openmethod/default_registry.hpp @@ -42,7 +42,7 @@ struct default_registry policies::std_rtti, policies::fast_perfect_hash, policies::vptr_vector, policies::default_error_handler, policies::stderr_output, - policies::attributes_guide + policies::declspec #ifdef BOOST_OPENMETHOD_ENABLE_RUNTIME_CHECKS , policies::runtime_checks diff --git a/include/boost/openmethod/policies/default_error_handler.hpp b/include/boost/openmethod/policies/default_error_handler.hpp index df056e96..3f6fcb49 100644 --- a/include/boost/openmethod/policies/default_error_handler.hpp +++ b/include/boost/openmethod/policies/default_error_handler.hpp @@ -85,8 +85,10 @@ struct default_error_handler : error_handler { //! The type of the error handler function object. using function_type = std::function; - using static_ = - detail::static_handler; + using static_ = detail::static_handler< + Registry, function_type, + typename Registry::template policy< + policies::declspec_policy>::guide_type>; //! Calls a function with the error object, wrapped in an @ref //! error_variant. @@ -95,8 +97,8 @@ struct default_error_handler : error_handler { //! @param error The error object. template static auto error(const Error& error) -> void { - auto handler = static_::handler ? static_::handler - : default_handler; + auto handler = + static_::handler ? static_::handler : default_handler; handler(error_variant(error)); } @@ -109,8 +111,7 @@ struct default_error_handler : error_handler { //! @return The previous function. // coverity[auto_causes_copy] static auto set(function_type new_handler) -> function_type { - auto prev = std::exchange( - static_::handler, std::move(new_handler)); + auto prev = std::exchange(static_::handler, std::move(new_handler)); return prev ? prev : default_handler; } diff --git a/include/boost/openmethod/policies/fast_perfect_hash.hpp b/include/boost/openmethod/policies/fast_perfect_hash.hpp index 550e9e8b..ec164c3b 100644 --- a/include/boost/openmethod/policies/fast_perfect_hash.hpp +++ b/include/boost/openmethod/policies/fast_perfect_hash.hpp @@ -43,9 +43,7 @@ struct hash_fn { }; BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(hash_fn); - -template -std::vector fast_perfect_hash_control; +BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(origin); } // namespace detail @@ -87,7 +85,12 @@ struct fast_perfect_hash : type_hash { //! @tparam Registry The registry containing this policy template class fn { - using static_ = detail::static_hash_fn; + using guide_type = typename Registry::template policy< + policies::declspec_policy>::guide_type; + using static_ = + detail::static_hash_fn; + using control = + detail::static_origin, guide_type>; static void check(std::size_t index, type_id type); template @@ -96,6 +99,8 @@ struct fast_perfect_hash : type_hash { const std::tuple& options); public: + using declspec = typename static_::declspec; + //! Finds the hash factors //! //! Attempts to find suitable values for the multiplication factor `M` @@ -113,8 +118,7 @@ struct fast_perfect_hash : type_hash { initialize(const Context& ctx, const std::tuple& options) -> void { if constexpr (Registry::has_runtime_checks) { - initialize_aux( - ctx, detail::fast_perfect_hash_control, options); + initialize_aux(ctx, control::origin, options); } else { std::vector buckets; initialize_aux(ctx, buckets, options); @@ -125,7 +129,8 @@ struct fast_perfect_hash : type_hash { //! //! @return A pair containing the minimum and maximum hash values. static auto hash_range() -> std::pair { - return std::pair{static_::hash_fn.min_value, static_::hash_fn.max_value}; + return std::pair{ + static_::hash_fn.min_value, static_::hash_fn.max_value}; } //! Hash a type id @@ -159,7 +164,9 @@ struct fast_perfect_hash : type_hash { //! @param options Zero or more option objects. template static auto finalize(const std::tuple&) -> void { - detail::fast_perfect_hash_control.clear(); + if constexpr (Registry::has_runtime_checks) { + control::origin.clear(); + } } static auto id() -> const void* { @@ -178,7 +185,10 @@ void fast_perfect_hash::fn::initialize_aux( const auto N = std::distance(ctx.classes_begin(), ctx.classes_end()); if constexpr (mp11::mp_contains, trace>::value) { - Registry::output::os << "Finding hash factor for " << N << " types\n"; + if (std::get(options).on) { + Registry::output::os << "Finding hash factor for " << N + << " types\n"; + } } std::default_random_engine rnd(13081963); @@ -218,8 +228,10 @@ void fast_perfect_hash::fn::initialize_aux( type_iter != iter->type_id_end(); ++type_iter) { auto type = *type_iter; auto index = static_::hash_fn(type); - static_::hash_fn.min_value = (std::min)(static_::hash_fn.min_value, index); - static_::hash_fn.max_value = (std::max)(static_::hash_fn.max_value, index); + static_::hash_fn.min_value = + (std::min)(static_::hash_fn.min_value, index); + static_::hash_fn.max_value = + (std::max)(static_::hash_fn.max_value, index); if (detail::uintptr(buckets[index]) != detail::uintptr_max) { @@ -233,8 +245,8 @@ void fast_perfect_hash::fn::initialize_aux( if constexpr (InitializeContext::template has_option) { ctx.tr << " found " << static_::hash_fn.mult << " after " << total_attempts << " attempts; span = [" - << static_::hash_fn.min_value << ", " << static_::hash_fn.max_value - << "]\n"; + << static_::hash_fn.min_value << ", " + << static_::hash_fn.max_value << "]\n"; } return; @@ -256,8 +268,8 @@ void fast_perfect_hash::fn::initialize_aux( template void fast_perfect_hash::fn::check(std::size_t index, type_id type) { - if (index < static_::hash_fn.min_value || index > static_::hash_fn.max_value || - detail::fast_perfect_hash_control[index] != type) { + if (index < static_::hash_fn.min_value || + index > static_::hash_fn.max_value || control::origin[index] != type) { if constexpr (Registry::has_error_handler) { missing_class error; diff --git a/include/boost/openmethod/policies/stderr_output.hpp b/include/boost/openmethod/policies/stderr_output.hpp index c41f0668..1c95c7cf 100644 --- a/include/boost/openmethod/policies/stderr_output.hpp +++ b/include/boost/openmethod/policies/stderr_output.hpp @@ -25,7 +25,10 @@ namespace policies { struct stderr_output : output { //! An OutputFn metafunction. template - struct fn : detail::static_os { + struct fn : detail::static_os< + Registry, detail::ostderr, + typename Registry::template policy< + policies::declspec_policy>::guide_type> { //! A @ref LightweightOuputStream. // static detail::ostderr os; // now inherited from static_os diff --git a/include/boost/openmethod/policies/vptr_map.hpp b/include/boost/openmethod/policies/vptr_map.hpp index 6e2cab4c..79547f19 100644 --- a/include/boost/openmethod/policies/vptr_map.hpp +++ b/include/boost/openmethod/policies/vptr_map.hpp @@ -40,7 +40,9 @@ class vptr_map : public vptr { using Value = std::conditional_t< Registry::has_indirect_vptr, const vptr_type*, vptr_type>; using static_ = detail::static_vptrs< - typename MapFn::template fn, Registry>; + Registry, typename MapFn::template fn, + typename Registry::template policy< + policies::declspec_policy>::guide_type>; public: //! Stores the v-table pointers. diff --git a/include/boost/openmethod/policies/vptr_vector.hpp b/include/boost/openmethod/policies/vptr_vector.hpp index 20a5aafc..28b30d33 100644 --- a/include/boost/openmethod/policies/vptr_vector.hpp +++ b/include/boost/openmethod/policies/vptr_vector.hpp @@ -52,9 +52,11 @@ struct vptr_vector : vptr { using static_ = std::conditional_t< Registry::has_indirect_vptr, detail::static_vptr_vector_indirect_vptrs< - std::vector, Registry>, + Registry, std::vector, + typename Registry::declspec_guide>, detail::static_vptr_vector_vptrs< - std::vector, Registry>>; + Registry, std::vector, + typename Registry::declspec_guide>>; //! Stores the v-table pointers. //! diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index ceca8fd2..86b7513a 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -739,8 +739,8 @@ struct TypeHashFn { //! blueprint. //! @return A pair containing the minimum and maximum hash values. template - static auto - initialize(const Context& ctx) -> std::pair; + static auto initialize(const Context& ctx) + -> std::pair; //! Hash a `type_id`. //! @@ -864,16 +864,21 @@ struct find_first_derived_of_aux, Default> { find_first_derived_of, Default>>; }; -template +template struct get_policy_aux { using type = typename Policy::template fn; }; template -struct get_policy_aux { +struct get_policy_aux { using type = void; }; +template +struct get_policy_aux { + using type = typename Default::template fn; +}; + template struct with_aux; @@ -944,32 +949,33 @@ struct declspec_none : declspec {}; namespace detail { #define BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(ID, ...) \ - template \ + template \ struct BOOST_PP_CAT(static_, ID) { \ using declspec = void; \ static Type ID; \ }; \ \ - template \ - Type BOOST_PP_CAT(static_, ID)::ID __VA_ARGS__; \ + template \ + Type BOOST_PP_CAT( \ + static_, ID)::ID __VA_ARGS__; \ \ - template \ + template \ struct BOOST_PP_CAT(static_, ID)< \ - Type, Guide, \ + Registry, Type, Guide, \ std::enable_if_t, dllexport>>> { \ using declspec = dllexport; \ static BOOST_SYMBOL_EXPORT Type ID __VA_ARGS__; \ }; \ \ - template \ + template \ Type BOOST_PP_CAT(static_, ID)< \ - Type, Guide, \ + Registry, Type, Guide, \ std::enable_if_t, dllexport>>>:: \ ID; \ \ - template \ + template \ struct BOOST_PP_CAT(static_, ID)< \ - Type, Guide, \ + Registry, Type, Guide, \ std::enable_if_t, dllimport>>> { \ using declspec = dllimport; \ static BOOST_SYMBOL_IMPORT Type ID; \ @@ -987,16 +993,24 @@ BOOST_OPENMETHOD_DETAIL_HAS_STATIC_FN(id); namespace policies { -struct attributes { - using category = attributes; +struct declspec_policy { + using category = declspec_policy; }; template -struct attributes_guide final : attributes { - using guide_type = GuideType; +struct declspec : declspec_policy { + template + struct fn { + using guide_type = GuideType; + }; +}; +template<> +struct declspec : declspec_policy { template - struct fn {}; + struct fn { + struct guide_type {}; + }; }; } // namespace policies @@ -1053,16 +1067,37 @@ struct attributes_guide final : attributes { template class registry : public detail::registry_base { + public: + //! List of policies selected in a registry. + //! + //! `policy_list` is a Boost.Mp11 list containing the policies passed to the + //! @ref registry clas template. + //! + //! @tparam Class A registered class. + using policy_list = mp11::mp_list; + + //! Find a policy by category. + //! + //! `policy` searches for a policy that derives from the specified @ref + //! Category. If none is found, it aliases to `void`. Otherwise, it aliases + //! to the policy's `fn` metafunction, applied to the registry. + //! + //! @tparam A policy. + template + using policy = typename detail::get_policy_aux< + registry, detail::find_first_derived_of, + Default>::type; + using declspec_guide = typename policy< + policies::declspec_policy, policies::declspec>::guide_type; + + private: template friend struct detail::use_class_aux; template friend class method; using static_ = detail::static_st< - detail::registry_state>, - typename detail::find_first_derived_of< - policies::attributes, mp11::mp_list, - policies::attributes_guide>>::guide_type>; + registry, detail::registry_state>, declspec_guide>; public: //! The type of this registry. @@ -1099,25 +1134,6 @@ class registry : public detail::registry_base { template inline static vptr_type static_vptr; - //! List of policies selected in a registry. - //! - //! `policy_list` is a Boost.Mp11 list containing the policies passed to the - //! @ref registry clas template. - //! - //! @tparam Class A registered class. - using policy_list = mp11::mp_list; - - //! Find a policy by category. - //! - //! `policy` searches for a policy that derives from the specified @ref - //! Category. If none is found, it aliases to `void`. Otherwise, it aliases - //! to the policy's `fn` metafunction, applied to the registry. - //! - //! @tparam A policy. - template - using policy = typename detail::get_policy_aux< - registry, detail::find_first_derived_of>::type; - //! Add or replace policies. //! //! `with` aliases to a registry with additional policies, overwriting any @@ -1210,7 +1226,7 @@ auto final_error::write(Stream& os) const { } struct default_registry_attributes; - +struct default_registry; } // namespace boost::openmethod #ifdef _MSC_VER diff --git a/test/dynamic_loading/CMakeLists.txt b/test/dynamic_loading/CMakeLists.txt index 10b62c6f..916caaba 100644 --- a/test/dynamic_loading/CMakeLists.txt +++ b/test/dynamic_loading/CMakeLists.txt @@ -25,7 +25,11 @@ add_executable( boost_openmethod-test_dynamic_loading EXCLUDE_FROM_ALL main.cpp) target_link_libraries( boost_openmethod-test_dynamic_loading + PUBLIC Boost::openmethod PRIVATE Boost::openmethod Boost::unit_test_framework Boost::dll) +add_dependencies( + boost_openmethod-test_dynamic_loading + dl_test_registry dl_test_method dl_test_overrider) # Put all outputs in the same directory so Boost.DLL can locate the shared # libraries relative to the test executable at runtime. @@ -54,5 +58,3 @@ endforeach() add_test( NAME boost_openmethod-test_dynamic_loading COMMAND boost_openmethod-test_dynamic_loading) -add_dependencies( - tests boost_openmethod-test_dynamic_loading dl_test_overrider) diff --git a/test/dynamic_loading/classes.hpp b/test/dynamic_loading/classes.hpp index 11c8486b..2fcb7d61 100644 --- a/test/dynamic_loading/classes.hpp +++ b/test/dynamic_loading/classes.hpp @@ -3,10 +3,6 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -#pragma once - -#include "registry.hpp" - #include struct Animal { @@ -14,5 +10,3 @@ struct Animal { }; struct Dog : Animal {}; - -BOOST_OPENMETHOD_CLASSES(Animal, Dog); diff --git a/test/dynamic_loading/lib_method.cpp b/test/dynamic_loading/lib_method.cpp index b1571c88..e3481b66 100644 --- a/test/dynamic_loading/lib_method.cpp +++ b/test/dynamic_loading/lib_method.cpp @@ -3,38 +3,34 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -// BOOST_OPENMETHOD_DYNAMIC_LOADING_METHOD_EXPORTS is set via CMake compile -// definition, making METHOD_API expand to dllexport in method.hpp. +#define EXPORT_METHOD +#define INCLUDED_FROM "lib_method.cpp" + +#include "registry.hpp" #include "method.hpp" + +#include #include +#include + using namespace boost::openmethod; namespace mp11 = boost::mp11; -using Method = BOOST_OPENMETHOD_TYPE(speak, (virtual_ptr), std::string); - -constexpr auto n_policies = mp11::mp_size::value; - -static auto get_policy_ids() -> const void** { - static const void* ids[n_policies + 1]; - static bool init = [] { - std::size_t i = 0; - mp11::mp_for_each([&](auto p) { - using P = decltype(p); - if constexpr (detail::has_id>) { - ids[i++] = default_registry::policy

::id(); - } - }); - ids[i] = nullptr; - return true; - }(); - (void)init; - return ids; +static_assert(std::is_same_v); + +BOOST_OPENMETHOD_CLASSES(Animal, Dog); + +BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { + return "?"; } -static auto get_method_fn() -> const void* { - return static_cast(&Method::fn); +BOOST_DLL_ALIAS(get_ids, method_get_ids) +BOOST_DLL_ALIAS(get_fn, method_get_fn) + +const char* call_speak() { + Dog snoopy; + return speak(snoopy); } -BOOST_DLL_ALIAS(get_policy_ids, dl_method_get_policy_ids) -BOOST_DLL_ALIAS(get_method_fn, dl_method_get_method_fn) +BOOST_DLL_AUTO_ALIAS(call_speak) \ No newline at end of file diff --git a/test/dynamic_loading/lib_overrider.cpp b/test/dynamic_loading/lib_overrider.cpp index 7485d1e9..ddafa3be 100644 --- a/test/dynamic_loading/lib_overrider.cpp +++ b/test/dynamic_loading/lib_overrider.cpp @@ -3,40 +3,19 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) +#define INCLUDED_FROM "lib_overrider.cpp" + #include "method.hpp" #include using namespace boost::openmethod; namespace mp11 = boost::mp11; -BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), std::string) { +BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { return "woof"; } -using Method = BOOST_OPENMETHOD_TYPE(speak, (virtual_ptr), std::string); - -constexpr auto n_policies = mp11::mp_size::value; - -static auto get_policy_ids() -> const void** { - static const void* ids[n_policies + 1]; - static bool init = [] { - std::size_t i = 0; - mp11::mp_for_each([&](auto p) { - using P = decltype(p); - if constexpr (detail::has_id>) { - ids[i++] = default_registry::policy

::id(); - } - }); - ids[i] = nullptr; - return true; - }(); - (void)init; - return ids; -} - -static auto get_method_fn() -> const void* { - return static_cast(&Method::fn); -} +BOOST_OPENMETHOD_CLASSES(Animal, Dog); -BOOST_DLL_ALIAS(get_policy_ids, dl_overrider_get_policy_ids) -BOOST_DLL_ALIAS(get_method_fn, dl_overrider_get_method_fn) +BOOST_DLL_ALIAS(get_ids, overrider_get_ids) +BOOST_DLL_ALIAS(get_fn, overrider_get_fn) diff --git a/test/dynamic_loading/lib_registry.cpp b/test/dynamic_loading/lib_registry.cpp index e94f0b08..e38a6442 100644 --- a/test/dynamic_loading/lib_registry.cpp +++ b/test/dynamic_loading/lib_registry.cpp @@ -3,34 +3,27 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -// BOOST_OPENMETHOD_DYNAMIC_LOADING_REGISTRY_EXPORTS is set via CMake compile -// definition, making REGISTRY_API expand to dllexport in registry.hpp (and -// transitively in classes.hpp). Including classes.hpp forces -// static_st>::st to be instantiated and exported from -// this shared library, so other libraries can import it. +#define EXPORT_REGISTRY +#define INCLUDED_FROM "lib_registry.cpp" + +#include "registry.hpp" #include "classes.hpp" + +#include + #include using namespace boost::openmethod; namespace mp11 = boost::mp11; -constexpr auto n_policies = mp11::mp_size::value; - -static auto get_policy_ids() -> const void** { - static const void* ids[n_policies + 1]; - static bool init = [] { - std::size_t i = 0; - mp11::mp_for_each([&](auto p) { - using P = decltype(p); - if constexpr (detail::has_id>) { - ids[i++] = default_registry::policy

::id(); - } - }); - ids[i] = nullptr; - return true; - }(); - (void)init; - return ids; +static_assert(std::is_same_v); + +BOOST_OPENMETHOD_CLASSES(Animal, Dog); + +BOOST_DLL_ALIAS(get_ids, registry_get_ids); + +void registry_initialize() { + boost::openmethod::initialize(trace::from_env()); } -BOOST_DLL_ALIAS(get_policy_ids, dl_registry_get_policy_ids) +BOOST_DLL_AUTO_ALIAS(registry_initialize); diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index b4109d8d..6bcd2927 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -6,19 +6,51 @@ #define BOOST_TEST_MODULE dynamic_loading #include +#define INCLUDED_FROM "main.cpp" + +#include "registry.hpp" #include "method.hpp" #include #include #include +#include + using namespace boost::openmethod; namespace mp11 = boost::mp11; constexpr auto n_policies = mp11::mp_size::value; using policy_ids_fn = const void**(); -using method_fn_fn = const void*(); +using method_fn = const void*(); + +bool same_ids(const void** ids1, const void** ids2) { + using std::setw; + BOOST_TEST_MESSAGE( + setw(60) << "registry state" << ": " << *ids1++ << " " << *ids2++); + + int diffs = 0; + + mp11::mp_for_each([&](auto p) { + using P = decltype(p); + + if constexpr (detail::has_id>) { + BOOST_TEST_MESSAGE( + setw(60) << boost::core::demangle(typeid(P).name()) << ": " + << *ids1 << " " << *ids2); + + if (*ids1 != *ids2) { + ++diffs; + } + + ++ids1; + ++ids2; + } + }); + + return diffs == 0; +} BOOST_AUTO_TEST_CASE(test_shared_state) { namespace dll = boost::dll; @@ -35,21 +67,52 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { dll::load_mode::append_decorations /*| dll::load_mode::rtld_global*/; #endif dll::shared_library registry_lib(lib_dir / "dl_test_registry", global_mode); + auto& registry_get_ids = + registry_lib.get_alias("registry_get_ids"); + dll::shared_library method_lib(lib_dir / "dl_test_method", global_mode); + auto& method_get_ids = + method_lib.get_alias("method_get_ids"); + + BOOST_TEST(same_ids(registry_get_ids(), method_get_ids())); + + auto& registry_initialize = + registry_lib.get_alias("registry_initialize"); + auto& speak = method_lib.get_alias("call_speak"); + registry_initialize(); + BOOST_TEST(speak() == "?"); + dll::shared_library overrider_lib( lib_dir / "dl_test_overrider", dll::load_mode::append_decorations); + auto& overrider_get_ids = + overrider_lib.get_alias("overrider_get_ids"); + auto& method_get_fn = method_lib.get_alias("method_get_fn"); + auto& overrider_get_fn = + overrider_lib.get_alias("overrider_get_fn"); + BOOST_TEST(same_ids(registry_get_ids(), overrider_get_ids())); + // BOOST_TEST(method_get_fn() == overrider_get_fn); + + registry_initialize(); + BOOST_TEST(speak() == "woof"); - auto& registry_get_policy_ids = - registry_lib.get_alias("dl_registry_get_policy_ids"); +#if 0 + dll::shared_library overrider_lib( + lib_dir / "dl_test_overrider", dll::load_mode::append_decorations); + + auto& method_get_registry_id = + registry_lib.get_alias("dl_registry_get_registry_id"); auto& method_get_policy_ids = method_lib.get_alias("dl_method_get_policy_ids"); auto& method_get_method_fn = method_lib.get_alias("dl_method_get_method_fn"); + auto& overrider_get_policy_ids = overrider_lib.get_alias("dl_overrider_get_policy_ids"); auto& overrider_get_method_fn = overrider_lib.get_alias("dl_overrider_get_method_fn"); + BOOST_TEST(registry_get_registry_id() == default_registry::id()); + // Build local policy id array for comparison const void* local_ids[n_policies + 1]; { @@ -77,10 +140,11 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { // All libraries must see the same method object BOOST_TEST(method_get_method_fn() == overrider_get_method_fn()); - // Build dispatch tables (including the Dog overrider from the loaded lib) - boost::openmethod::initialize(); +#endif + // // Build dispatch tables (including the Dog overrider from the loaded lib) + // boost::openmethod::initialize(); - // Verify dispatch via the method - Dog dog; - BOOST_TEST(speak(dog) == "woof"); + // // Verify dispatch via the method + // Dog dog; + // BOOST_TEST(speak(dog) == "woof"); } diff --git a/test/dynamic_loading/method.hpp b/test/dynamic_loading/method.hpp index 5f14f930..9dbbf893 100644 --- a/test/dynamic_loading/method.hpp +++ b/test/dynamic_loading/method.hpp @@ -3,19 +3,28 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -#pragma once - +#include "registry.hpp" #include "classes.hpp" -#if !defined(_MSC_VER) -#define METHOD_API boost::openmethod::declspec_none -#elif defined(BOOST_OPENMETHOD_DYNAMIC_LOADING_METHOD_EXPORTS) -#define METHOD_API boost::openmethod::dllexport +#include + +#if defined(_MSC_VER) +#if defined(EXPORT_METHOD) +#pragma message(INCLUDED_FROM ": exporting method") +#define METHOD_DECLSPEC boost::openmethod::dllexport #else -#define METHOD_API boost::openmethod::dllimport +#pragma message(INCLUDED_FROM ": importing method") +#define METHOD_DECLSPEC boost::openmethod::dllimport +#endif +#else +#define METHOD_DECLSPEC boost::openmethod::declspec_none #endif - -#include BOOST_OPENMETHOD( - speak, (boost::openmethod::virtual_ptr), std::string, METHOD_API); + speak, (boost::openmethod::virtual_ptr), const char*, + METHOD_DECLSPEC); + +inline auto get_fn() { + return static_cast(&BOOST_OPENMETHOD_TYPE( + speak, (boost::openmethod::virtual_ptr), const char*)::fn); +} diff --git a/test/dynamic_loading/registry.hpp b/test/dynamic_loading/registry.hpp index beb619fe..631afb10 100644 --- a/test/dynamic_loading/registry.hpp +++ b/test/dynamic_loading/registry.hpp @@ -3,18 +3,45 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) -#pragma once +#ifndef BOOST_OPENMETHOD_TEST_DYNAMIC_LOADING_REGISTRY_HPP +#define BOOST_OPENMETHOD_TEST_DYNAMIC_LOADING_REGISTRY_HPP -#if !defined(_MSC_VER) -#define REGISTRY_API boost::openmethod::declspec_none -#elif BOOST_OPENMETHOD_DYNAMIC_LOADING_REGISTRY_EXPORTS -#define REGISTRY_API boost::openmethod::dllexport +#include +#if defined(_MSC_VER) + +#if defined(EXPORT_REGISTRY) +#pragma message(INCLUDED_FROM ": exporting registry") +#define REGISTRY_DECLSPEC boost::openmethod::dllexport #else -#define REGISTRY_API boost::openmethod::dllimport +#pragma message(INCLUDED_FROM ": importing registry") +#define REGISTRY_DECLSPEC boost::openmethod::dllimport #endif +namespace boost::openmethod { +REGISTRY_DECLSPEC +boost_openmethod_declspec(default_registry&); +} // namespace boost::openmethod -#include +#endif -namespace boost::openmethod { -REGISTRY_API boost_openmethod_declspec(default_registry_attributes); +#include + +static auto get_ids() -> const void** { + using namespace boost::openmethod; + namespace mp11 = boost::mp11; + + constexpr auto n_policies = mp11::mp_size::value; + static const void* ids[1 + n_policies + 1] = {default_registry::id()}; + std::size_t i = 1; + + mp11::mp_for_each([&](auto p) { + using P = decltype(p); + + if constexpr (detail::has_id>) { + ids[i++] = default_registry::policy

::id(); + } + }); + + return ids; } + +#endif diff --git a/test/test_runtime_errors.cpp b/test/test_runtime_errors.cpp index fcd57c9c..e3ed95e4 100644 --- a/test/test_runtime_errors.cpp +++ b/test/test_runtime_errors.cpp @@ -113,7 +113,8 @@ BOOST_AUTO_TEST_CASE(initialize_unknown_class) { { registry::capture capture; BOOST_CHECK_THROW(initialize(), missing_class); - BOOST_TEST(capture().find("unknown class") != std::string::npos); + auto s = capture(); + BOOST_TEST(s.find("unknown class") != std::string::npos); } } } From 55afef528d27d7f3a7db7627acd37154a11b27d0 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 14 Mar 2026 10:55:27 -0400 Subject: [PATCH 04/74] support dynamic loading on Windows --- .../ROOT/examples/shared_libs/animals.hpp | 4 +++ .../examples/shared_libs/dynamic_main.cpp | 4 +++ .../ROOT/examples/shared_libs/extensions.cpp | 4 +++ include/boost/openmethod/preamble.hpp | 26 +++++++++++++------ 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/doc/modules/ROOT/examples/shared_libs/animals.hpp b/doc/modules/ROOT/examples/shared_libs/animals.hpp index 9ea98f1c..eef3020d 100644 --- a/doc/modules/ROOT/examples/shared_libs/animals.hpp +++ b/doc/modules/ROOT/examples/shared_libs/animals.hpp @@ -21,11 +21,15 @@ //#pragma GCC diagnostic ignored "-Wundefined-var-template" #endif +#ifdef _WIN32 #ifdef LIBRARY_NAME #define ANIMALS_API boost::openmethod::dllexport #else #define ANIMALS_API boost::openmethod::dllimport #endif +#else +#define ANIMALS_API boost::openmethod::declspec_none +#endif namespace boost::openmethod { ANIMALS_API boost_openmethod_declspec(default_registry&); diff --git a/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp b/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp index 2654d88a..7b073c69 100644 --- a/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp +++ b/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp @@ -26,6 +26,8 @@ using namespace boost::openmethod; +#ifdef _WIN32 + static_assert(!std::is_same_v< BOOST_OPENMETHOD_TYPE( meet, (virtual_ptr, virtual_ptr), @@ -41,6 +43,8 @@ static_assert(std::is_same_v< static_assert(!std::is_same_v); static_assert(std::is_same_v); +#endif + BOOST_OPENMETHOD_CLASSES(Herbivore, Cow, Carnivore, Wolf); BOOST_OPENMETHOD_OVERRIDE( diff --git a/doc/modules/ROOT/examples/shared_libs/extensions.cpp b/doc/modules/ROOT/examples/shared_libs/extensions.cpp index 3bdf807d..28a41d58 100644 --- a/doc/modules/ROOT/examples/shared_libs/extensions.cpp +++ b/doc/modules/ROOT/examples/shared_libs/extensions.cpp @@ -9,6 +9,8 @@ using namespace boost::openmethod; +#ifdef _WIN32 + static_assert(std::is_same_v); static_assert(std::is_same_v< @@ -17,6 +19,8 @@ static_assert(std::is_same_v< std::string)::declspec, dllimport>); +#endif + BOOST_OPENMETHOD_OVERRIDE( meet, (virtual_ptr a, virtual_ptr b), std::string) { auto p = BOOST_OPENMETHOD_TYPE( diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index 86b7513a..a678bae2 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -739,8 +739,8 @@ struct TypeHashFn { //! blueprint. //! @return A pair containing the minimum and maximum hash values. template - static auto initialize(const Context& ctx) - -> std::pair; + static auto + initialize(const Context& ctx) -> std::pair; //! Hash a `type_id`. //! @@ -942,22 +942,28 @@ struct initialize_aux; // import/export struct declspec {}; +struct declspec_none : declspec {}; + +#if defined(__MRDOCS__) || defined(_WIN32) struct dllexport : declspec {}; struct dllimport : declspec {}; -struct declspec_none : declspec {}; +#endif namespace detail { -#define BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(ID, ...) \ +#define BOOST_OPENMETHOD_DETAIL_MAKE_STATICS_COMMON(ID, ...) \ template \ struct BOOST_PP_CAT(static_, ID) { \ - using declspec = void; \ - static Type ID; \ + using declspec = declspec_none; \ + static Type ID __VA_ARGS__; \ }; \ \ template \ - Type BOOST_PP_CAT( \ - static_, ID)::ID __VA_ARGS__; \ + Type BOOST_PP_CAT(static_, ID)::ID; + +#if defined(_WIN32) +#define BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(ID, ...) \ + BOOST_OPENMETHOD_DETAIL_MAKE_STATICS_COMMON(ID, __VA_ARGS__) \ \ template \ struct BOOST_PP_CAT(static_, ID)< \ @@ -980,6 +986,10 @@ namespace detail { using declspec = dllimport; \ static BOOST_SYMBOL_IMPORT Type ID; \ } +#else +#define BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(ID, ...) \ + BOOST_OPENMETHOD_DETAIL_MAKE_STATICS_COMMON(ID, __VA_ARGS__) +#endif template using get_attributes = From c001105acdf7398fce566d0317c8641534efb16f Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 14 Mar 2026 12:44:43 -0400 Subject: [PATCH 05/74] support dynamic loading on Windows --- test/dynamic_loading/CMakeLists.txt | 8 ++++---- test/dynamic_loading/lib_method.cpp | 2 ++ test/dynamic_loading/lib_registry.cpp | 2 ++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/test/dynamic_loading/CMakeLists.txt b/test/dynamic_loading/CMakeLists.txt index 916caaba..95157ed0 100644 --- a/test/dynamic_loading/CMakeLists.txt +++ b/test/dynamic_loading/CMakeLists.txt @@ -4,25 +4,25 @@ # or copy at http://www.boost.org/LICENSE_1_0.txt) # lib_registry: exports the default registry's static policy state -add_library(dl_test_registry SHARED EXCLUDE_FROM_ALL lib_registry.cpp) +add_library(dl_test_registry SHARED lib_registry.cpp) target_compile_definitions( dl_test_registry PRIVATE BOOST_OPENMETHOD_DYNAMIC_LOADING_REGISTRY_EXPORTS) target_link_libraries(dl_test_registry PUBLIC Boost::openmethod PRIVATE Boost::dll) # lib_method: exports the speak method; imports registry state from lib_registry -add_library(dl_test_method SHARED EXCLUDE_FROM_ALL lib_method.cpp) +add_library(dl_test_method SHARED lib_method.cpp) target_compile_definitions( dl_test_method PRIVATE BOOST_OPENMETHOD_DYNAMIC_LOADING_METHOD_EXPORTS) target_link_libraries(dl_test_method PUBLIC Boost::openmethod dl_test_registry PRIVATE Boost::dll) # lib_overrider: adds a Dog overrider; imported at runtime via Boost.DLL -add_library(dl_test_overrider SHARED EXCLUDE_FROM_ALL lib_overrider.cpp) +add_library(dl_test_overrider SHARED lib_overrider.cpp) target_link_libraries(dl_test_overrider PRIVATE Boost::openmethod dl_test_method Boost::dll) # Test executable: links against lib_method (and lib_registry transitively); # dynamically loads lib_overrider at runtime. add_executable( - boost_openmethod-test_dynamic_loading EXCLUDE_FROM_ALL main.cpp) + boost_openmethod-test_dynamic_loading main.cpp) target_link_libraries( boost_openmethod-test_dynamic_loading PUBLIC Boost::openmethod diff --git a/test/dynamic_loading/lib_method.cpp b/test/dynamic_loading/lib_method.cpp index e3481b66..b565adeb 100644 --- a/test/dynamic_loading/lib_method.cpp +++ b/test/dynamic_loading/lib_method.cpp @@ -17,7 +17,9 @@ using namespace boost::openmethod; namespace mp11 = boost::mp11; +#ifdef _WIN32 static_assert(std::is_same_v); +#endif BOOST_OPENMETHOD_CLASSES(Animal, Dog); diff --git a/test/dynamic_loading/lib_registry.cpp b/test/dynamic_loading/lib_registry.cpp index e38a6442..9062ce5e 100644 --- a/test/dynamic_loading/lib_registry.cpp +++ b/test/dynamic_loading/lib_registry.cpp @@ -16,7 +16,9 @@ using namespace boost::openmethod; namespace mp11 = boost::mp11; +#ifdef _WIN32 static_assert(std::is_same_v); +#endif BOOST_OPENMETHOD_CLASSES(Animal, Dog); From 26769344af6790a69a6a81a5f4ebe56a3f1ba5af Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 14 Mar 2026 13:07:31 -0400 Subject: [PATCH 06/74] test --- test/dynamic_loading/CMakeLists.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/test/dynamic_loading/CMakeLists.txt b/test/dynamic_loading/CMakeLists.txt index 95157ed0..d95b5ed7 100644 --- a/test/dynamic_loading/CMakeLists.txt +++ b/test/dynamic_loading/CMakeLists.txt @@ -55,6 +55,19 @@ foreach( VISIBILITY_INLINES_HIDDEN OFF) endforeach() +add_test( + NAME boost_openmethod-test_dynamic_loading-build + COMMAND + ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} + --target boost_openmethod-test_dynamic_loading + --config $) +set_tests_properties( + boost_openmethod-test_dynamic_loading-build + PROPERTIES FIXTURES_SETUP boost_openmethod-test_dynamic_loading-fixture) + add_test( NAME boost_openmethod-test_dynamic_loading COMMAND boost_openmethod-test_dynamic_loading) +set_tests_properties( + boost_openmethod-test_dynamic_loading + PROPERTIES FIXTURES_REQUIRED boost_openmethod-test_dynamic_loading-fixture) From 4ba9bc7a091fc03d3b44bf7588c0b282150f2191 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 14 Mar 2026 13:37:01 -0400 Subject: [PATCH 07/74] test _WIN32, not _MSC_VER --- test/dynamic_loading/method.hpp | 2 +- test/dynamic_loading/registry.hpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/test/dynamic_loading/method.hpp b/test/dynamic_loading/method.hpp index 9dbbf893..621e2fdb 100644 --- a/test/dynamic_loading/method.hpp +++ b/test/dynamic_loading/method.hpp @@ -8,7 +8,7 @@ #include -#if defined(_MSC_VER) +#if defined(_WIN32) #if defined(EXPORT_METHOD) #pragma message(INCLUDED_FROM ": exporting method") #define METHOD_DECLSPEC boost::openmethod::dllexport diff --git a/test/dynamic_loading/registry.hpp b/test/dynamic_loading/registry.hpp index 631afb10..767824be 100644 --- a/test/dynamic_loading/registry.hpp +++ b/test/dynamic_loading/registry.hpp @@ -7,8 +7,8 @@ #define BOOST_OPENMETHOD_TEST_DYNAMIC_LOADING_REGISTRY_HPP #include -#if defined(_MSC_VER) +#if defined(_WIN32) #if defined(EXPORT_REGISTRY) #pragma message(INCLUDED_FROM ": exporting registry") #define REGISTRY_DECLSPEC boost::openmethod::dllexport @@ -16,6 +16,7 @@ #pragma message(INCLUDED_FROM ": importing registry") #define REGISTRY_DECLSPEC boost::openmethod::dllimport #endif + namespace boost::openmethod { REGISTRY_DECLSPEC boost_openmethod_declspec(default_registry&); From b10fee901a616c09d4749e1e03abb25e5a1b2146 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 14 Mar 2026 13:39:32 -0400 Subject: [PATCH 08/74] detail::unspecified --- include/boost/openmethod/preamble.hpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index a678bae2..cee4fcda 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -372,7 +372,9 @@ struct deferred_overrider_info : overrider_info { #ifdef __MRDOCS__ +namespace detail { struct unspecified {}; +} // namespace detail //! Blueprint for a lightweight output stream (exposition only). //! From 00ba04fa19bfa377996330173a901cc4cb6dd94e Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 14 Mar 2026 14:03:30 -0400 Subject: [PATCH 09/74] find dlls --- test/dynamic_loading/CMakeLists.txt | 8 ++++---- test/dynamic_loading/main.cpp | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/test/dynamic_loading/CMakeLists.txt b/test/dynamic_loading/CMakeLists.txt index d95b5ed7..f7170cc6 100644 --- a/test/dynamic_loading/CMakeLists.txt +++ b/test/dynamic_loading/CMakeLists.txt @@ -26,7 +26,7 @@ add_executable( target_link_libraries( boost_openmethod-test_dynamic_loading PUBLIC Boost::openmethod - PRIVATE Boost::openmethod Boost::unit_test_framework Boost::dll) + PRIVATE Boost::openmethod Boost::dll) add_dependencies( boost_openmethod-test_dynamic_loading dl_test_registry dl_test_method dl_test_overrider) @@ -47,9 +47,9 @@ foreach( set_target_properties( ${target} PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" - ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/stage/bin/$" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/stage/bin/$" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/stage/bin/$" ENABLE_EXPORTS ON CXX_VISIBILITY_PRESET default VISIBILITY_INLINES_HIDDEN OFF) diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index 6bcd2927..9c5477e9 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -4,6 +4,7 @@ // or copy at http://www.boost.org/LICENSE_1_0.txt) #define BOOST_TEST_MODULE dynamic_loading +#define BOOST_TEST_INCLUDED #include #define INCLUDED_FROM "main.cpp" From 9d751c4f3cec79d46278bbcfcf4448ea53ce38bf Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 14 Mar 2026 14:09:09 -0400 Subject: [PATCH 10/74] find dlls --- test/dynamic_loading/CMakeLists.txt | 2 +- test/dynamic_loading/main.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/test/dynamic_loading/CMakeLists.txt b/test/dynamic_loading/CMakeLists.txt index f7170cc6..1c3e42c0 100644 --- a/test/dynamic_loading/CMakeLists.txt +++ b/test/dynamic_loading/CMakeLists.txt @@ -26,7 +26,7 @@ add_executable( target_link_libraries( boost_openmethod-test_dynamic_loading PUBLIC Boost::openmethod - PRIVATE Boost::openmethod Boost::dll) + PRIVATE Boost::openmethod Boost::unit_test_framework Boost::dll) add_dependencies( boost_openmethod-test_dynamic_loading dl_test_registry dl_test_method dl_test_overrider) diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index 9c5477e9..6bcd2927 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -4,7 +4,6 @@ // or copy at http://www.boost.org/LICENSE_1_0.txt) #define BOOST_TEST_MODULE dynamic_loading -#define BOOST_TEST_INCLUDED #include #define INCLUDED_FROM "main.cpp" From fa68acc75db206becc79fed94f3cf8271894d063 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 14 Mar 2026 14:32:08 -0400 Subject: [PATCH 11/74] update doc (claude) --- .../ROOT/examples/shared_libs/animals.hpp | 2 +- .../examples/shared_libs/dynamic_main.cpp | 4 +- .../ROOT/examples/shared_libs/extensions.cpp | 2 +- doc/modules/ROOT/pages/shared_libraries.adoc | 118 +++++++++++++----- 4 files changed, 89 insertions(+), 37 deletions(-) diff --git a/doc/modules/ROOT/examples/shared_libs/animals.hpp b/doc/modules/ROOT/examples/shared_libs/animals.hpp index eef3020d..79d2cea2 100644 --- a/doc/modules/ROOT/examples/shared_libs/animals.hpp +++ b/doc/modules/ROOT/examples/shared_libs/animals.hpp @@ -8,7 +8,6 @@ // clang-format off -// tag::content[] // animals.hpp #include @@ -21,6 +20,7 @@ //#pragma GCC diagnostic ignored "-Wundefined-var-template" #endif +// tag::content[] #ifdef _WIN32 #ifdef LIBRARY_NAME #define ANIMALS_API boost::openmethod::dllexport diff --git a/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp b/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp index 7b073c69..c116b6aa 100644 --- a/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp +++ b/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp @@ -12,7 +12,6 @@ #endif #endif -// tag::before[] // dynamic_main.cpp #include "animals.hpp" @@ -45,6 +44,9 @@ static_assert(std::is_same_v); #endif +// tag::before[] +// dynamic_main.cpp + BOOST_OPENMETHOD_CLASSES(Herbivore, Cow, Carnivore, Wolf); BOOST_OPENMETHOD_OVERRIDE( diff --git a/doc/modules/ROOT/examples/shared_libs/extensions.cpp b/doc/modules/ROOT/examples/shared_libs/extensions.cpp index 28a41d58..fb3d7305 100644 --- a/doc/modules/ROOT/examples/shared_libs/extensions.cpp +++ b/doc/modules/ROOT/examples/shared_libs/extensions.cpp @@ -3,7 +3,6 @@ // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -// tag::content[] // extensions.cpp #include "animals.hpp" @@ -21,6 +20,7 @@ static_assert(std::is_same_v< #endif +// tag::content[] BOOST_OPENMETHOD_OVERRIDE( meet, (virtual_ptr a, virtual_ptr b), std::string) { auto p = BOOST_OPENMETHOD_TYPE( diff --git a/doc/modules/ROOT/pages/shared_libraries.adoc b/doc/modules/ROOT/pages/shared_libraries.adoc index 3fc689e1..be4a06d9 100644 --- a/doc/modules/ROOT/pages/shared_libraries.adoc +++ b/doc/modules/ROOT/pages/shared_libraries.adoc @@ -36,8 +36,10 @@ If a library only uses its own registries, for example, if using open-methods as an implementation detail, it has its own global data, and there is no need to call `initialize`. -Let's look at an example. The following header is included by the program and -the shared library: +Let's look at an example. The following header is shared between the program and +the dynamically loaded library. It defines the class hierarchy, the `meet` +method, and sets up declspec decoration for Windows compatibility (explained in +the <> section below): [source,c++] ---- @@ -52,8 +54,7 @@ The shared library contains an object that adds two overriders, a new class, include::{shared}/extensions.cpp[tag=content] ---- -The main program adds a couple of classes then calls `meet` method. At this -point, we only have the catch-call overrider: +The main program provides a catch-all overrider, then calls the `meet` method: [source,c++] ---- @@ -79,36 +80,85 @@ include::{shared}/dynamic_main.cpp[tag=unload] ## Windows -If we try the example on Windows, the result is disappointing: - -``` -Before loading the shared library. -cow meets wolf -> greet -wolf meets cow -> greet - -After loading the shared library. -cow meets wolf -> greet -wolf meets cow -> greet -cow meets tiger -> unknown class struct Tiger -``` - -What happens here is that the program and the DLL have their own copies of -"global" variables. When the DLL is loaded, its static constructors run, and -they add overriders to _their_ copy of the method (the `method::fn` static -variable for the given name and signature). They are ignored when the main -program calls `initialize`. - -Likewise, `BOOST_OPENMETHOD_CLASSES(Tiger, Carnivore)` in the DLL adds `Tiger` -to the DLL's copy of the registry. For the perspective of the program's -registry, the class does not exist. - -In theory, this can be fixed by adding `__declspec(dllimport)` and -`__declspec(dllexport)` attributes where needed. However, this is not practical, -because programs and DLLs can both import and export registries and methods. The -underlying objects are instantiated from templates, which complicates the -matter. Research is being done on this subject. However, as of now, dynamic -loading is supported on Windows only if it does not attempt to share a registry -across modules. +On Windows, each module (executable or DLL) receives its own copy of global and +static variables by default. Without special measures, the registry state, +dispatch tables, and method function pointers are duplicated: when a DLL's +static constructors register classes and overriders, they populate the DLL's own +copy of the registry, invisible to the main program. + +OpenMethod solves this with _declspec decoration_. Three types are provided in +`namespace boost::openmethod`: + +- `dllexport` -- marks the _owning_ module, which defines and exports shared + state +- `dllimport` -- marks _client_ modules, which import shared state from the + owner +- `declspec_none` -- a no-op for non-Windows platforms, enabling portable + headers + +The `animals.hpp` header shown above demonstrates the pattern. The key elements +are: + +1. A preprocessor block that selects `dllexport` or `dllimport` depending on + which module is being compiled. The owning module (the executable, in this + example) defines a distinguishing macro; client modules (the DLL) do not: ++ +[source,c++] +---- +#ifdef _WIN32 +#ifdef LIBRARY_NAME +#define ANIMALS_API boost::openmethod::dllexport +#else +#define ANIMALS_API boost::openmethod::dllimport +#endif +#else +#define ANIMALS_API boost::openmethod::declspec_none +#endif +---- + +2. A `boost_openmethod_declspec` function declaration in `namespace + boost::openmethod` that tells the library how to decorate the registry's + static variables: ++ +[source,c++] +---- +namespace boost::openmethod { + ANIMALS_API boost_openmethod_declspec(default_registry&); +} +---- + +3. The declspec type passed as a fourth argument to `BOOST_OPENMETHOD`, + decorating the method's function pointer and associated statics: ++ +[source,c++] +---- +BOOST_OPENMETHOD(meet, (...), std::string, ANIMALS_API); +---- + +### CMake Setup + +On Windows, the DLL must link against the executable to resolve the imported +symbols. This is the reverse of the typical linking direction: + +[source,cmake] +---- +add_executable(my_app dynamic_main.cpp) +set_target_properties(my_app PROPERTIES ENABLE_EXPORTS ON) +target_link_libraries(my_app Boost::openmethod Boost::dll) + +add_library(my_plugin SHARED extensions.cpp) +target_link_libraries(my_plugin PRIVATE Boost::openmethod my_app) +---- + +`ENABLE_EXPORTS ON` on the executable tells CMake to generate an import library +(`.lib` on MSVC) that the DLL links against. On POSIX, `ENABLE_EXPORTS` adds +`-rdynamic`, which is the only setup needed. + +NOTE: The owning module must compile code that triggers static registration -- +it must include headers containing `BOOST_OPENMETHOD_CLASSES` or +`BOOST_OPENMETHOD` macros. Simply declaring `boost_openmethod_declspec` is not +sufficient; the registry state is only instantiated and exported when +registration code references it. ## Indirect Vptrs From 27f2cc333a7550323b6028becaa6661931b6d278 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 14 Mar 2026 14:52:01 -0400 Subject: [PATCH 12/74] simplify file names --- test/dynamic_loading/CMakeLists.txt | 6 +++--- test/dynamic_loading/{lib_method.cpp => method.cpp} | 0 test/dynamic_loading/{lib_overrider.cpp => overrider.cpp} | 0 test/dynamic_loading/{lib_registry.cpp => registry.cpp} | 0 4 files changed, 3 insertions(+), 3 deletions(-) rename test/dynamic_loading/{lib_method.cpp => method.cpp} (100%) rename test/dynamic_loading/{lib_overrider.cpp => overrider.cpp} (100%) rename test/dynamic_loading/{lib_registry.cpp => registry.cpp} (100%) diff --git a/test/dynamic_loading/CMakeLists.txt b/test/dynamic_loading/CMakeLists.txt index 1c3e42c0..ddec3456 100644 --- a/test/dynamic_loading/CMakeLists.txt +++ b/test/dynamic_loading/CMakeLists.txt @@ -4,19 +4,19 @@ # or copy at http://www.boost.org/LICENSE_1_0.txt) # lib_registry: exports the default registry's static policy state -add_library(dl_test_registry SHARED lib_registry.cpp) +add_library(dl_test_registry SHARED registry.cpp) target_compile_definitions( dl_test_registry PRIVATE BOOST_OPENMETHOD_DYNAMIC_LOADING_REGISTRY_EXPORTS) target_link_libraries(dl_test_registry PUBLIC Boost::openmethod PRIVATE Boost::dll) # lib_method: exports the speak method; imports registry state from lib_registry -add_library(dl_test_method SHARED lib_method.cpp) +add_library(dl_test_method SHARED method.cpp) target_compile_definitions( dl_test_method PRIVATE BOOST_OPENMETHOD_DYNAMIC_LOADING_METHOD_EXPORTS) target_link_libraries(dl_test_method PUBLIC Boost::openmethod dl_test_registry PRIVATE Boost::dll) # lib_overrider: adds a Dog overrider; imported at runtime via Boost.DLL -add_library(dl_test_overrider SHARED lib_overrider.cpp) +add_library(dl_test_overrider SHARED overrider.cpp) target_link_libraries(dl_test_overrider PRIVATE Boost::openmethod dl_test_method Boost::dll) # Test executable: links against lib_method (and lib_registry transitively); diff --git a/test/dynamic_loading/lib_method.cpp b/test/dynamic_loading/method.cpp similarity index 100% rename from test/dynamic_loading/lib_method.cpp rename to test/dynamic_loading/method.cpp diff --git a/test/dynamic_loading/lib_overrider.cpp b/test/dynamic_loading/overrider.cpp similarity index 100% rename from test/dynamic_loading/lib_overrider.cpp rename to test/dynamic_loading/overrider.cpp diff --git a/test/dynamic_loading/lib_registry.cpp b/test/dynamic_loading/registry.cpp similarity index 100% rename from test/dynamic_loading/lib_registry.cpp rename to test/dynamic_loading/registry.cpp From 2329655d261393ceed2a03eb69f9d3d04437b2d6 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 14 Mar 2026 14:52:26 -0400 Subject: [PATCH 13/74] remove useless compile definitions --- test/dynamic_loading/CMakeLists.txt | 37 +++++++++++++++++------------ 1 file changed, 22 insertions(+), 15 deletions(-) diff --git a/test/dynamic_loading/CMakeLists.txt b/test/dynamic_loading/CMakeLists.txt index ddec3456..a2b27f3a 100644 --- a/test/dynamic_loading/CMakeLists.txt +++ b/test/dynamic_loading/CMakeLists.txt @@ -4,20 +4,22 @@ # or copy at http://www.boost.org/LICENSE_1_0.txt) # lib_registry: exports the default registry's static policy state -add_library(dl_test_registry SHARED registry.cpp) -target_compile_definitions( - dl_test_registry PRIVATE BOOST_OPENMETHOD_DYNAMIC_LOADING_REGISTRY_EXPORTS) -target_link_libraries(dl_test_registry PUBLIC Boost::openmethod PRIVATE Boost::dll) +add_library(boost_openmethod-dl_test_registry SHARED registry.cpp) +target_link_libraries( + boost_openmethod-dl_test_registry + PUBLIC Boost::openmethod PRIVATE Boost::dll) # lib_method: exports the speak method; imports registry state from lib_registry -add_library(dl_test_method SHARED method.cpp) -target_compile_definitions( - dl_test_method PRIVATE BOOST_OPENMETHOD_DYNAMIC_LOADING_METHOD_EXPORTS) -target_link_libraries(dl_test_method PUBLIC Boost::openmethod dl_test_registry PRIVATE Boost::dll) +add_library(boost_openmethod-dl_test_method SHARED method.cpp) +target_link_libraries( + boost_openmethod-dl_test_method + PUBLIC Boost::openmethod boost_openmethod-dl_test_registry PRIVATE Boost::dll) # lib_overrider: adds a Dog overrider; imported at runtime via Boost.DLL -add_library(dl_test_overrider SHARED overrider.cpp) -target_link_libraries(dl_test_overrider PRIVATE Boost::openmethod dl_test_method Boost::dll) +add_library(boost_openmethod-dl_test_overrider SHARED overrider.cpp) +target_link_libraries( + boost_openmethod-dl_test_overrider + PRIVATE Boost::openmethod boost_openmethod-dl_test_method Boost::dll) # Test executable: links against lib_method (and lib_registry transitively); # dynamically loads lib_overrider at runtime. @@ -29,21 +31,26 @@ target_link_libraries( PRIVATE Boost::openmethod Boost::unit_test_framework Boost::dll) add_dependencies( boost_openmethod-test_dynamic_loading - dl_test_registry dl_test_method dl_test_overrider) + boost_openmethod-dl_test_registry + boost_openmethod-dl_test_method + boost_openmethod-dl_test_overrider) # Put all outputs in the same directory so Boost.DLL can locate the shared # libraries relative to the test executable at runtime. # ENABLE_EXPORTS is required so that symbols from each shared library are -# visible to other shared libraries loaded at runtime (e.g. dl_test_overrider -# can resolve symbols from dl_test_method/dl_test_registry). +# visible to other shared libraries loaded at runtime (e.g. boost_openmethod-dl_test_overrider +# can resolve symbols from boost_openmethod-dl_test_method/boost_openmethod-dl_test_registry). # CXX_VISIBILITY_PRESET must be "default" so that the template static variables # used as the shared registry state (policy statics) are exported with DEFAULT # ELF visibility. With hidden visibility (as set by BoostRoot.cmake in the # super-project build) they become UNIQUE HIDDEN and are not deduplicated by # the dynamic linker, breaking cross-DSO state sharing. foreach( - target dl_test_registry dl_test_method dl_test_overrider - boost_openmethod-test_dynamic_loading) + target + boost_openmethod-dl_test_registry + boost_openmethod-dl_test_method + boost_openmethod-dl_test_overrider + boost_openmethod-test_dynamic_loading) set_target_properties( ${target} PROPERTIES From bdd644e2d88482e148492f13b43415e0d571fb79 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 14 Mar 2026 14:52:33 -0400 Subject: [PATCH 14/74] mingw --- include/boost/openmethod/preamble.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index cee4fcda..7367c6c6 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -995,7 +995,7 @@ namespace detail { template using get_attributes = - decltype(boost_openmethod_declspec(std::declval())); + decltype(boost_openmethod_declspec(std::declval())); BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(st); From b4e77545de156425dd48647ea6d98da65d5b956e Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 14 Mar 2026 14:58:04 -0400 Subject: [PATCH 15/74] prefix DLL file names --- test/dynamic_loading/main.cpp | 62 ++++------------------------------- 1 file changed, 6 insertions(+), 56 deletions(-) diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index 6bcd2927..6da7eb2c 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -66,11 +66,13 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { constexpr auto global_mode = dll::load_mode::append_decorations /*| dll::load_mode::rtld_global*/; #endif - dll::shared_library registry_lib(lib_dir / "dl_test_registry", global_mode); + dll::shared_library registry_lib( + lib_dir / "boost_openmethod-dl_test_registry", global_mode); auto& registry_get_ids = registry_lib.get_alias("registry_get_ids"); - dll::shared_library method_lib(lib_dir / "dl_test_method", global_mode); + dll::shared_library method_lib( + lib_dir / "boost_openmethod-dl_test_method", global_mode); auto& method_get_ids = method_lib.get_alias("method_get_ids"); @@ -83,7 +85,8 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { BOOST_TEST(speak() == "?"); dll::shared_library overrider_lib( - lib_dir / "dl_test_overrider", dll::load_mode::append_decorations); + lib_dir / "boost_openmethod-dl_test_overrider", + dll::load_mode::append_decorations); auto& overrider_get_ids = overrider_lib.get_alias("overrider_get_ids"); auto& method_get_fn = method_lib.get_alias("method_get_fn"); @@ -94,57 +97,4 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { registry_initialize(); BOOST_TEST(speak() == "woof"); - -#if 0 - dll::shared_library overrider_lib( - lib_dir / "dl_test_overrider", dll::load_mode::append_decorations); - - auto& method_get_registry_id = - registry_lib.get_alias("dl_registry_get_registry_id"); - auto& method_get_policy_ids = - method_lib.get_alias("dl_method_get_policy_ids"); - auto& method_get_method_fn = - method_lib.get_alias("dl_method_get_method_fn"); - - auto& overrider_get_policy_ids = - overrider_lib.get_alias("dl_overrider_get_policy_ids"); - auto& overrider_get_method_fn = - overrider_lib.get_alias("dl_overrider_get_method_fn"); - - BOOST_TEST(registry_get_registry_id() == default_registry::id()); - - // Build local policy id array for comparison - const void* local_ids[n_policies + 1]; - { - std::size_t i = 0; - mp11::mp_for_each([&](auto p) { - using P = decltype(p); - if constexpr (detail::has_id>) { - local_ids[i++] = default_registry::policy

::id(); - } - }); - local_ids[i] = nullptr; - } - - const void** registry_ids = registry_get_policy_ids(); - const void** method_ids = method_get_policy_ids(); - const void** overrider_ids = overrider_get_policy_ids(); - - // All libraries must share the same policy static variables - for (std::size_t i = 0; local_ids[i] != nullptr; ++i) { - BOOST_TEST(registry_ids[i] == local_ids[i]); - BOOST_TEST(method_ids[i] == local_ids[i]); - BOOST_TEST(overrider_ids[i] == local_ids[i]); - } - - // All libraries must see the same method object - BOOST_TEST(method_get_method_fn() == overrider_get_method_fn()); - -#endif - // // Build dispatch tables (including the Dog overrider from the loaded lib) - // boost::openmethod::initialize(); - - // // Verify dispatch via the method - // Dog dog; - // BOOST_TEST(speak(dog) == "woof"); } From 031adbf73209b72c2926b4065be97bb0603abf37 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 14 Mar 2026 15:03:51 -0400 Subject: [PATCH 16/74] 'main' static links with 'registry' --- test/dynamic_loading/CMakeLists.txt | 2 +- test/dynamic_loading/main.cpp | 17 +++++------------ 2 files changed, 6 insertions(+), 13 deletions(-) diff --git a/test/dynamic_loading/CMakeLists.txt b/test/dynamic_loading/CMakeLists.txt index a2b27f3a..c785dcad 100644 --- a/test/dynamic_loading/CMakeLists.txt +++ b/test/dynamic_loading/CMakeLists.txt @@ -27,7 +27,7 @@ add_executable( boost_openmethod-test_dynamic_loading main.cpp) target_link_libraries( boost_openmethod-test_dynamic_loading - PUBLIC Boost::openmethod + PUBLIC Boost::openmethod boost_openmethod-dl_test_registry PRIVATE Boost::openmethod Boost::unit_test_framework Boost::dll) add_dependencies( boost_openmethod-test_dynamic_loading diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index 6da7eb2c..f9c775ca 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -66,22 +66,15 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { constexpr auto global_mode = dll::load_mode::append_decorations /*| dll::load_mode::rtld_global*/; #endif - dll::shared_library registry_lib( - lib_dir / "boost_openmethod-dl_test_registry", global_mode); - auto& registry_get_ids = - registry_lib.get_alias("registry_get_ids"); - dll::shared_library method_lib( lib_dir / "boost_openmethod-dl_test_method", global_mode); auto& method_get_ids = method_lib.get_alias("method_get_ids"); - BOOST_TEST(same_ids(registry_get_ids(), method_get_ids())); + BOOST_TEST(same_ids(get_ids(), method_get_ids())); - auto& registry_initialize = - registry_lib.get_alias("registry_initialize"); auto& speak = method_lib.get_alias("call_speak"); - registry_initialize(); + initialize(); BOOST_TEST(speak() == "?"); dll::shared_library overrider_lib( @@ -92,9 +85,9 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { auto& method_get_fn = method_lib.get_alias("method_get_fn"); auto& overrider_get_fn = overrider_lib.get_alias("overrider_get_fn"); - BOOST_TEST(same_ids(registry_get_ids(), overrider_get_ids())); - // BOOST_TEST(method_get_fn() == overrider_get_fn); + BOOST_TEST(same_ids(get_ids(), overrider_get_ids())); + BOOST_TEST(method_get_fn() == overrider_get_fn()); - registry_initialize(); + initialize(); BOOST_TEST(speak() == "woof"); } From d257c6677179c98ff377ce08a405b5f09f23204e Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 14 Mar 2026 16:00:07 -0400 Subject: [PATCH 17/74] improve dl tests --- test/dynamic_loading/classes.hpp | 7 +++++ test/dynamic_loading/main.cpp | 45 ++++++++++++++++++++++-------- test/dynamic_loading/method.cpp | 13 +++------ test/dynamic_loading/method.hpp | 4 +++ test/dynamic_loading/overrider.cpp | 6 ++-- test/dynamic_loading/registry.cpp | 2 +- 6 files changed, 54 insertions(+), 23 deletions(-) diff --git a/test/dynamic_loading/classes.hpp b/test/dynamic_loading/classes.hpp index 2fcb7d61..e4bbf0aa 100644 --- a/test/dynamic_loading/classes.hpp +++ b/test/dynamic_loading/classes.hpp @@ -4,9 +4,16 @@ // or copy at http://www.boost.org/LICENSE_1_0.txt) #include +#include +#include struct Animal { virtual ~Animal() = default; }; struct Dog : Animal {}; + +static auto make_dog() { + auto p = &typeid(Dog); + return boost::openmethod::make_unique_virtual(); +} \ No newline at end of file diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index f9c775ca..86f9df66 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -25,6 +25,8 @@ constexpr auto n_policies = mp11::mp_size::value; using policy_ids_fn = const void**(); using method_fn = const void*(); +BOOST_OPENMETHOD_CLASSES(Animal, Dog); + bool same_ids(const void** ids1, const void** ids2) { using std::setw; BOOST_TEST_MESSAGE( @@ -68,26 +70,47 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { #endif dll::shared_library method_lib( lib_dir / "boost_openmethod-dl_test_method", global_mode); - auto& method_get_ids = - method_lib.get_alias("method_get_ids"); + auto& method_get_ids = method_lib.get_alias("get_ids"); + auto& method_speak = + method_lib.get_alias)>("call_speak"); + auto& method_make_dog = + method_lib.get_alias()>("make_dog"); + auto& method_get_fn = method_lib.get_alias("get_fn"); BOOST_TEST(same_ids(get_ids(), method_get_ids())); - auto& speak = method_lib.get_alias("call_speak"); initialize(); - BOOST_TEST(speak() == "?"); + + auto main_dog = make_dog(); + auto method_dog = method_make_dog(); + BOOST_TEST(main_dog.vptr() == method_dog.vptr()); +#ifdef _WIN32 + BOOST_TEST(&typeid(*main_dog.get()) != &typeid(*method_dog.get())); +#endif + BOOST_TEST(method_speak(main_dog) == "?"); + BOOST_TEST(method_speak(method_dog) == "?"); dll::shared_library overrider_lib( lib_dir / "boost_openmethod-dl_test_overrider", dll::load_mode::append_decorations); - auto& overrider_get_ids = - overrider_lib.get_alias("overrider_get_ids"); - auto& method_get_fn = method_lib.get_alias("method_get_fn"); - auto& overrider_get_fn = - overrider_lib.get_alias("overrider_get_fn"); + auto& overrider_get_ids = overrider_lib.get_alias("get_ids"); + auto& overrider_speak = + overrider_lib.get_alias)>("call_speak"); + auto& overrider_make_dog = + overrider_lib.get_alias()>("make_dog"); + auto& overrider_get_fn = overrider_lib.get_alias("get_fn"); + BOOST_TEST(same_ids(get_ids(), overrider_get_ids())); - BOOST_TEST(method_get_fn() == overrider_get_fn()); initialize(); - BOOST_TEST(speak() == "woof"); + auto overrider_dog = overrider_make_dog(); + main_dog = make_dog(); // because its vptr was invalidated by initialize() + method_dog = method_make_dog(); // ditto + BOOST_TEST(main_dog.vptr() == overrider_dog.vptr()); +#ifdef _WIN32 + BOOST_TEST(&typeid(*main_dog.get()) != &typeid(*overrider_dog.get())); +#endif + BOOST_TEST(overrider_speak(main_dog) == "woof"); + BOOST_TEST(overrider_speak(method_dog) == "woof"); + BOOST_TEST(overrider_speak(overrider_dog) == "woof"); } diff --git a/test/dynamic_loading/method.cpp b/test/dynamic_loading/method.cpp index b565adeb..11d4e271 100644 --- a/test/dynamic_loading/method.cpp +++ b/test/dynamic_loading/method.cpp @@ -27,12 +27,7 @@ BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { return "?"; } -BOOST_DLL_ALIAS(get_ids, method_get_ids) -BOOST_DLL_ALIAS(get_fn, method_get_fn) - -const char* call_speak() { - Dog snoopy; - return speak(snoopy); -} - -BOOST_DLL_AUTO_ALIAS(call_speak) \ No newline at end of file +BOOST_DLL_AUTO_ALIAS(get_ids) +BOOST_DLL_AUTO_ALIAS(get_fn) +BOOST_DLL_AUTO_ALIAS(make_dog); +BOOST_DLL_AUTO_ALIAS(call_speak); diff --git a/test/dynamic_loading/method.hpp b/test/dynamic_loading/method.hpp index 621e2fdb..c4a5b1c2 100644 --- a/test/dynamic_loading/method.hpp +++ b/test/dynamic_loading/method.hpp @@ -28,3 +28,7 @@ inline auto get_fn() { return static_cast(&BOOST_OPENMETHOD_TYPE( speak, (boost::openmethod::virtual_ptr), const char*)::fn); } + +inline auto call_speak(boost::openmethod::virtual_ptr animal) { + return speak(animal); +} \ No newline at end of file diff --git a/test/dynamic_loading/overrider.cpp b/test/dynamic_loading/overrider.cpp index ddafa3be..0d487f3d 100644 --- a/test/dynamic_loading/overrider.cpp +++ b/test/dynamic_loading/overrider.cpp @@ -17,5 +17,7 @@ BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { BOOST_OPENMETHOD_CLASSES(Animal, Dog); -BOOST_DLL_ALIAS(get_ids, overrider_get_ids) -BOOST_DLL_ALIAS(get_fn, overrider_get_fn) +BOOST_DLL_AUTO_ALIAS(get_ids) +BOOST_DLL_AUTO_ALIAS(get_fn) +BOOST_DLL_AUTO_ALIAS(make_dog); +BOOST_DLL_AUTO_ALIAS(call_speak); diff --git a/test/dynamic_loading/registry.cpp b/test/dynamic_loading/registry.cpp index 9062ce5e..e2a1cd6a 100644 --- a/test/dynamic_loading/registry.cpp +++ b/test/dynamic_loading/registry.cpp @@ -28,4 +28,4 @@ void registry_initialize() { boost::openmethod::initialize(trace::from_env()); } -BOOST_DLL_AUTO_ALIAS(registry_initialize); +BOOST_DLL_ALIAS(make_dog, registry_make_dog); From d27f3e2327726d3d8954f3886b158d3db463b105 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 14 Mar 2026 16:59:26 -0400 Subject: [PATCH 18/74] mingw --- test/dynamic_loading/CMakeLists.txt | 13 +++++++++ test/dynamic_loading/classes.hpp | 9 ++++--- test/dynamic_loading/main.cpp | 3 +++ test/dynamic_loading/method.cpp | 9 +++++-- test/dynamic_loading/method.hpp | 16 ++++++----- test/dynamic_loading/overrider.cpp | 9 +++++-- test/dynamic_loading/registry.cpp | 8 +++--- test/dynamic_loading/registry.hpp | 41 ++++++++++++++++------------- 8 files changed, 72 insertions(+), 36 deletions(-) diff --git a/test/dynamic_loading/CMakeLists.txt b/test/dynamic_loading/CMakeLists.txt index c785dcad..f89b98ae 100644 --- a/test/dynamic_loading/CMakeLists.txt +++ b/test/dynamic_loading/CMakeLists.txt @@ -35,6 +35,19 @@ add_dependencies( boost_openmethod-dl_test_method boost_openmethod-dl_test_overrider) +# On MinGW/GCC+Windows, BOOST_DLL_AUTO_ALIAS only emits a declaration without +# a definition (relying on --export-all-symbols). Force full alias instantiation +# so the alias pointer variables are actually defined and exported. +if(MINGW) + foreach(target + boost_openmethod-dl_test_registry + boost_openmethod-dl_test_method + boost_openmethod-dl_test_overrider) + target_compile_definitions( + ${target} PRIVATE BOOST_DLL_FORCE_ALIAS_INSTANTIATION) + endforeach() +endif() + # Put all outputs in the same directory so Boost.DLL can locate the shared # libraries relative to the test executable at runtime. # ENABLE_EXPORTS is required so that symbols from each shared library are diff --git a/test/dynamic_loading/classes.hpp b/test/dynamic_loading/classes.hpp index e4bbf0aa..6875f09d 100644 --- a/test/dynamic_loading/classes.hpp +++ b/test/dynamic_loading/classes.hpp @@ -13,7 +13,8 @@ struct Animal { struct Dog : Animal {}; -static auto make_dog() { - auto p = &typeid(Dog); - return boost::openmethod::make_unique_virtual(); -} \ No newline at end of file +#define DEFINE_MAKE_DOG() \ + auto make_dog() { \ + auto p = &typeid(Dog); \ + return boost::openmethod::make_unique_virtual(); \ + } \ No newline at end of file diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index 86f9df66..3f873d18 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -27,6 +27,9 @@ using method_fn = const void*(); BOOST_OPENMETHOD_CLASSES(Animal, Dog); +DEFINE_GET_IDS() +DEFINE_MAKE_DOG() + bool same_ids(const void** ids1, const void** ids2) { using std::setw; BOOST_TEST_MESSAGE( diff --git a/test/dynamic_loading/method.cpp b/test/dynamic_loading/method.cpp index 11d4e271..919e3c90 100644 --- a/test/dynamic_loading/method.cpp +++ b/test/dynamic_loading/method.cpp @@ -27,7 +27,12 @@ BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { return "?"; } +DEFINE_GET_IDS() +DEFINE_MAKE_DOG() +DEFINE_GET_FN() +DEFINE_CALL_SPEAK() + BOOST_DLL_AUTO_ALIAS(get_ids) BOOST_DLL_AUTO_ALIAS(get_fn) -BOOST_DLL_AUTO_ALIAS(make_dog); -BOOST_DLL_AUTO_ALIAS(call_speak); +BOOST_DLL_AUTO_ALIAS(make_dog) +BOOST_DLL_AUTO_ALIAS(call_speak) diff --git a/test/dynamic_loading/method.hpp b/test/dynamic_loading/method.hpp index c4a5b1c2..3fc12235 100644 --- a/test/dynamic_loading/method.hpp +++ b/test/dynamic_loading/method.hpp @@ -24,11 +24,13 @@ BOOST_OPENMETHOD( speak, (boost::openmethod::virtual_ptr), const char*, METHOD_DECLSPEC); -inline auto get_fn() { - return static_cast(&BOOST_OPENMETHOD_TYPE( - speak, (boost::openmethod::virtual_ptr), const char*)::fn); -} +#define DEFINE_GET_FN() \ + auto get_fn() { \ + return static_cast(&BOOST_OPENMETHOD_TYPE( \ + speak, (boost::openmethod::virtual_ptr), const char*)::fn);\ + } -inline auto call_speak(boost::openmethod::virtual_ptr animal) { - return speak(animal); -} \ No newline at end of file +#define DEFINE_CALL_SPEAK() \ + auto call_speak(boost::openmethod::virtual_ptr animal) { \ + return speak(animal); \ + } \ No newline at end of file diff --git a/test/dynamic_loading/overrider.cpp b/test/dynamic_loading/overrider.cpp index 0d487f3d..866762c9 100644 --- a/test/dynamic_loading/overrider.cpp +++ b/test/dynamic_loading/overrider.cpp @@ -17,7 +17,12 @@ BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { BOOST_OPENMETHOD_CLASSES(Animal, Dog); +DEFINE_GET_IDS() +DEFINE_MAKE_DOG() +DEFINE_GET_FN() +DEFINE_CALL_SPEAK() + BOOST_DLL_AUTO_ALIAS(get_ids) BOOST_DLL_AUTO_ALIAS(get_fn) -BOOST_DLL_AUTO_ALIAS(make_dog); -BOOST_DLL_AUTO_ALIAS(call_speak); +BOOST_DLL_AUTO_ALIAS(make_dog) +BOOST_DLL_AUTO_ALIAS(call_speak) diff --git a/test/dynamic_loading/registry.cpp b/test/dynamic_loading/registry.cpp index e2a1cd6a..4b5871e4 100644 --- a/test/dynamic_loading/registry.cpp +++ b/test/dynamic_loading/registry.cpp @@ -22,10 +22,12 @@ static_assert(std::is_same_v); BOOST_OPENMETHOD_CLASSES(Animal, Dog); -BOOST_DLL_ALIAS(get_ids, registry_get_ids); +DEFINE_GET_IDS() +DEFINE_MAKE_DOG() + +BOOST_DLL_AUTO_ALIAS(get_ids) +BOOST_DLL_AUTO_ALIAS(make_dog) void registry_initialize() { boost::openmethod::initialize(trace::from_env()); } - -BOOST_DLL_ALIAS(make_dog, registry_make_dog); diff --git a/test/dynamic_loading/registry.hpp b/test/dynamic_loading/registry.hpp index 767824be..b8c4d03e 100644 --- a/test/dynamic_loading/registry.hpp +++ b/test/dynamic_loading/registry.hpp @@ -26,23 +26,28 @@ boost_openmethod_declspec(default_registry&); #include -static auto get_ids() -> const void** { - using namespace boost::openmethod; - namespace mp11 = boost::mp11; - - constexpr auto n_policies = mp11::mp_size::value; - static const void* ids[1 + n_policies + 1] = {default_registry::id()}; - std::size_t i = 1; - - mp11::mp_for_each([&](auto p) { - using P = decltype(p); - - if constexpr (detail::has_id>) { - ids[i++] = default_registry::policy

::id(); - } - }); - - return ids; -} +// Each TU that needs to export get_ids() should define it as a non-static +// function so that BOOST_DLL_AUTO_ALIAS can export it on MinGW. +#define DEFINE_GET_IDS() \ + auto get_ids() -> const void** { \ + using namespace boost::openmethod; \ + namespace mp11 = boost::mp11; \ + \ + constexpr auto n_policies = \ + mp11::mp_size::value; \ + static const void* ids[1 + n_policies + 1] = { \ + default_registry::id()}; \ + std::size_t i = 1; \ + \ + mp11::mp_for_each([&](auto p) { \ + using P = decltype(p); \ + \ + if constexpr (detail::has_id>) { \ + ids[i++] = default_registry::policy

::id(); \ + } \ + }); \ + \ + return ids; \ + } #endif From 1c0d683d5de6265831fedce9bbc83d1952bc01bf Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 14 Mar 2026 17:36:17 -0400 Subject: [PATCH 19/74] with b2 --- test/Jamfile | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/Jamfile b/test/Jamfile index a4becf8c..87124491 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -46,6 +46,46 @@ for local src in [ glob compile_fail_*.cpp ] compile-fail $(src) ; } +# Dynamic loading test +# Requires Boost.DLL; builds three shared libraries and a test executable +# that loads them at runtime. +lib dl_test_registry + : dynamic_loading/registry.cpp + : shared + BOOST_DLL_FORCE_ALIAS_INSTANTIATION + global + /boost/dll//boost_dll + ; + +lib dl_test_method + : dynamic_loading/method.cpp + : shared + BOOST_DLL_FORCE_ALIAS_INSTANTIATION + global + /boost/dll//boost_dll + dl_test_registry + ; + +lib dl_test_overrider + : dynamic_loading/overrider.cpp + : shared + BOOST_DLL_FORCE_ALIAS_INSTANTIATION + global + /boost/dll//boost_dll + dl_test_method + ; + +run dynamic_loading/main.cpp + unit_test_framework + : # args + : dl_test_method dl_test_overrider dl_test_registry # input-files (runtime deps) + : shared + global + /boost/dll//boost_dll + dl_test_registry + : test_dynamic_loading + ; + # quick (for CI) alias quick : test_dispatch ; explicit quick ; From 0d1adb05fb13c65be73bf15334e0b8a87acd010b Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 15 Mar 2026 13:13:33 -0400 Subject: [PATCH 20/74] mingw --- .../ROOT/examples/shared_libs/CMakeLists.txt | 14 +++++++++++++- .../examples/shared_libs/dynamic_main.cpp | 19 +++++++------------ .../examples/shared_libs/indirect_main.cpp | 13 +++++-------- test/dynamic_loading/CMakeLists.txt | 9 +++++---- test/dynamic_loading/main.cpp | 9 ++------- 5 files changed, 32 insertions(+), 32 deletions(-) diff --git a/doc/modules/ROOT/examples/shared_libs/CMakeLists.txt b/doc/modules/ROOT/examples/shared_libs/CMakeLists.txt index 228185aa..5d7ef32b 100644 --- a/doc/modules/ROOT/examples/shared_libs/CMakeLists.txt +++ b/doc/modules/ROOT/examples/shared_libs/CMakeLists.txt @@ -32,7 +32,19 @@ add_library(boost_openmethod-shared SHARED extensions.cpp) target_link_libraries(boost_openmethod-shared PRIVATE Boost::openmethod boost_openmethod-dynamic) set_target_properties(boost_openmethod-shared PROPERTIES ENABLE_EXPORTS ON) -add_test(NAME boost_openmethod-dynamic_shared COMMAND boost_openmethod-dynamic) +foreach(target boost_openmethod-dynamic boost_openmethod-shared) + set_target_properties( + ${target} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") +endforeach() + +add_test( + NAME boost_openmethod-dynamic_shared + COMMAND boost_openmethod-dynamic + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") # NOTE: build the following target (or ALL) when working on this. Just building # boost_openmethod-dynamic will *not* build the DLL. diff --git a/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp b/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp index c116b6aa..102e4f6c 100644 --- a/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp +++ b/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp @@ -3,14 +3,7 @@ // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -#ifndef LIBRARY_NAME -#ifdef _MSC_VER -#define LIBRARY_NAME "boost_openmethod-shared.dll" -#else -#define LIBRARY_NAME "libboost_openmethod-shared.so" - -#endif -#endif +#define LIBRARY_NAME "boost_openmethod-shared" // dynamic_main.cpp @@ -77,11 +70,13 @@ int main() { // tag::load[] // ... - std::cout << "\nAfter loading the shared library.\n"; + std::cout << "\nLoading " << LIBRARY_NAME << ".\n"; - boost::dll::shared_library lib( - boost::dll::program_location().parent_path() / LIBRARY_NAME, - boost::dll::load_mode::rtld_now); + using namespace boost::dll; + shared_library lib( + program_location().parent_path() / LIBRARY_NAME, + load_mode::append_decorations); + std::cout << "\nLoaded " << LIBRARY_NAME << ".\n"; boost::openmethod::initialize(trace::from_env()); std::cout << "cow meets wolf -> " diff --git a/doc/modules/ROOT/examples/shared_libs/indirect_main.cpp b/doc/modules/ROOT/examples/shared_libs/indirect_main.cpp index 555435e0..ca513fbe 100644 --- a/doc/modules/ROOT/examples/shared_libs/indirect_main.cpp +++ b/doc/modules/ROOT/examples/shared_libs/indirect_main.cpp @@ -3,11 +3,7 @@ // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -#ifdef _MSC_VER -#define LIBRARY_NAME "indirect_shared.dll" -#else -#define LIBRARY_NAME "libindirect_shared.so" -#endif +#define LIBRARY_NAME "indirect_shared" // tag::content[] // indirect_main.cpp @@ -49,9 +45,10 @@ auto main() -> int { std::cout << "\nAfter loading the shared library.\n"; - boost::dll::shared_library lib( - boost::dll::program_location().parent_path() / LIBRARY_NAME, - boost::dll::load_mode::rtld_now); + using namespace boost::dll; + shared_library lib( + program_location().parent_path() / LIBRARY_NAME, + load_mode::append_decorations); boost::openmethod::initialize(); diff --git a/test/dynamic_loading/CMakeLists.txt b/test/dynamic_loading/CMakeLists.txt index f89b98ae..f3fc65be 100644 --- a/test/dynamic_loading/CMakeLists.txt +++ b/test/dynamic_loading/CMakeLists.txt @@ -67,9 +67,9 @@ foreach( set_target_properties( ${target} PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/stage/bin/$" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/stage/bin/$" - ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/stage/bin/$" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" ENABLE_EXPORTS ON CXX_VISIBILITY_PRESET default VISIBILITY_INLINES_HIDDEN OFF) @@ -87,7 +87,8 @@ set_tests_properties( add_test( NAME boost_openmethod-test_dynamic_loading - COMMAND boost_openmethod-test_dynamic_loading) + COMMAND boost_openmethod-test_dynamic_loading + WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") set_tests_properties( boost_openmethod-test_dynamic_loading PROPERTIES FIXTURES_REQUIRED boost_openmethod-test_dynamic_loading-fixture) diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index 3f873d18..3280d982 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -65,14 +65,9 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { // Use rtld_global for registry and method so their symbols (fn, policy // statics) are visible globally and win over any locally-instantiated // copies when the overrider library is subsequently loaded. -#ifdef _WIN32 - constexpr auto global_mode = dll::load_mode::append_decorations; -#else - constexpr auto global_mode = - dll::load_mode::append_decorations /*| dll::load_mode::rtld_global*/; -#endif dll::shared_library method_lib( - lib_dir / "boost_openmethod-dl_test_method", global_mode); + lib_dir / "boost_openmethod-dl_test_method", + dll::load_mode::append_decorations); auto& method_get_ids = method_lib.get_alias("get_ids"); auto& method_speak = method_lib.get_alias)>("call_speak"); From 7f114d15be7f510172ca7012864081456ea74fc0 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Mon, 16 Mar 2026 21:00:31 -0400 Subject: [PATCH 21/74] minor reformatting --- test/dynamic_loading/classes.hpp | 3 +-- test/dynamic_loading/method.hpp | 5 +++-- test/dynamic_loading/registry.hpp | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/test/dynamic_loading/classes.hpp b/test/dynamic_loading/classes.hpp index 6875f09d..9f0484c1 100644 --- a/test/dynamic_loading/classes.hpp +++ b/test/dynamic_loading/classes.hpp @@ -15,6 +15,5 @@ struct Dog : Animal {}; #define DEFINE_MAKE_DOG() \ auto make_dog() { \ - auto p = &typeid(Dog); \ return boost::openmethod::make_unique_virtual(); \ - } \ No newline at end of file + } diff --git a/test/dynamic_loading/method.hpp b/test/dynamic_loading/method.hpp index 3fc12235..fa9555dd 100644 --- a/test/dynamic_loading/method.hpp +++ b/test/dynamic_loading/method.hpp @@ -27,10 +27,11 @@ BOOST_OPENMETHOD( #define DEFINE_GET_FN() \ auto get_fn() { \ return static_cast(&BOOST_OPENMETHOD_TYPE( \ - speak, (boost::openmethod::virtual_ptr), const char*)::fn);\ + speak, (boost::openmethod::virtual_ptr), \ + const char*)::fn); \ } #define DEFINE_CALL_SPEAK() \ auto call_speak(boost::openmethod::virtual_ptr animal) { \ return speak(animal); \ - } \ No newline at end of file + } diff --git a/test/dynamic_loading/registry.hpp b/test/dynamic_loading/registry.hpp index b8c4d03e..afe47af2 100644 --- a/test/dynamic_loading/registry.hpp +++ b/test/dynamic_loading/registry.hpp @@ -35,8 +35,7 @@ boost_openmethod_declspec(default_registry&); \ constexpr auto n_policies = \ mp11::mp_size::value; \ - static const void* ids[1 + n_policies + 1] = { \ - default_registry::id()}; \ + static const void* ids[1 + n_policies + 1] = {default_registry::id()}; \ std::size_t i = 1; \ \ mp11::mp_for_each([&](auto p) { \ From 0b11d2e5ff511982500d8f512c78d7c4dbd1d6a5 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Mon, 16 Mar 2026 21:01:13 -0400 Subject: [PATCH 22/74] work with b2 on posix (claude) --- test/Jamfile | 16 +++++---- test/dynamic_loading/main.cpp | 55 +++++++++++++++++++++--------- test/dynamic_loading/method.cpp | 8 ++--- test/dynamic_loading/overrider.cpp | 8 ++--- 4 files changed, 55 insertions(+), 32 deletions(-) diff --git a/test/Jamfile b/test/Jamfile index 87124491..241f9e88 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -49,7 +49,7 @@ for local src in [ glob compile_fail_*.cpp ] # Dynamic loading test # Requires Boost.DLL; builds three shared libraries and a test executable # that loads them at runtime. -lib dl_test_registry +lib boost_openmethod-dl_test_registry : dynamic_loading/registry.cpp : shared BOOST_DLL_FORCE_ALIAS_INSTANTIATION @@ -57,32 +57,34 @@ lib dl_test_registry /boost/dll//boost_dll ; -lib dl_test_method +lib boost_openmethod-dl_test_method : dynamic_loading/method.cpp : shared BOOST_DLL_FORCE_ALIAS_INSTANTIATION global /boost/dll//boost_dll - dl_test_registry + boost_openmethod-dl_test_registry ; -lib dl_test_overrider +lib boost_openmethod-dl_test_overrider : dynamic_loading/overrider.cpp : shared BOOST_DLL_FORCE_ALIAS_INSTANTIATION global /boost/dll//boost_dll - dl_test_method + boost_openmethod-dl_test_method ; run dynamic_loading/main.cpp unit_test_framework : # args - : dl_test_method dl_test_overrider dl_test_registry # input-files (runtime deps) + : boost_openmethod-dl_test_method boost_openmethod-dl_test_overrider boost_openmethod-dl_test_registry # input-files (runtime deps) : shared global /boost/dll//boost_dll - dl_test_registry + boost_openmethod-dl_test_registry + gcc,linux:-rdynamic + clang,linux:-rdynamic : test_dynamic_loading ; diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index 3280d982..460002e0 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -20,8 +20,6 @@ using namespace boost::openmethod; namespace mp11 = boost::mp11; -constexpr auto n_policies = mp11::mp_size::value; - using policy_ids_fn = const void**(); using method_fn = const void*(); @@ -59,21 +57,47 @@ bool same_ids(const void** ids1, const void** ids2) { BOOST_AUTO_TEST_CASE(test_shared_state) { namespace dll = boost::dll; - auto lib_dir = dll::program_location().parent_path(); + namespace fs = boost::filesystem; + namespace ut = boost::unit_test; + + // b2 passes input-files as extra argv in alphabetical-path order (not + // necessarily the order listed in the Jamfile). CMake puts all outputs in + // one directory, found via program_location(), with no extra argv. + auto& mts = ut::framework::master_test_suite(); + fs::path method_path, overrider_path; + dll::load_mode::type load_flags; + + // Search argv for paths containing the library names (b2 mode). + for (int i = 1; i < mts.argc; ++i) { + std::string arg = mts.argv[i]; + if (arg.find("dl_test_method") != std::string::npos) { + method_path = arg; + } else if (arg.find("dl_test_overrider") != std::string::npos) { + overrider_path = arg; + } + } + + if (!method_path.empty()) { + // b2 mode: full versioned paths passed as argv (e.g. libfoo.so.1.91.0) + load_flags = dll::load_mode::default_mode; + } else { + // CMake mode: libraries are alongside the executable, use decorations + auto lib_dir = dll::program_location().parent_path(); + method_path = lib_dir / "boost_openmethod-dl_test_method"; + overrider_path = lib_dir / "boost_openmethod-dl_test_overrider"; + load_flags = dll::load_mode::append_decorations; + } // Load all three shared libraries via Boost.DLL. // Use rtld_global for registry and method so their symbols (fn, policy // statics) are visible globally and win over any locally-instantiated // copies when the overrider library is subsequently loaded. - dll::shared_library method_lib( - lib_dir / "boost_openmethod-dl_test_method", - dll::load_mode::append_decorations); - auto& method_get_ids = method_lib.get_alias("get_ids"); + dll::shared_library method_lib(method_path, load_flags); + auto& method_get_ids = method_lib.get_alias("method_get_ids"); auto& method_speak = - method_lib.get_alias)>("call_speak"); + method_lib.get_alias)>("method_call_speak"); auto& method_make_dog = - method_lib.get_alias()>("make_dog"); - auto& method_get_fn = method_lib.get_alias("get_fn"); + method_lib.get_alias()>("method_make_dog"); BOOST_TEST(same_ids(get_ids(), method_get_ids())); @@ -88,15 +112,12 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { BOOST_TEST(method_speak(main_dog) == "?"); BOOST_TEST(method_speak(method_dog) == "?"); - dll::shared_library overrider_lib( - lib_dir / "boost_openmethod-dl_test_overrider", - dll::load_mode::append_decorations); - auto& overrider_get_ids = overrider_lib.get_alias("get_ids"); + dll::shared_library overrider_lib(overrider_path, load_flags); + auto& overrider_get_ids = overrider_lib.get_alias("overrider_get_ids"); auto& overrider_speak = - overrider_lib.get_alias)>("call_speak"); + overrider_lib.get_alias)>("overrider_call_speak"); auto& overrider_make_dog = - overrider_lib.get_alias()>("make_dog"); - auto& overrider_get_fn = overrider_lib.get_alias("get_fn"); + overrider_lib.get_alias()>("overrider_make_dog"); BOOST_TEST(same_ids(get_ids(), overrider_get_ids())); diff --git a/test/dynamic_loading/method.cpp b/test/dynamic_loading/method.cpp index 919e3c90..ecfbc479 100644 --- a/test/dynamic_loading/method.cpp +++ b/test/dynamic_loading/method.cpp @@ -32,7 +32,7 @@ DEFINE_MAKE_DOG() DEFINE_GET_FN() DEFINE_CALL_SPEAK() -BOOST_DLL_AUTO_ALIAS(get_ids) -BOOST_DLL_AUTO_ALIAS(get_fn) -BOOST_DLL_AUTO_ALIAS(make_dog) -BOOST_DLL_AUTO_ALIAS(call_speak) +BOOST_DLL_ALIAS(get_ids, method_get_ids) +BOOST_DLL_ALIAS(get_fn, method_get_fn) +BOOST_DLL_ALIAS(make_dog, method_make_dog) +BOOST_DLL_ALIAS(call_speak, method_call_speak) diff --git a/test/dynamic_loading/overrider.cpp b/test/dynamic_loading/overrider.cpp index 866762c9..7338fcc8 100644 --- a/test/dynamic_loading/overrider.cpp +++ b/test/dynamic_loading/overrider.cpp @@ -22,7 +22,7 @@ DEFINE_MAKE_DOG() DEFINE_GET_FN() DEFINE_CALL_SPEAK() -BOOST_DLL_AUTO_ALIAS(get_ids) -BOOST_DLL_AUTO_ALIAS(get_fn) -BOOST_DLL_AUTO_ALIAS(make_dog) -BOOST_DLL_AUTO_ALIAS(call_speak) +BOOST_DLL_ALIAS(get_ids, overrider_get_ids) +BOOST_DLL_ALIAS(get_fn, overrider_get_fn) +BOOST_DLL_ALIAS(make_dog, overrider_make_dog) +BOOST_DLL_ALIAS(call_speak, overrider_call_speak) From 23deeec2375b1542f920342fe07f90d377a5ccf2 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 22 Mar 2026 11:36:24 -0400 Subject: [PATCH 23/74] Revert "work with b2 on posix (claude)" This reverts commit 485d1a6e11d797398a7529ed03c519b85761082c. --- test/Jamfile | 16 ++++----- test/dynamic_loading/main.cpp | 55 +++++++++--------------------- test/dynamic_loading/method.cpp | 8 ++--- test/dynamic_loading/overrider.cpp | 8 ++--- 4 files changed, 32 insertions(+), 55 deletions(-) diff --git a/test/Jamfile b/test/Jamfile index 241f9e88..87124491 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -49,7 +49,7 @@ for local src in [ glob compile_fail_*.cpp ] # Dynamic loading test # Requires Boost.DLL; builds three shared libraries and a test executable # that loads them at runtime. -lib boost_openmethod-dl_test_registry +lib dl_test_registry : dynamic_loading/registry.cpp : shared BOOST_DLL_FORCE_ALIAS_INSTANTIATION @@ -57,34 +57,32 @@ lib boost_openmethod-dl_test_registry /boost/dll//boost_dll ; -lib boost_openmethod-dl_test_method +lib dl_test_method : dynamic_loading/method.cpp : shared BOOST_DLL_FORCE_ALIAS_INSTANTIATION global /boost/dll//boost_dll - boost_openmethod-dl_test_registry + dl_test_registry ; -lib boost_openmethod-dl_test_overrider +lib dl_test_overrider : dynamic_loading/overrider.cpp : shared BOOST_DLL_FORCE_ALIAS_INSTANTIATION global /boost/dll//boost_dll - boost_openmethod-dl_test_method + dl_test_method ; run dynamic_loading/main.cpp unit_test_framework : # args - : boost_openmethod-dl_test_method boost_openmethod-dl_test_overrider boost_openmethod-dl_test_registry # input-files (runtime deps) + : dl_test_method dl_test_overrider dl_test_registry # input-files (runtime deps) : shared global /boost/dll//boost_dll - boost_openmethod-dl_test_registry - gcc,linux:-rdynamic - clang,linux:-rdynamic + dl_test_registry : test_dynamic_loading ; diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index 460002e0..3280d982 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -20,6 +20,8 @@ using namespace boost::openmethod; namespace mp11 = boost::mp11; +constexpr auto n_policies = mp11::mp_size::value; + using policy_ids_fn = const void**(); using method_fn = const void*(); @@ -57,47 +59,21 @@ bool same_ids(const void** ids1, const void** ids2) { BOOST_AUTO_TEST_CASE(test_shared_state) { namespace dll = boost::dll; - namespace fs = boost::filesystem; - namespace ut = boost::unit_test; - - // b2 passes input-files as extra argv in alphabetical-path order (not - // necessarily the order listed in the Jamfile). CMake puts all outputs in - // one directory, found via program_location(), with no extra argv. - auto& mts = ut::framework::master_test_suite(); - fs::path method_path, overrider_path; - dll::load_mode::type load_flags; - - // Search argv for paths containing the library names (b2 mode). - for (int i = 1; i < mts.argc; ++i) { - std::string arg = mts.argv[i]; - if (arg.find("dl_test_method") != std::string::npos) { - method_path = arg; - } else if (arg.find("dl_test_overrider") != std::string::npos) { - overrider_path = arg; - } - } - - if (!method_path.empty()) { - // b2 mode: full versioned paths passed as argv (e.g. libfoo.so.1.91.0) - load_flags = dll::load_mode::default_mode; - } else { - // CMake mode: libraries are alongside the executable, use decorations - auto lib_dir = dll::program_location().parent_path(); - method_path = lib_dir / "boost_openmethod-dl_test_method"; - overrider_path = lib_dir / "boost_openmethod-dl_test_overrider"; - load_flags = dll::load_mode::append_decorations; - } + auto lib_dir = dll::program_location().parent_path(); // Load all three shared libraries via Boost.DLL. // Use rtld_global for registry and method so their symbols (fn, policy // statics) are visible globally and win over any locally-instantiated // copies when the overrider library is subsequently loaded. - dll::shared_library method_lib(method_path, load_flags); - auto& method_get_ids = method_lib.get_alias("method_get_ids"); + dll::shared_library method_lib( + lib_dir / "boost_openmethod-dl_test_method", + dll::load_mode::append_decorations); + auto& method_get_ids = method_lib.get_alias("get_ids"); auto& method_speak = - method_lib.get_alias)>("method_call_speak"); + method_lib.get_alias)>("call_speak"); auto& method_make_dog = - method_lib.get_alias()>("method_make_dog"); + method_lib.get_alias()>("make_dog"); + auto& method_get_fn = method_lib.get_alias("get_fn"); BOOST_TEST(same_ids(get_ids(), method_get_ids())); @@ -112,12 +88,15 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { BOOST_TEST(method_speak(main_dog) == "?"); BOOST_TEST(method_speak(method_dog) == "?"); - dll::shared_library overrider_lib(overrider_path, load_flags); - auto& overrider_get_ids = overrider_lib.get_alias("overrider_get_ids"); + dll::shared_library overrider_lib( + lib_dir / "boost_openmethod-dl_test_overrider", + dll::load_mode::append_decorations); + auto& overrider_get_ids = overrider_lib.get_alias("get_ids"); auto& overrider_speak = - overrider_lib.get_alias)>("overrider_call_speak"); + overrider_lib.get_alias)>("call_speak"); auto& overrider_make_dog = - overrider_lib.get_alias()>("overrider_make_dog"); + overrider_lib.get_alias()>("make_dog"); + auto& overrider_get_fn = overrider_lib.get_alias("get_fn"); BOOST_TEST(same_ids(get_ids(), overrider_get_ids())); diff --git a/test/dynamic_loading/method.cpp b/test/dynamic_loading/method.cpp index ecfbc479..919e3c90 100644 --- a/test/dynamic_loading/method.cpp +++ b/test/dynamic_loading/method.cpp @@ -32,7 +32,7 @@ DEFINE_MAKE_DOG() DEFINE_GET_FN() DEFINE_CALL_SPEAK() -BOOST_DLL_ALIAS(get_ids, method_get_ids) -BOOST_DLL_ALIAS(get_fn, method_get_fn) -BOOST_DLL_ALIAS(make_dog, method_make_dog) -BOOST_DLL_ALIAS(call_speak, method_call_speak) +BOOST_DLL_AUTO_ALIAS(get_ids) +BOOST_DLL_AUTO_ALIAS(get_fn) +BOOST_DLL_AUTO_ALIAS(make_dog) +BOOST_DLL_AUTO_ALIAS(call_speak) diff --git a/test/dynamic_loading/overrider.cpp b/test/dynamic_loading/overrider.cpp index 7338fcc8..866762c9 100644 --- a/test/dynamic_loading/overrider.cpp +++ b/test/dynamic_loading/overrider.cpp @@ -22,7 +22,7 @@ DEFINE_MAKE_DOG() DEFINE_GET_FN() DEFINE_CALL_SPEAK() -BOOST_DLL_ALIAS(get_ids, overrider_get_ids) -BOOST_DLL_ALIAS(get_fn, overrider_get_fn) -BOOST_DLL_ALIAS(make_dog, overrider_make_dog) -BOOST_DLL_ALIAS(call_speak, overrider_call_speak) +BOOST_DLL_AUTO_ALIAS(get_ids) +BOOST_DLL_AUTO_ALIAS(get_fn) +BOOST_DLL_AUTO_ALIAS(make_dog) +BOOST_DLL_AUTO_ALIAS(call_speak) From 676ccd8becd4bf8f1a459b17c9212b6efdbf5759 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 22 Mar 2026 11:36:24 -0400 Subject: [PATCH 24/74] Revert "minor reformatting" This reverts commit b5c05560abb8e7073aff8e1c7acebf5ff0ed6d95. --- test/dynamic_loading/classes.hpp | 3 ++- test/dynamic_loading/method.hpp | 5 ++--- test/dynamic_loading/registry.hpp | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/test/dynamic_loading/classes.hpp b/test/dynamic_loading/classes.hpp index 9f0484c1..6875f09d 100644 --- a/test/dynamic_loading/classes.hpp +++ b/test/dynamic_loading/classes.hpp @@ -15,5 +15,6 @@ struct Dog : Animal {}; #define DEFINE_MAKE_DOG() \ auto make_dog() { \ + auto p = &typeid(Dog); \ return boost::openmethod::make_unique_virtual(); \ - } + } \ No newline at end of file diff --git a/test/dynamic_loading/method.hpp b/test/dynamic_loading/method.hpp index fa9555dd..3fc12235 100644 --- a/test/dynamic_loading/method.hpp +++ b/test/dynamic_loading/method.hpp @@ -27,11 +27,10 @@ BOOST_OPENMETHOD( #define DEFINE_GET_FN() \ auto get_fn() { \ return static_cast(&BOOST_OPENMETHOD_TYPE( \ - speak, (boost::openmethod::virtual_ptr), \ - const char*)::fn); \ + speak, (boost::openmethod::virtual_ptr), const char*)::fn);\ } #define DEFINE_CALL_SPEAK() \ auto call_speak(boost::openmethod::virtual_ptr animal) { \ return speak(animal); \ - } + } \ No newline at end of file diff --git a/test/dynamic_loading/registry.hpp b/test/dynamic_loading/registry.hpp index afe47af2..b8c4d03e 100644 --- a/test/dynamic_loading/registry.hpp +++ b/test/dynamic_loading/registry.hpp @@ -35,7 +35,8 @@ boost_openmethod_declspec(default_registry&); \ constexpr auto n_policies = \ mp11::mp_size::value; \ - static const void* ids[1 + n_policies + 1] = {default_registry::id()}; \ + static const void* ids[1 + n_policies + 1] = { \ + default_registry::id()}; \ std::size_t i = 1; \ \ mp11::mp_for_each([&](auto p) { \ From 11dc8c2428b6fc0528e37b1627801ad4043bd7f8 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 22 Mar 2026 11:36:24 -0400 Subject: [PATCH 25/74] Revert "mingw" This reverts commit f9883d08ef5ebd98d1e8f2daacca02883f3f948f. --- .../ROOT/examples/shared_libs/CMakeLists.txt | 14 +------------- .../examples/shared_libs/dynamic_main.cpp | 19 ++++++++++++------- .../examples/shared_libs/indirect_main.cpp | 13 ++++++++----- test/dynamic_loading/CMakeLists.txt | 9 ++++----- test/dynamic_loading/main.cpp | 9 +++++++-- 5 files changed, 32 insertions(+), 32 deletions(-) diff --git a/doc/modules/ROOT/examples/shared_libs/CMakeLists.txt b/doc/modules/ROOT/examples/shared_libs/CMakeLists.txt index 5d7ef32b..228185aa 100644 --- a/doc/modules/ROOT/examples/shared_libs/CMakeLists.txt +++ b/doc/modules/ROOT/examples/shared_libs/CMakeLists.txt @@ -32,19 +32,7 @@ add_library(boost_openmethod-shared SHARED extensions.cpp) target_link_libraries(boost_openmethod-shared PRIVATE Boost::openmethod boost_openmethod-dynamic) set_target_properties(boost_openmethod-shared PROPERTIES ENABLE_EXPORTS ON) -foreach(target boost_openmethod-dynamic boost_openmethod-shared) - set_target_properties( - ${target} - PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" - ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") -endforeach() - -add_test( - NAME boost_openmethod-dynamic_shared - COMMAND boost_openmethod-dynamic - WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") +add_test(NAME boost_openmethod-dynamic_shared COMMAND boost_openmethod-dynamic) # NOTE: build the following target (or ALL) when working on this. Just building # boost_openmethod-dynamic will *not* build the DLL. diff --git a/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp b/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp index 102e4f6c..c116b6aa 100644 --- a/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp +++ b/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp @@ -3,7 +3,14 @@ // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -#define LIBRARY_NAME "boost_openmethod-shared" +#ifndef LIBRARY_NAME +#ifdef _MSC_VER +#define LIBRARY_NAME "boost_openmethod-shared.dll" +#else +#define LIBRARY_NAME "libboost_openmethod-shared.so" + +#endif +#endif // dynamic_main.cpp @@ -70,13 +77,11 @@ int main() { // tag::load[] // ... - std::cout << "\nLoading " << LIBRARY_NAME << ".\n"; + std::cout << "\nAfter loading the shared library.\n"; - using namespace boost::dll; - shared_library lib( - program_location().parent_path() / LIBRARY_NAME, - load_mode::append_decorations); - std::cout << "\nLoaded " << LIBRARY_NAME << ".\n"; + boost::dll::shared_library lib( + boost::dll::program_location().parent_path() / LIBRARY_NAME, + boost::dll::load_mode::rtld_now); boost::openmethod::initialize(trace::from_env()); std::cout << "cow meets wolf -> " diff --git a/doc/modules/ROOT/examples/shared_libs/indirect_main.cpp b/doc/modules/ROOT/examples/shared_libs/indirect_main.cpp index ca513fbe..555435e0 100644 --- a/doc/modules/ROOT/examples/shared_libs/indirect_main.cpp +++ b/doc/modules/ROOT/examples/shared_libs/indirect_main.cpp @@ -3,7 +3,11 @@ // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -#define LIBRARY_NAME "indirect_shared" +#ifdef _MSC_VER +#define LIBRARY_NAME "indirect_shared.dll" +#else +#define LIBRARY_NAME "libindirect_shared.so" +#endif // tag::content[] // indirect_main.cpp @@ -45,10 +49,9 @@ auto main() -> int { std::cout << "\nAfter loading the shared library.\n"; - using namespace boost::dll; - shared_library lib( - program_location().parent_path() / LIBRARY_NAME, - load_mode::append_decorations); + boost::dll::shared_library lib( + boost::dll::program_location().parent_path() / LIBRARY_NAME, + boost::dll::load_mode::rtld_now); boost::openmethod::initialize(); diff --git a/test/dynamic_loading/CMakeLists.txt b/test/dynamic_loading/CMakeLists.txt index f3fc65be..f89b98ae 100644 --- a/test/dynamic_loading/CMakeLists.txt +++ b/test/dynamic_loading/CMakeLists.txt @@ -67,9 +67,9 @@ foreach( set_target_properties( ${target} PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" - ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/stage/bin/$" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/stage/bin/$" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/stage/bin/$" ENABLE_EXPORTS ON CXX_VISIBILITY_PRESET default VISIBILITY_INLINES_HIDDEN OFF) @@ -87,8 +87,7 @@ set_tests_properties( add_test( NAME boost_openmethod-test_dynamic_loading - COMMAND boost_openmethod-test_dynamic_loading - WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") + COMMAND boost_openmethod-test_dynamic_loading) set_tests_properties( boost_openmethod-test_dynamic_loading PROPERTIES FIXTURES_REQUIRED boost_openmethod-test_dynamic_loading-fixture) diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index 3280d982..3f873d18 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -65,9 +65,14 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { // Use rtld_global for registry and method so their symbols (fn, policy // statics) are visible globally and win over any locally-instantiated // copies when the overrider library is subsequently loaded. +#ifdef _WIN32 + constexpr auto global_mode = dll::load_mode::append_decorations; +#else + constexpr auto global_mode = + dll::load_mode::append_decorations /*| dll::load_mode::rtld_global*/; +#endif dll::shared_library method_lib( - lib_dir / "boost_openmethod-dl_test_method", - dll::load_mode::append_decorations); + lib_dir / "boost_openmethod-dl_test_method", global_mode); auto& method_get_ids = method_lib.get_alias("get_ids"); auto& method_speak = method_lib.get_alias)>("call_speak"); From 41d680219ec9f66af6869570e92ca15b423bb0a3 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 22 Mar 2026 11:36:24 -0400 Subject: [PATCH 26/74] Revert "with b2" This reverts commit f2cf2bf6fe8c249eca855db4734bbfcb452a47ba. --- test/Jamfile | 40 ---------------------------------------- 1 file changed, 40 deletions(-) diff --git a/test/Jamfile b/test/Jamfile index 87124491..a4becf8c 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -46,46 +46,6 @@ for local src in [ glob compile_fail_*.cpp ] compile-fail $(src) ; } -# Dynamic loading test -# Requires Boost.DLL; builds three shared libraries and a test executable -# that loads them at runtime. -lib dl_test_registry - : dynamic_loading/registry.cpp - : shared - BOOST_DLL_FORCE_ALIAS_INSTANTIATION - global - /boost/dll//boost_dll - ; - -lib dl_test_method - : dynamic_loading/method.cpp - : shared - BOOST_DLL_FORCE_ALIAS_INSTANTIATION - global - /boost/dll//boost_dll - dl_test_registry - ; - -lib dl_test_overrider - : dynamic_loading/overrider.cpp - : shared - BOOST_DLL_FORCE_ALIAS_INSTANTIATION - global - /boost/dll//boost_dll - dl_test_method - ; - -run dynamic_loading/main.cpp - unit_test_framework - : # args - : dl_test_method dl_test_overrider dl_test_registry # input-files (runtime deps) - : shared - global - /boost/dll//boost_dll - dl_test_registry - : test_dynamic_loading - ; - # quick (for CI) alias quick : test_dispatch ; explicit quick ; From e8d05fabd59a4cb11a5b26be79f16aa4dbc4a927 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 22 Mar 2026 11:36:24 -0400 Subject: [PATCH 27/74] Revert "mingw" This reverts commit f00aace9f8a2aed225ca0d8dd826696a42842273. --- test/dynamic_loading/CMakeLists.txt | 13 --------- test/dynamic_loading/classes.hpp | 9 +++---- test/dynamic_loading/main.cpp | 3 --- test/dynamic_loading/method.cpp | 9 ++----- test/dynamic_loading/method.hpp | 16 +++++------ test/dynamic_loading/overrider.cpp | 9 ++----- test/dynamic_loading/registry.cpp | 8 +++--- test/dynamic_loading/registry.hpp | 41 +++++++++++++---------------- 8 files changed, 36 insertions(+), 72 deletions(-) diff --git a/test/dynamic_loading/CMakeLists.txt b/test/dynamic_loading/CMakeLists.txt index f89b98ae..c785dcad 100644 --- a/test/dynamic_loading/CMakeLists.txt +++ b/test/dynamic_loading/CMakeLists.txt @@ -35,19 +35,6 @@ add_dependencies( boost_openmethod-dl_test_method boost_openmethod-dl_test_overrider) -# On MinGW/GCC+Windows, BOOST_DLL_AUTO_ALIAS only emits a declaration without -# a definition (relying on --export-all-symbols). Force full alias instantiation -# so the alias pointer variables are actually defined and exported. -if(MINGW) - foreach(target - boost_openmethod-dl_test_registry - boost_openmethod-dl_test_method - boost_openmethod-dl_test_overrider) - target_compile_definitions( - ${target} PRIVATE BOOST_DLL_FORCE_ALIAS_INSTANTIATION) - endforeach() -endif() - # Put all outputs in the same directory so Boost.DLL can locate the shared # libraries relative to the test executable at runtime. # ENABLE_EXPORTS is required so that symbols from each shared library are diff --git a/test/dynamic_loading/classes.hpp b/test/dynamic_loading/classes.hpp index 6875f09d..e4bbf0aa 100644 --- a/test/dynamic_loading/classes.hpp +++ b/test/dynamic_loading/classes.hpp @@ -13,8 +13,7 @@ struct Animal { struct Dog : Animal {}; -#define DEFINE_MAKE_DOG() \ - auto make_dog() { \ - auto p = &typeid(Dog); \ - return boost::openmethod::make_unique_virtual(); \ - } \ No newline at end of file +static auto make_dog() { + auto p = &typeid(Dog); + return boost::openmethod::make_unique_virtual(); +} \ No newline at end of file diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index 3f873d18..86f9df66 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -27,9 +27,6 @@ using method_fn = const void*(); BOOST_OPENMETHOD_CLASSES(Animal, Dog); -DEFINE_GET_IDS() -DEFINE_MAKE_DOG() - bool same_ids(const void** ids1, const void** ids2) { using std::setw; BOOST_TEST_MESSAGE( diff --git a/test/dynamic_loading/method.cpp b/test/dynamic_loading/method.cpp index 919e3c90..11d4e271 100644 --- a/test/dynamic_loading/method.cpp +++ b/test/dynamic_loading/method.cpp @@ -27,12 +27,7 @@ BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { return "?"; } -DEFINE_GET_IDS() -DEFINE_MAKE_DOG() -DEFINE_GET_FN() -DEFINE_CALL_SPEAK() - BOOST_DLL_AUTO_ALIAS(get_ids) BOOST_DLL_AUTO_ALIAS(get_fn) -BOOST_DLL_AUTO_ALIAS(make_dog) -BOOST_DLL_AUTO_ALIAS(call_speak) +BOOST_DLL_AUTO_ALIAS(make_dog); +BOOST_DLL_AUTO_ALIAS(call_speak); diff --git a/test/dynamic_loading/method.hpp b/test/dynamic_loading/method.hpp index 3fc12235..c4a5b1c2 100644 --- a/test/dynamic_loading/method.hpp +++ b/test/dynamic_loading/method.hpp @@ -24,13 +24,11 @@ BOOST_OPENMETHOD( speak, (boost::openmethod::virtual_ptr), const char*, METHOD_DECLSPEC); -#define DEFINE_GET_FN() \ - auto get_fn() { \ - return static_cast(&BOOST_OPENMETHOD_TYPE( \ - speak, (boost::openmethod::virtual_ptr), const char*)::fn);\ - } +inline auto get_fn() { + return static_cast(&BOOST_OPENMETHOD_TYPE( + speak, (boost::openmethod::virtual_ptr), const char*)::fn); +} -#define DEFINE_CALL_SPEAK() \ - auto call_speak(boost::openmethod::virtual_ptr animal) { \ - return speak(animal); \ - } \ No newline at end of file +inline auto call_speak(boost::openmethod::virtual_ptr animal) { + return speak(animal); +} \ No newline at end of file diff --git a/test/dynamic_loading/overrider.cpp b/test/dynamic_loading/overrider.cpp index 866762c9..0d487f3d 100644 --- a/test/dynamic_loading/overrider.cpp +++ b/test/dynamic_loading/overrider.cpp @@ -17,12 +17,7 @@ BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { BOOST_OPENMETHOD_CLASSES(Animal, Dog); -DEFINE_GET_IDS() -DEFINE_MAKE_DOG() -DEFINE_GET_FN() -DEFINE_CALL_SPEAK() - BOOST_DLL_AUTO_ALIAS(get_ids) BOOST_DLL_AUTO_ALIAS(get_fn) -BOOST_DLL_AUTO_ALIAS(make_dog) -BOOST_DLL_AUTO_ALIAS(call_speak) +BOOST_DLL_AUTO_ALIAS(make_dog); +BOOST_DLL_AUTO_ALIAS(call_speak); diff --git a/test/dynamic_loading/registry.cpp b/test/dynamic_loading/registry.cpp index 4b5871e4..e2a1cd6a 100644 --- a/test/dynamic_loading/registry.cpp +++ b/test/dynamic_loading/registry.cpp @@ -22,12 +22,10 @@ static_assert(std::is_same_v); BOOST_OPENMETHOD_CLASSES(Animal, Dog); -DEFINE_GET_IDS() -DEFINE_MAKE_DOG() - -BOOST_DLL_AUTO_ALIAS(get_ids) -BOOST_DLL_AUTO_ALIAS(make_dog) +BOOST_DLL_ALIAS(get_ids, registry_get_ids); void registry_initialize() { boost::openmethod::initialize(trace::from_env()); } + +BOOST_DLL_ALIAS(make_dog, registry_make_dog); diff --git a/test/dynamic_loading/registry.hpp b/test/dynamic_loading/registry.hpp index b8c4d03e..767824be 100644 --- a/test/dynamic_loading/registry.hpp +++ b/test/dynamic_loading/registry.hpp @@ -26,28 +26,23 @@ boost_openmethod_declspec(default_registry&); #include -// Each TU that needs to export get_ids() should define it as a non-static -// function so that BOOST_DLL_AUTO_ALIAS can export it on MinGW. -#define DEFINE_GET_IDS() \ - auto get_ids() -> const void** { \ - using namespace boost::openmethod; \ - namespace mp11 = boost::mp11; \ - \ - constexpr auto n_policies = \ - mp11::mp_size::value; \ - static const void* ids[1 + n_policies + 1] = { \ - default_registry::id()}; \ - std::size_t i = 1; \ - \ - mp11::mp_for_each([&](auto p) { \ - using P = decltype(p); \ - \ - if constexpr (detail::has_id>) { \ - ids[i++] = default_registry::policy

::id(); \ - } \ - }); \ - \ - return ids; \ - } +static auto get_ids() -> const void** { + using namespace boost::openmethod; + namespace mp11 = boost::mp11; + + constexpr auto n_policies = mp11::mp_size::value; + static const void* ids[1 + n_policies + 1] = {default_registry::id()}; + std::size_t i = 1; + + mp11::mp_for_each([&](auto p) { + using P = decltype(p); + + if constexpr (detail::has_id>) { + ids[i++] = default_registry::policy

::id(); + } + }); + + return ids; +} #endif From ad50ae83edfaa12ca5d98ca2536c17ce9917de32 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 28 Mar 2026 10:53:37 -0400 Subject: [PATCH 28/74] mingw --- .../examples/shared_libs/dynamic_main.cpp | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp b/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp index c116b6aa..6ddd7422 100644 --- a/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp +++ b/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp @@ -5,10 +5,9 @@ #ifndef LIBRARY_NAME #ifdef _MSC_VER -#define LIBRARY_NAME "boost_openmethod-shared.dll" +#define LIBRARY_NAME "boost_openmethod-shared" #else -#define LIBRARY_NAME "libboost_openmethod-shared.so" - +#define LIBRARY_NAME "libboost_openmethod-shared" #endif #endif @@ -78,12 +77,21 @@ int main() { // ... std::cout << "\nAfter loading the shared library.\n"; +#ifdef _WIN32 + constexpr auto global_mode = boost::dll::load_mode::append_decorations; +#else + constexpr auto global_mode = + boost::dll::load_mode::append_decorations /*| boost::dll::load_mode::rtld_global*/; +#endif - boost::dll::shared_library lib( - boost::dll::program_location().parent_path() / LIBRARY_NAME, - boost::dll::load_mode::rtld_now); + auto library_path = boost::dll::program_location().parent_path() / + boost::dll::shared_library::decorate(LIBRARY_NAME); + std::cout << "\nAfter loading " << library_path << ".\n"; + + boost::dll::shared_library lib(library_path, boost::dll::load_mode::rtld_now); boost::openmethod::initialize(trace::from_env()); + std::cout << "cow meets wolf -> " << meet(*std::make_unique(), *std::make_unique()) << "\n"; // run @@ -127,4 +135,4 @@ int main() { } return 0; -} \ No newline at end of file +} From 43912c32c73e1fcf86b75287bafbfebcaa8715f5 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 28 Mar 2026 11:21:48 -0400 Subject: [PATCH 29/74] mingw --- test/dynamic_loading/CMakeLists.txt | 13 +++---------- test/dynamic_loading/main.cpp | 15 ++++++++------- 2 files changed, 11 insertions(+), 17 deletions(-) diff --git a/test/dynamic_loading/CMakeLists.txt b/test/dynamic_loading/CMakeLists.txt index c785dcad..64ca419a 100644 --- a/test/dynamic_loading/CMakeLists.txt +++ b/test/dynamic_loading/CMakeLists.txt @@ -54,9 +54,9 @@ foreach( set_target_properties( ${target} PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/stage/bin/$" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/stage/bin/$" - ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/stage/bin/$" + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" + ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" ENABLE_EXPORTS ON CXX_VISIBILITY_PRESET default VISIBILITY_INLINES_HIDDEN OFF) @@ -71,10 +71,3 @@ add_test( set_tests_properties( boost_openmethod-test_dynamic_loading-build PROPERTIES FIXTURES_SETUP boost_openmethod-test_dynamic_loading-fixture) - -add_test( - NAME boost_openmethod-test_dynamic_loading - COMMAND boost_openmethod-test_dynamic_loading) -set_tests_properties( - boost_openmethod-test_dynamic_loading - PROPERTIES FIXTURES_REQUIRED boost_openmethod-test_dynamic_loading-fixture) diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index 86f9df66..bbd5c354 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -62,14 +62,15 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { // Use rtld_global for registry and method so their symbols (fn, policy // statics) are visible globally and win over any locally-instantiated // copies when the overrider library is subsequently loaded. -#ifdef _WIN32 - constexpr auto global_mode = dll::load_mode::append_decorations; +#ifdef _MSC_VER +#define LIBRARY_PREFIX "" #else - constexpr auto global_mode = - dll::load_mode::append_decorations /*| dll::load_mode::rtld_global*/; +#define LIBRARY_PREFIX "lib" #endif + dll::shared_library method_lib( - lib_dir / "boost_openmethod-dl_test_method", global_mode); + lib_dir / LIBRARY_PREFIX "boost_openmethod-dl_test_method", + dll::load_mode::rtld_global | dll::load_mode::append_decorations); auto& method_get_ids = method_lib.get_alias("get_ids"); auto& method_speak = method_lib.get_alias)>("call_speak"); @@ -91,8 +92,8 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { BOOST_TEST(method_speak(method_dog) == "?"); dll::shared_library overrider_lib( - lib_dir / "boost_openmethod-dl_test_overrider", - dll::load_mode::append_decorations); + lib_dir / LIBRARY_PREFIX "boost_openmethod-dl_test_overrider", + dll::load_mode::rtld_global | dll::load_mode::append_decorations); auto& overrider_get_ids = overrider_lib.get_alias("get_ids"); auto& overrider_speak = overrider_lib.get_alias)>("call_speak"); From 6161186a48b5f782bdc5c84872de92f291e18c55 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 28 Mar 2026 13:15:46 -0400 Subject: [PATCH 30/74] simpler? --- .../examples/shared_libs/dynamic_main.cpp | 23 ++++--------------- test/dynamic_loading/main.cpp | 10 ++------ 2 files changed, 7 insertions(+), 26 deletions(-) diff --git a/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp b/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp index 6ddd7422..bb24ac49 100644 --- a/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp +++ b/doc/modules/ROOT/examples/shared_libs/dynamic_main.cpp @@ -3,13 +3,7 @@ // accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) -#ifndef LIBRARY_NAME -#ifdef _MSC_VER #define LIBRARY_NAME "boost_openmethod-shared" -#else -#define LIBRARY_NAME "libboost_openmethod-shared" -#endif -#endif // dynamic_main.cpp @@ -76,22 +70,15 @@ int main() { // tag::load[] // ... - std::cout << "\nAfter loading the shared library.\n"; -#ifdef _WIN32 - constexpr auto global_mode = boost::dll::load_mode::append_decorations; -#else - constexpr auto global_mode = - boost::dll::load_mode::append_decorations /*| boost::dll::load_mode::rtld_global*/; -#endif + std::cout << "\nLoading shared object / DLL.\n"; - auto library_path = boost::dll::program_location().parent_path() / - boost::dll::shared_library::decorate(LIBRARY_NAME); - std::cout << "\nAfter loading " << library_path << ".\n"; + boost::dll::shared_library lib( + LIBRARY_NAME, + boost::dll::load_mode::rtld_global | + boost::dll::load_mode::append_decorations); - boost::dll::shared_library lib(library_path, boost::dll::load_mode::rtld_now); boost::openmethod::initialize(trace::from_env()); - std::cout << "cow meets wolf -> " << meet(*std::make_unique(), *std::make_unique()) << "\n"; // run diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index bbd5c354..ae20ac9b 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -62,14 +62,8 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { // Use rtld_global for registry and method so their symbols (fn, policy // statics) are visible globally and win over any locally-instantiated // copies when the overrider library is subsequently loaded. -#ifdef _MSC_VER -#define LIBRARY_PREFIX "" -#else -#define LIBRARY_PREFIX "lib" -#endif - dll::shared_library method_lib( - lib_dir / LIBRARY_PREFIX "boost_openmethod-dl_test_method", + lib_dir / "boost_openmethod-dl_test_method", dll::load_mode::rtld_global | dll::load_mode::append_decorations); auto& method_get_ids = method_lib.get_alias("get_ids"); auto& method_speak = @@ -92,7 +86,7 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { BOOST_TEST(method_speak(method_dog) == "?"); dll::shared_library overrider_lib( - lib_dir / LIBRARY_PREFIX "boost_openmethod-dl_test_overrider", + lib_dir / "boost_openmethod-dl_test_overrider", dll::load_mode::rtld_global | dll::load_mode::append_decorations); auto& overrider_get_ids = overrider_lib.get_alias("get_ids"); auto& overrider_speak = From 7d3859d1d7fe0f59fbab10760836d501e08b02a8 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 28 Mar 2026 13:24:23 -0400 Subject: [PATCH 31/74] get_attributes -> get_declspec --- include/boost/openmethod/default_registry.hpp | 2 -- include/boost/openmethod/preamble.hpp | 15 ++++++--------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/include/boost/openmethod/default_registry.hpp b/include/boost/openmethod/default_registry.hpp index 2e5aa468..ff044ab1 100644 --- a/include/boost/openmethod/default_registry.hpp +++ b/include/boost/openmethod/default_registry.hpp @@ -15,8 +15,6 @@ namespace boost::openmethod { -struct default_registry_attributes {}; - //! Default registry. //! //! `default_registry` is a predefined @ref registry, and the default value of diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index 7367c6c6..5e8aa5cd 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -741,8 +741,8 @@ struct TypeHashFn { //! blueprint. //! @return A pair containing the minimum and maximum hash values. template - static auto - initialize(const Context& ctx) -> std::pair; + static auto initialize(const Context& ctx) + -> std::pair; //! Hash a `type_id`. //! @@ -970,7 +970,7 @@ namespace detail { template \ struct BOOST_PP_CAT(static_, ID)< \ Registry, Type, Guide, \ - std::enable_if_t, dllexport>>> { \ + std::enable_if_t, dllexport>>> { \ using declspec = dllexport; \ static BOOST_SYMBOL_EXPORT Type ID __VA_ARGS__; \ }; \ @@ -978,13 +978,12 @@ namespace detail { template \ Type BOOST_PP_CAT(static_, ID)< \ Registry, Type, Guide, \ - std::enable_if_t, dllexport>>>:: \ - ID; \ + std::enable_if_t, dllexport>>>::ID; \ \ template \ struct BOOST_PP_CAT(static_, ID)< \ Registry, Type, Guide, \ - std::enable_if_t, dllimport>>> { \ + std::enable_if_t, dllimport>>> { \ using declspec = dllimport; \ static BOOST_SYMBOL_IMPORT Type ID; \ } @@ -994,8 +993,7 @@ namespace detail { #endif template -using get_attributes = - decltype(boost_openmethod_declspec(std::declval())); +using get_declspec = decltype(boost_openmethod_declspec(std::declval())); BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(st); @@ -1237,7 +1235,6 @@ auto final_error::write(Stream& os) const { Registry::rtti::type_name(dynamic_type, os); } -struct default_registry_attributes; struct default_registry; } // namespace boost::openmethod From 422266c09f3f175a0da189469557820a480f043e Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 28 Mar 2026 14:12:59 -0400 Subject: [PATCH 32/74] b2 --- test/Jamfile | 2 + test/dynamic_loading/Jamfile | 92 ++++++++++++++++++++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 test/dynamic_loading/Jamfile diff --git a/test/Jamfile b/test/Jamfile index a4becf8c..dccc3a72 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -46,6 +46,8 @@ for local src in [ glob compile_fail_*.cpp ] compile-fail $(src) ; } +build-project dynamic_loading ; + # quick (for CI) alias quick : test_dispatch ; explicit quick ; diff --git a/test/dynamic_loading/Jamfile b/test/dynamic_loading/Jamfile new file mode 100644 index 00000000..e23cbcca --- /dev/null +++ b/test/dynamic_loading/Jamfile @@ -0,0 +1,92 @@ +# Boost.OpenMethod Library – dynamic_loading test Jamfile +# +# Copyright 2025 Jean-Louis Leroy +# +# Distributed under the Boost Software License, Version 1.0. +# See accompanying file LICENSE_1_0.txt or copy at +# http://www.boost.org/LICENSE_1_0.txt + +# Reproduce the CMake dynamic-loading test in Boost.Build. +# +# Build graph (all shared libraries): +# +# lib_registry ──▶ lib_method ──▶ lib_overrider +# │ (runtime-loaded) +# └──────────────────────┐ +# test executable ──linked──▶ lib_registry +# ──dlopen──▶ lib_method, lib_overrider +# +# Key CMake properties reproduced here: +# CXX_VISIBILITY_PRESET = default → global +# VISIBILITY_INLINES_HIDDEN = OFF → (no extra flag needed) +# ENABLE_EXPORTS = ON → -rdynamic (non-Windows) +# RUNTIME/LIBRARY_OUTPUT_DIRECTORY → b2 places all per-toolset/variant +# outputs in the same bin/ subdir + +import testing ; + +# -rdynamic: export exe symbols to dynamically loaded libs so that +# dlopen'd overrider can resolve symbols from the already-loaded method lib. +local RDYNAMIC = + linux:"-rdynamic" + freebsd:"-rdynamic" + android:"-rdynamic" + darwin,gcc:"-dynamic" + darwin,clang:"-rdynamic" + qnxnto,gcc:"-rdynamic" + solaris:"-Bdynamic" ; + +# Default ELF symbol visibility: template static variables used as shared +# registry state must be exported with DEFAULT visibility so the dynamic +# linker deduplicates them across DSOs. MSVC ignores this property. +local VISIBILITY = global ; + +# lib_registry: owns and exports the default registry's static policy state. +# registry.cpp defines EXPORT_REGISTRY and INCLUDED_FROM itself. +lib boost_openmethod-dl_test_registry + : registry.cpp + : shared + $(VISIBILITY) + /boost/dll//boost_dll + ; + +# lib_method: exports the speak() open-method; imports registry from +# lib_registry. method.cpp defines EXPORT_METHOD and INCLUDED_FROM itself. +lib boost_openmethod-dl_test_method + : method.cpp + : shared + $(VISIBILITY) + /boost/dll//boost_dll + boost_openmethod-dl_test_registry + ; + +# lib_overrider: adds the Dog overrider; dynamically loaded at runtime. +# overrider.cpp defines INCLUDED_FROM itself. +lib boost_openmethod-dl_test_overrider + : overrider.cpp + : shared + $(VISIBILITY) + /boost/dll//boost_dll + boost_openmethod-dl_test_method + ; + +# Test executable: +# sources (linked) : main.cpp, lib_registry, unit_test_framework, boost_dll +# input-files (built but not linked, placed in the same bin/ dir): +# lib_method, lib_overrider +# At runtime the test locates shared libs via dll::program_location().parent_path(). +run main.cpp + boost_openmethod-dl_test_registry + ../unit_test_framework + /boost/dll//boost_dll + : # args + : boost_openmethod-dl_test_method # input-files: built, not linked + boost_openmethod-dl_test_overrider + : shared + $(RDYNAMIC) + $(VISIBILITY) + linux:"-ldl" + freebsd:"-ldl" + android:"-ldl" + : dynamic_loading + ; From c6a4a4b15be5c7d5d33fdf46d2e6db94e7c3dcc9 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 28 Mar 2026 16:43:29 -0400 Subject: [PATCH 33/74] ok cmake --- test/dynamic_loading/CMakeLists.txt | 11 ++++------- test/dynamic_loading/Jamfile | 15 +++++++-------- test/dynamic_loading/classes.hpp | 1 - test/dynamic_loading/main.cpp | 29 +++++++++++++++++------------ test/dynamic_loading/method.cpp | 8 ++++---- test/dynamic_loading/overrider.cpp | 8 ++++---- 6 files changed, 36 insertions(+), 36 deletions(-) diff --git a/test/dynamic_loading/CMakeLists.txt b/test/dynamic_loading/CMakeLists.txt index 64ca419a..587e6e2a 100644 --- a/test/dynamic_loading/CMakeLists.txt +++ b/test/dynamic_loading/CMakeLists.txt @@ -63,11 +63,8 @@ foreach( endforeach() add_test( - NAME boost_openmethod-test_dynamic_loading-build - COMMAND - ${CMAKE_COMMAND} --build ${CMAKE_BINARY_DIR} - --target boost_openmethod-test_dynamic_loading - --config $) + NAME boost_openmethod-test_dynamic_loading + COMMAND boost_openmethod-test_dynamic_loading) set_tests_properties( - boost_openmethod-test_dynamic_loading-build - PROPERTIES FIXTURES_SETUP boost_openmethod-test_dynamic_loading-fixture) + boost_openmethod-test_dynamic_loading + PROPERTIES FIXTURES_REQUIRED boost_openmethod-test_dynamic_loading-fixture) diff --git a/test/dynamic_loading/Jamfile b/test/dynamic_loading/Jamfile index e23cbcca..695f2e57 100644 --- a/test/dynamic_loading/Jamfile +++ b/test/dynamic_loading/Jamfile @@ -47,7 +47,7 @@ lib boost_openmethod-dl_test_registry : registry.cpp : shared $(VISIBILITY) - /boost/dll//boost_dll + /boost/dll//boost_dll ; # lib_method: exports the speak() open-method; imports registry from @@ -56,8 +56,8 @@ lib boost_openmethod-dl_test_method : method.cpp : shared $(VISIBILITY) - /boost/dll//boost_dll - boost_openmethod-dl_test_registry + /boost/dll//boost_dll + boost_openmethod-dl_test_registry ; # lib_overrider: adds the Dog overrider; dynamically loaded at runtime. @@ -66,8 +66,8 @@ lib boost_openmethod-dl_test_overrider : overrider.cpp : shared $(VISIBILITY) - /boost/dll//boost_dll - boost_openmethod-dl_test_method + /boost/dll//boost_dll + boost_openmethod-dl_test_method ; # Test executable: @@ -77,11 +77,10 @@ lib boost_openmethod-dl_test_overrider # At runtime the test locates shared libs via dll::program_location().parent_path(). run main.cpp boost_openmethod-dl_test_registry - ../unit_test_framework + /boost/test//boost_unit_test_framework/off /boost/dll//boost_dll : # args - : boost_openmethod-dl_test_method # input-files: built, not linked - boost_openmethod-dl_test_overrider + : : shared $(RDYNAMIC) $(VISIBILITY) diff --git a/test/dynamic_loading/classes.hpp b/test/dynamic_loading/classes.hpp index e4bbf0aa..88a65c10 100644 --- a/test/dynamic_loading/classes.hpp +++ b/test/dynamic_loading/classes.hpp @@ -14,6 +14,5 @@ struct Animal { struct Dog : Animal {}; static auto make_dog() { - auto p = &typeid(Dog); return boost::openmethod::make_unique_virtual(); } \ No newline at end of file diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index ae20ac9b..e99b8d5a 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -56,21 +56,22 @@ bool same_ids(const void** ids1, const void** ids2) { BOOST_AUTO_TEST_CASE(test_shared_state) { namespace dll = boost::dll; - auto lib_dir = dll::program_location().parent_path(); // Load all three shared libraries via Boost.DLL. // Use rtld_global for registry and method so their symbols (fn, policy // statics) are visible globally and win over any locally-instantiated // copies when the overrider library is subsequently loaded. + dll::shared_library method_lib( - lib_dir / "boost_openmethod-dl_test_method", + "boost_openmethod-dl_test_method", dll::load_mode::rtld_global | dll::load_mode::append_decorations); - auto& method_get_ids = method_lib.get_alias("get_ids"); - auto& method_speak = - method_lib.get_alias)>("call_speak"); + auto& method_get_ids = + method_lib.get_alias("method_get_ids"); + auto& method_speak = method_lib.get_alias)>( + "method_call_speak"); auto& method_make_dog = - method_lib.get_alias()>("make_dog"); - auto& method_get_fn = method_lib.get_alias("get_fn"); + method_lib.get_alias()>("method_make_dog"); + auto& method_get_fn = method_lib.get_alias("method_get_fn"); BOOST_TEST(same_ids(get_ids(), method_get_ids())); @@ -86,14 +87,18 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { BOOST_TEST(method_speak(method_dog) == "?"); dll::shared_library overrider_lib( - lib_dir / "boost_openmethod-dl_test_overrider", + "boost_openmethod-dl_test_overrider", dll::load_mode::rtld_global | dll::load_mode::append_decorations); - auto& overrider_get_ids = overrider_lib.get_alias("get_ids"); + auto& overrider_get_ids = + overrider_lib.get_alias("overrider_get_ids"); auto& overrider_speak = - overrider_lib.get_alias)>("call_speak"); + overrider_lib.get_alias)>( + "overrider_call_speak"); auto& overrider_make_dog = - overrider_lib.get_alias()>("make_dog"); - auto& overrider_get_fn = overrider_lib.get_alias("get_fn"); + overrider_lib.get_alias()>( + "overrider_make_dog"); + auto& overrider_get_fn = + overrider_lib.get_alias("overrider_get_fn"); BOOST_TEST(same_ids(get_ids(), overrider_get_ids())); diff --git a/test/dynamic_loading/method.cpp b/test/dynamic_loading/method.cpp index 11d4e271..748ffffd 100644 --- a/test/dynamic_loading/method.cpp +++ b/test/dynamic_loading/method.cpp @@ -27,7 +27,7 @@ BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { return "?"; } -BOOST_DLL_AUTO_ALIAS(get_ids) -BOOST_DLL_AUTO_ALIAS(get_fn) -BOOST_DLL_AUTO_ALIAS(make_dog); -BOOST_DLL_AUTO_ALIAS(call_speak); +BOOST_DLL_ALIAS(get_ids, method_get_ids) +BOOST_DLL_ALIAS(get_fn, method_get_fn) +BOOST_DLL_ALIAS(make_dog, method_make_dog); +BOOST_DLL_ALIAS(call_speak, method_call_speak); diff --git a/test/dynamic_loading/overrider.cpp b/test/dynamic_loading/overrider.cpp index 0d487f3d..b7e258fb 100644 --- a/test/dynamic_loading/overrider.cpp +++ b/test/dynamic_loading/overrider.cpp @@ -17,7 +17,7 @@ BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { BOOST_OPENMETHOD_CLASSES(Animal, Dog); -BOOST_DLL_AUTO_ALIAS(get_ids) -BOOST_DLL_AUTO_ALIAS(get_fn) -BOOST_DLL_AUTO_ALIAS(make_dog); -BOOST_DLL_AUTO_ALIAS(call_speak); +BOOST_DLL_ALIAS(get_ids, overrider_get_ids) +BOOST_DLL_ALIAS(get_fn, overrider_get_fn) +BOOST_DLL_ALIAS(make_dog, overrider_make_dog); +BOOST_DLL_ALIAS(call_speak, overrider_call_speak); From fbdebd8d390a52d4113ea95a896be73dc93202ce Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 28 Mar 2026 16:46:02 -0400 Subject: [PATCH 34/74] also check fn --- test/dynamic_loading/main.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index e99b8d5a..c13f1168 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -61,7 +61,7 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { // Use rtld_global for registry and method so their symbols (fn, policy // statics) are visible globally and win over any locally-instantiated // copies when the overrider library is subsequently loaded. - + //if (!getenv("FOOBAR")) return; dll::shared_library method_lib( "boost_openmethod-dl_test_method", dll::load_mode::rtld_global | dll::load_mode::append_decorations); @@ -74,6 +74,7 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { auto& method_get_fn = method_lib.get_alias("method_get_fn"); BOOST_TEST(same_ids(get_ids(), method_get_ids())); + BOOST_TEST(get_fn() == method_get_fn()); initialize(); @@ -101,6 +102,7 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { overrider_lib.get_alias("overrider_get_fn"); BOOST_TEST(same_ids(get_ids(), overrider_get_ids())); + BOOST_TEST(get_fn() == overrider_get_fn()); initialize(); auto overrider_dog = overrider_make_dog(); From d206a8bea1492819b87d916b8ec29aed3f12f202 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 29 Mar 2026 09:58:49 -0400 Subject: [PATCH 35/74] alleluiah! --- test/dynamic_loading/Jamfile | 24 +++++++++++++++++++++--- test/dynamic_loading/main.cpp | 14 +++++++++----- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/test/dynamic_loading/Jamfile b/test/dynamic_loading/Jamfile index 695f2e57..c396da0d 100644 --- a/test/dynamic_loading/Jamfile +++ b/test/dynamic_loading/Jamfile @@ -20,11 +20,29 @@ # CXX_VISIBILITY_PRESET = default → global # VISIBILITY_INLINES_HIDDEN = OFF → (no extra flag needed) # ENABLE_EXPORTS = ON → -rdynamic (non-Windows) -# RUNTIME/LIBRARY_OUTPUT_DIRECTORY → b2 places all per-toolset/variant -# outputs in the same bin/ subdir import testing ; +# Strip version/toolset decorations from shared lib names so dlopen() can +# find them by their base name at runtime. +rule tag ( name : type ? : property-set ) +{ + if $(type) in SHARED_LIB STATIC_LIB IMPORT_LIB + { + return $(name) ; + } +} + +# Remove the Boost jamroot (which adds version/toolset suffixes) and +# replace it with our own no-op tag so shared lib names are predictable for +# dlopen(). +project + : requirements + -@%boostcpp.tag + -@$(BOOST_JAMROOT_MODULE)%$(BOOST_JAMROOT_MODULE).tag + @$(__name__).tag + ; + # -rdynamic: export exe symbols to dynamically loaded libs so that # dlopen'd overrider can resolve symbols from the already-loaded method lib. local RDYNAMIC = @@ -79,7 +97,7 @@ run main.cpp boost_openmethod-dl_test_registry /boost/test//boost_unit_test_framework/off /boost/dll//boost_dll - : # args + : : : shared $(RDYNAMIC) diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index c13f1168..585235ae 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -61,10 +61,13 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { // Use rtld_global for registry and method so their symbols (fn, policy // statics) are visible globally and win over any locally-instantiated // copies when the overrider library is subsequently loaded. - //if (!getenv("FOOBAR")) return; + + constexpr auto load_mode = dll::load_mode::rtld_global | + dll::load_mode::append_decorations | + dll::load_mode::search_system_folders; + dll::shared_library method_lib( - "boost_openmethod-dl_test_method", - dll::load_mode::rtld_global | dll::load_mode::append_decorations); + "boost_openmethod-dl_test_method", load_mode); auto& method_get_ids = method_lib.get_alias("method_get_ids"); auto& method_speak = method_lib.get_alias)>( @@ -88,8 +91,7 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { BOOST_TEST(method_speak(method_dog) == "?"); dll::shared_library overrider_lib( - "boost_openmethod-dl_test_overrider", - dll::load_mode::rtld_global | dll::load_mode::append_decorations); + "boost_openmethod-dl_test_overrider", load_mode); auto& overrider_get_ids = overrider_lib.get_alias("overrider_get_ids"); auto& overrider_speak = @@ -111,6 +113,8 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { BOOST_TEST(main_dog.vptr() == overrider_dog.vptr()); #ifdef _WIN32 BOOST_TEST(&typeid(*main_dog.get()) != &typeid(*overrider_dog.get())); +#else + BOOST_TEST(&typeid(*main_dog.get()) == &typeid(*overrider_dog.get())); #endif BOOST_TEST(overrider_speak(main_dog) == "woof"); BOOST_TEST(overrider_speak(method_dog) == "woof"); From d9e88cfe00c37631d046e23f6c0f9489218d857b Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 29 Mar 2026 10:54:41 -0400 Subject: [PATCH 36/74] cmake --- test/dynamic_loading/main.cpp | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index 585235ae..73142d9c 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -92,15 +92,15 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { dll::shared_library overrider_lib( "boost_openmethod-dl_test_overrider", load_mode); - auto& overrider_get_ids = + auto overrider_get_ids = overrider_lib.get_alias("overrider_get_ids"); - auto& overrider_speak = + auto overrider_speak = overrider_lib.get_alias)>( "overrider_call_speak"); - auto& overrider_make_dog = + auto overrider_make_dog = overrider_lib.get_alias()>( "overrider_make_dog"); - auto& overrider_get_fn = + auto overrider_get_fn = overrider_lib.get_alias("overrider_get_fn"); BOOST_TEST(same_ids(get_ids(), overrider_get_ids())); @@ -111,11 +111,6 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { main_dog = make_dog(); // because its vptr was invalidated by initialize() method_dog = method_make_dog(); // ditto BOOST_TEST(main_dog.vptr() == overrider_dog.vptr()); -#ifdef _WIN32 - BOOST_TEST(&typeid(*main_dog.get()) != &typeid(*overrider_dog.get())); -#else - BOOST_TEST(&typeid(*main_dog.get()) == &typeid(*overrider_dog.get())); -#endif BOOST_TEST(overrider_speak(main_dog) == "woof"); BOOST_TEST(overrider_speak(method_dog) == "woof"); BOOST_TEST(overrider_speak(overrider_dog) == "woof"); From d60e264e871afe256c6073aecfa78141b94a64e8 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 29 Mar 2026 11:03:14 -0400 Subject: [PATCH 37/74] cmake --- test/dynamic_loading/CMakeLists.txt | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/dynamic_loading/CMakeLists.txt b/test/dynamic_loading/CMakeLists.txt index 587e6e2a..965c05a8 100644 --- a/test/dynamic_loading/CMakeLists.txt +++ b/test/dynamic_loading/CMakeLists.txt @@ -65,6 +65,3 @@ endforeach() add_test( NAME boost_openmethod-test_dynamic_loading COMMAND boost_openmethod-test_dynamic_loading) -set_tests_properties( - boost_openmethod-test_dynamic_loading - PROPERTIES FIXTURES_REQUIRED boost_openmethod-test_dynamic_loading-fixture) From 7d9e3250fbff21c61522d118e9555459dc2cd716 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 29 Mar 2026 11:29:46 -0400 Subject: [PATCH 38/74] b2: add deps --- test/dynamic_loading/Jamfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/dynamic_loading/Jamfile b/test/dynamic_loading/Jamfile index c396da0d..2277bf1e 100644 --- a/test/dynamic_loading/Jamfile +++ b/test/dynamic_loading/Jamfile @@ -105,5 +105,7 @@ run main.cpp linux:"-ldl" freebsd:"-ldl" android:"-ldl" + boost_openmethod-dl_test_method + boost_openmethod-dl_test_overrider : dynamic_loading ; From f0e522eeb1b0aa43f5b49f20b39ba0c32efe7ef7 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 29 Mar 2026 11:38:32 -0400 Subject: [PATCH 39/74] initialize.hpp need to include --- include/boost/openmethod/initialize.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/boost/openmethod/initialize.hpp b/include/boost/openmethod/initialize.hpp index 2d38a63d..066ac284 100644 --- a/include/boost/openmethod/initialize.hpp +++ b/include/boost/openmethod/initialize.hpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include From dca2b90cd06d3f71994b93d50e739295063dc92f Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 29 Mar 2026 11:46:04 -0400 Subject: [PATCH 40/74] cml --- test/dynamic_loading/CMakeLists.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/dynamic_loading/CMakeLists.txt b/test/dynamic_loading/CMakeLists.txt index 965c05a8..932284c9 100644 --- a/test/dynamic_loading/CMakeLists.txt +++ b/test/dynamic_loading/CMakeLists.txt @@ -3,6 +3,11 @@ # See accompanying file LICENSE_1_0.txt # or copy at http://www.boost.org/LICENSE_1_0.txt) +if (NOT BUILD_SHARED_LIBS) + message(STATUS "Skipping dynamic_loading test: requires BUILD_SHARED_LIBS=ON") + return() +endif() + # lib_registry: exports the default registry's static policy state add_library(boost_openmethod-dl_test_registry SHARED registry.cpp) target_link_libraries( @@ -62,6 +67,10 @@ foreach( VISIBILITY_INLINES_HIDDEN OFF) endforeach() +if (TARGET tests) + add_dependencies(tests boost_openmethod-test_dynamic_loading) +endif() + add_test( NAME boost_openmethod-test_dynamic_loading COMMAND boost_openmethod-test_dynamic_loading) From b89a53087662c5e86010e5290a553a33d0353ae3 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 29 Mar 2026 12:02:15 -0400 Subject: [PATCH 41/74] cml --- test/dynamic_loading/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/dynamic_loading/CMakeLists.txt b/test/dynamic_loading/CMakeLists.txt index 932284c9..d99d8373 100644 --- a/test/dynamic_loading/CMakeLists.txt +++ b/test/dynamic_loading/CMakeLists.txt @@ -32,7 +32,9 @@ add_executable( boost_openmethod-test_dynamic_loading main.cpp) target_link_libraries( boost_openmethod-test_dynamic_loading - PUBLIC Boost::openmethod boost_openmethod-dl_test_registry + PUBLIC + Boost::openmethod boost_openmethod-dl_test_registry + Boost::openmethod boost_openmethod-dl_test_method PRIVATE Boost::openmethod Boost::unit_test_framework Boost::dll) add_dependencies( boost_openmethod-test_dynamic_loading From ea2b7bfdfa47f50ae709e20b786628eae2862379 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 29 Mar 2026 12:05:02 -0400 Subject: [PATCH 42/74] Jamfile: move tag stuff one level --- test/Jamfile | 14 ++++++++++++++ test/dynamic_loading/Jamfile | 20 -------------------- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/test/Jamfile b/test/Jamfile index dccc3a72..a746a438 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -11,6 +11,16 @@ import-search /boost/config/checks ; import testing ; import config : requires ; +# Strip version/toolset decorations from shared lib names so dlopen() can +# find them by their base name at runtime (used by the dynamic_loading test). +rule tag ( name : type ? : property-set ) +{ + if $(type) in SHARED_LIB STATIC_LIB IMPORT_LIB + { + return $(name) ; + } +} + project : requirements @@ -26,6 +36,10 @@ project clang:on gcc:on msvc:on + + -@%boostcpp.tag + -@$(BOOST_JAMROOT_MODULE)%$(BOOST_JAMROOT_MODULE).tag + @$(__name__).tag ; alias unit_test_framework diff --git a/test/dynamic_loading/Jamfile b/test/dynamic_loading/Jamfile index 2277bf1e..e6ba1d44 100644 --- a/test/dynamic_loading/Jamfile +++ b/test/dynamic_loading/Jamfile @@ -23,26 +23,6 @@ import testing ; -# Strip version/toolset decorations from shared lib names so dlopen() can -# find them by their base name at runtime. -rule tag ( name : type ? : property-set ) -{ - if $(type) in SHARED_LIB STATIC_LIB IMPORT_LIB - { - return $(name) ; - } -} - -# Remove the Boost jamroot (which adds version/toolset suffixes) and -# replace it with our own no-op tag so shared lib names are predictable for -# dlopen(). -project - : requirements - -@%boostcpp.tag - -@$(BOOST_JAMROOT_MODULE)%$(BOOST_JAMROOT_MODULE).tag - @$(__name__).tag - ; - # -rdynamic: export exe symbols to dynamically loaded libs so that # dlopen'd overrider can resolve symbols from the already-loaded method lib. local RDYNAMIC = From 2e20cb63a184f879f02ccda4a67306e88a7eb495 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 29 Mar 2026 12:44:21 -0400 Subject: [PATCH 43/74] cml on win --- test/CMakeLists.txt | 12 ++++++++++++ test/dynamic_loading/CMakeLists.txt | 7 +++++++ 2 files changed, 19 insertions(+) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 18d77aec..efdd187a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -61,12 +61,24 @@ foreach(test_cpp ${test_cpp_files}) add_executable(${test_target} EXCLUDE_FROM_ALL ${test_cpp}) target_link_libraries(${test_target} PRIVATE Boost::openmethod Boost::unit_test_framework) add_test(NAME ${test_target} COMMAND ${test_target}) + if (WIN32) + set_property( + TEST ${test_target} + PROPERTY ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() add_dependencies(tests ${test_target}) endforeach() add_executable(boost_openmethod-test_mix_release_debug EXCLUDE_FROM_ALL mix_release_debug/main.cpp mix_release_debug/lib.cpp) target_link_libraries(boost_openmethod-test_mix_release_debug PRIVATE Boost::openmethod Boost::unit_test_framework) add_test(NAME boost_openmethod-test_mix_release_debug COMMAND boost_openmethod-test_mix_release_debug) +if (WIN32) + set_property( + TEST boost_openmethod-test_mix_release_debug + PROPERTY ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") +endif() add_dependencies(tests boost_openmethod-test_mix_release_debug) function(openmethod_compile_fail_test testname fail_regex) diff --git a/test/dynamic_loading/CMakeLists.txt b/test/dynamic_loading/CMakeLists.txt index d99d8373..379e76a7 100644 --- a/test/dynamic_loading/CMakeLists.txt +++ b/test/dynamic_loading/CMakeLists.txt @@ -76,3 +76,10 @@ endif() add_test( NAME boost_openmethod-test_dynamic_loading COMMAND boost_openmethod-test_dynamic_loading) + +if (WIN32) + set_property( + TEST boost_openmethod-test_dynamic_loading + PROPERTY ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") +endif() From d30a1d17168d6a405a41c907c835db5734e43cb2 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 29 Mar 2026 12:44:28 -0400 Subject: [PATCH 44/74] cml on win --- test/CMakeLists.txt | 26 ++++++++++++-------------- test/dynamic_loading/CMakeLists.txt | 11 +---------- 2 files changed, 13 insertions(+), 24 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index efdd187a..ff0379ab 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -53,6 +53,16 @@ if (BOOST_OPENMETHOD_WARNINGS_AS_ERRORS) endif() endif() +function(boost_openmethod_add_test name) + add_test(NAME ${name} COMMAND ${name}) + if (WIN32) + set_property( + TEST ${name} + PROPERTY ENVIRONMENT_MODIFICATION + "PATH=path_list_prepend:$") + endif() +endfunction() + file(GLOB test_cpp_files "test_*.cpp") foreach(test_cpp ${test_cpp_files}) @@ -60,25 +70,13 @@ foreach(test_cpp ${test_cpp_files}) set(test_target "boost_openmethod-${test}") add_executable(${test_target} EXCLUDE_FROM_ALL ${test_cpp}) target_link_libraries(${test_target} PRIVATE Boost::openmethod Boost::unit_test_framework) - add_test(NAME ${test_target} COMMAND ${test_target}) - if (WIN32) - set_property( - TEST ${test_target} - PROPERTY ENVIRONMENT_MODIFICATION - "PATH=path_list_prepend:$") - endif() + boost_openmethod_add_test(${test_target}) add_dependencies(tests ${test_target}) endforeach() add_executable(boost_openmethod-test_mix_release_debug EXCLUDE_FROM_ALL mix_release_debug/main.cpp mix_release_debug/lib.cpp) target_link_libraries(boost_openmethod-test_mix_release_debug PRIVATE Boost::openmethod Boost::unit_test_framework) -add_test(NAME boost_openmethod-test_mix_release_debug COMMAND boost_openmethod-test_mix_release_debug) -if (WIN32) - set_property( - TEST boost_openmethod-test_mix_release_debug - PROPERTY ENVIRONMENT_MODIFICATION - "PATH=path_list_prepend:$") -endif() +boost_openmethod_add_test(boost_openmethod-test_mix_release_debug) add_dependencies(tests boost_openmethod-test_mix_release_debug) function(openmethod_compile_fail_test testname fail_regex) diff --git a/test/dynamic_loading/CMakeLists.txt b/test/dynamic_loading/CMakeLists.txt index 379e76a7..d8ef7dc9 100644 --- a/test/dynamic_loading/CMakeLists.txt +++ b/test/dynamic_loading/CMakeLists.txt @@ -73,13 +73,4 @@ if (TARGET tests) add_dependencies(tests boost_openmethod-test_dynamic_loading) endif() -add_test( - NAME boost_openmethod-test_dynamic_loading - COMMAND boost_openmethod-test_dynamic_loading) - -if (WIN32) - set_property( - TEST boost_openmethod-test_dynamic_loading - PROPERTY ENVIRONMENT_MODIFICATION - "PATH=path_list_prepend:$") -endif() +boost_openmethod_add_test(boost_openmethod-test_dynamic_loading) From 441e635f7df5b4ab8d37e7b9c076d6346ec9b309 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 29 Mar 2026 12:46:36 -0400 Subject: [PATCH 45/74] kill warning --- test/dynamic_loading/main.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index 73142d9c..a5d37768 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -20,8 +20,6 @@ using namespace boost::openmethod; namespace mp11 = boost::mp11; -constexpr auto n_policies = mp11::mp_size::value; - using policy_ids_fn = const void**(); using method_fn = const void*(); From 4c7e4cfc8d09b7d291274513c99be3449746e42b Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 29 Mar 2026 14:45:52 -0400 Subject: [PATCH 46/74] b2: create symlinks/copies instead --- test/Jamfile | 14 -------- test/dynamic_loading/Jamfile | 64 ++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 14 deletions(-) diff --git a/test/Jamfile b/test/Jamfile index a746a438..dccc3a72 100644 --- a/test/Jamfile +++ b/test/Jamfile @@ -11,16 +11,6 @@ import-search /boost/config/checks ; import testing ; import config : requires ; -# Strip version/toolset decorations from shared lib names so dlopen() can -# find them by their base name at runtime (used by the dynamic_loading test). -rule tag ( name : type ? : property-set ) -{ - if $(type) in SHARED_LIB STATIC_LIB IMPORT_LIB - { - return $(name) ; - } -} - project : requirements @@ -36,10 +26,6 @@ project clang:on gcc:on msvc:on - - -@%boostcpp.tag - -@$(BOOST_JAMROOT_MODULE)%$(BOOST_JAMROOT_MODULE).tag - @$(__name__).tag ; alias unit_test_framework diff --git a/test/dynamic_loading/Jamfile b/test/dynamic_loading/Jamfile index e6ba1d44..bd5c9cea 100644 --- a/test/dynamic_loading/Jamfile +++ b/test/dynamic_loading/Jamfile @@ -22,6 +22,40 @@ # ENABLE_EXPORTS = ON → -rdynamic (non-Windows) import testing ; +import os ; + +local os-name = [ os.name ] ; + +# After building each test shared library, create a plain-name alias so that +# Boost.DLL's append_decorations load mode can find it at runtime. +# +# On Linux (and other ELF platforms), boostcpp.tag appends .X.Y.Z to shared +# lib filenames; dlopen with append_decorations looks for the unversioned .so +# name, so we create a relative symlink. +# On macOS (Darwin), no version suffix is appended and the file is already +# named lib.dylib — no alias needed. +# On Windows, the default boostcpp tag adds toolset/variant decorations to the +# DLL name; we copy to the plain .dll name. +if $(os-name) = NT +{ + # Strip everything from the first '-' or '.' after the base to get plain + # name, then copy the DLL alongside the decorated one. + actions plain-lib-alias + { + copy /Y "$(>[1])" "$(>[1]:D)\$(<:D=)" + } +} +else if $(os-name) != MACOSX +{ + # Create a plain .so symlink in the same directory as the versioned lib. + # $(>[1]:D) = directory of versioned lib + # $(>[1]:D=) = filename of versioned lib (e.g. libfoo.so.1.91.0) + # $(<:D=) = plain filename (e.g. libfoo.so) + actions plain-lib-alias + { + ln -sf "$(>[1]:D=)" "$(>[1]:D)/$(<:D=)" + } +} # -rdynamic: export exe symbols to dynamically loaded libs so that # dlopen'd overrider can resolve symbols from the already-loaded method lib. @@ -68,6 +102,35 @@ lib boost_openmethod-dl_test_overrider boost_openmethod-dl_test_method ; +# Plain-name aliases: after building each shared lib, create a symlink/copy +# with the undecorated name so Boost.DLL's append_decorations can find it. +local plain-aliases ; +for local lib in + boost_openmethod-dl_test_registry + boost_openmethod-dl_test_method + boost_openmethod-dl_test_overrider +{ + local plain-name ; + if $(os-name) = NT + { + plain-name = $(lib).dll ; + } + else if $(os-name) != MACOSX + { + plain-name = lib$(lib).so ; + } + if $(plain-name) + { + make $(plain-name) + : $(lib) + : @plain-lib-alias + : shared + global + ; + plain-aliases += $(plain-name) ; + } +} + # Test executable: # sources (linked) : main.cpp, lib_registry, unit_test_framework, boost_dll # input-files (built but not linked, placed in the same bin/ dir): @@ -87,5 +150,6 @@ run main.cpp android:"-ldl" boost_openmethod-dl_test_method boost_openmethod-dl_test_overrider + $(plain-aliases) : dynamic_loading ; From df6f341d27df46b6e91d5a4ab721b02c6f2719e5 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 29 Mar 2026 14:57:37 -0400 Subject: [PATCH 47/74] disable error=unused-function --- test/dynamic_loading/method.cpp | 2 ++ test/dynamic_loading/overrider.cpp | 2 ++ test/dynamic_loading/registry.cpp | 2 ++ 3 files changed, 6 insertions(+) diff --git a/test/dynamic_loading/method.cpp b/test/dynamic_loading/method.cpp index 748ffffd..e71a6c88 100644 --- a/test/dynamic_loading/method.cpp +++ b/test/dynamic_loading/method.cpp @@ -3,6 +3,8 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) +#pragma GCC diagnostic ignored "-Wunused-function" + #define EXPORT_METHOD #define INCLUDED_FROM "lib_method.cpp" diff --git a/test/dynamic_loading/overrider.cpp b/test/dynamic_loading/overrider.cpp index b7e258fb..e4f45312 100644 --- a/test/dynamic_loading/overrider.cpp +++ b/test/dynamic_loading/overrider.cpp @@ -3,6 +3,8 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) +#pragma GCC diagnostic ignored "-Wunused-function" + #define INCLUDED_FROM "lib_overrider.cpp" #include "method.hpp" diff --git a/test/dynamic_loading/registry.cpp b/test/dynamic_loading/registry.cpp index e2a1cd6a..6e830187 100644 --- a/test/dynamic_loading/registry.cpp +++ b/test/dynamic_loading/registry.cpp @@ -3,6 +3,8 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) +#pragma GCC diagnostic ignored "-Wunused-function" + #define EXPORT_REGISTRY #define INCLUDED_FROM "lib_registry.cpp" From fa71b8ebbed06e86a433a5d8d72b0704060012d2 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 29 Mar 2026 15:07:00 -0400 Subject: [PATCH 48/74] add guards --- test/dynamic_loading/method.cpp | 2 ++ test/dynamic_loading/overrider.cpp | 2 ++ test/dynamic_loading/registry.cpp | 2 ++ 3 files changed, 6 insertions(+) diff --git a/test/dynamic_loading/method.cpp b/test/dynamic_loading/method.cpp index e71a6c88..5699af83 100644 --- a/test/dynamic_loading/method.cpp +++ b/test/dynamic_loading/method.cpp @@ -3,7 +3,9 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) +#if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic ignored "-Wunused-function" +#endif #define EXPORT_METHOD #define INCLUDED_FROM "lib_method.cpp" diff --git a/test/dynamic_loading/overrider.cpp b/test/dynamic_loading/overrider.cpp index e4f45312..0b442c8a 100644 --- a/test/dynamic_loading/overrider.cpp +++ b/test/dynamic_loading/overrider.cpp @@ -3,7 +3,9 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) +#if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic ignored "-Wunused-function" +#endif #define INCLUDED_FROM "lib_overrider.cpp" diff --git a/test/dynamic_loading/registry.cpp b/test/dynamic_loading/registry.cpp index 6e830187..90fe73e1 100644 --- a/test/dynamic_loading/registry.cpp +++ b/test/dynamic_loading/registry.cpp @@ -3,7 +3,9 @@ // See accompanying file LICENSE_1_0.txt // or copy at http://www.boost.org/LICENSE_1_0.txt) +#if defined(__GNUC__) || defined(__clang__) #pragma GCC diagnostic ignored "-Wunused-function" +#endif #define EXPORT_REGISTRY #define INCLUDED_FROM "lib_registry.cpp" From 504f9db2f374abbb7f06a7e637c40a32a2652bd0 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 29 Mar 2026 15:11:23 -0400 Subject: [PATCH 49/74] remove INCLUDED_FROM --- test/dynamic_loading/method.cpp | 1 - test/dynamic_loading/method.hpp | 2 -- test/dynamic_loading/overrider.cpp | 2 -- test/dynamic_loading/registry.cpp | 1 - test/dynamic_loading/registry.hpp | 2 -- 5 files changed, 8 deletions(-) diff --git a/test/dynamic_loading/method.cpp b/test/dynamic_loading/method.cpp index 5699af83..3e34eb66 100644 --- a/test/dynamic_loading/method.cpp +++ b/test/dynamic_loading/method.cpp @@ -8,7 +8,6 @@ #endif #define EXPORT_METHOD -#define INCLUDED_FROM "lib_method.cpp" #include "registry.hpp" #include "method.hpp" diff --git a/test/dynamic_loading/method.hpp b/test/dynamic_loading/method.hpp index c4a5b1c2..e01257a7 100644 --- a/test/dynamic_loading/method.hpp +++ b/test/dynamic_loading/method.hpp @@ -10,10 +10,8 @@ #if defined(_WIN32) #if defined(EXPORT_METHOD) -#pragma message(INCLUDED_FROM ": exporting method") #define METHOD_DECLSPEC boost::openmethod::dllexport #else -#pragma message(INCLUDED_FROM ": importing method") #define METHOD_DECLSPEC boost::openmethod::dllimport #endif #else diff --git a/test/dynamic_loading/overrider.cpp b/test/dynamic_loading/overrider.cpp index 0b442c8a..9edafed9 100644 --- a/test/dynamic_loading/overrider.cpp +++ b/test/dynamic_loading/overrider.cpp @@ -7,8 +7,6 @@ #pragma GCC diagnostic ignored "-Wunused-function" #endif -#define INCLUDED_FROM "lib_overrider.cpp" - #include "method.hpp" #include diff --git a/test/dynamic_loading/registry.cpp b/test/dynamic_loading/registry.cpp index 90fe73e1..777db8c8 100644 --- a/test/dynamic_loading/registry.cpp +++ b/test/dynamic_loading/registry.cpp @@ -8,7 +8,6 @@ #endif #define EXPORT_REGISTRY -#define INCLUDED_FROM "lib_registry.cpp" #include "registry.hpp" #include "classes.hpp" diff --git a/test/dynamic_loading/registry.hpp b/test/dynamic_loading/registry.hpp index 767824be..61941147 100644 --- a/test/dynamic_loading/registry.hpp +++ b/test/dynamic_loading/registry.hpp @@ -10,10 +10,8 @@ #if defined(_WIN32) #if defined(EXPORT_REGISTRY) -#pragma message(INCLUDED_FROM ": exporting registry") #define REGISTRY_DECLSPEC boost::openmethod::dllexport #else -#pragma message(INCLUDED_FROM ": importing registry") #define REGISTRY_DECLSPEC boost::openmethod::dllimport #endif From d22988f0333f10905531a74fac6519bbbd287159 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 29 Mar 2026 15:29:25 -0400 Subject: [PATCH 50/74] fast_perfect_hash.hpp: #include --- include/boost/openmethod/policies/fast_perfect_hash.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/include/boost/openmethod/policies/fast_perfect_hash.hpp b/include/boost/openmethod/policies/fast_perfect_hash.hpp index ec164c3b..5cc125e7 100644 --- a/include/boost/openmethod/policies/fast_perfect_hash.hpp +++ b/include/boost/openmethod/policies/fast_perfect_hash.hpp @@ -10,6 +10,7 @@ #include #include +#include #include #ifdef _MSC_VER #pragma warning(push) From a9573821e9e2426a943e663acb2e6d08eb2b7912 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 29 Mar 2026 15:38:41 -0400 Subject: [PATCH 51/74] win+b2 --- test/dynamic_loading/Jamfile | 1 + 1 file changed, 1 insertion(+) diff --git a/test/dynamic_loading/Jamfile b/test/dynamic_loading/Jamfile index bd5c9cea..11efb808 100644 --- a/test/dynamic_loading/Jamfile +++ b/test/dynamic_loading/Jamfile @@ -100,6 +100,7 @@ lib boost_openmethod-dl_test_overrider $(VISIBILITY) /boost/dll//boost_dll boost_openmethod-dl_test_method + windows:boost_openmethod-dl_test_registry ; # Plain-name aliases: after building each shared lib, create a symlink/copy From a9019887968b3c7ea388ded14842568f42c658c0 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 29 Mar 2026 15:54:34 -0400 Subject: [PATCH 52/74] win+b2 --- test/dynamic_loading/Jamfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/dynamic_loading/Jamfile b/test/dynamic_loading/Jamfile index 11efb808..2dbf67e8 100644 --- a/test/dynamic_loading/Jamfile +++ b/test/dynamic_loading/Jamfile @@ -149,6 +149,8 @@ run main.cpp linux:"-ldl" freebsd:"-ldl" android:"-ldl" + windows:boost_openmethod-dl_test_registry + windows:boost_openmethod-dl_test_method boost_openmethod-dl_test_method boost_openmethod-dl_test_overrider $(plain-aliases) From 1b36962eba33eaadc308a546ab6b8d0705cd86b7 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Tue, 14 Apr 2026 18:27:32 -0400 Subject: [PATCH 53/74] claude --- test/dynamic_loading/Jamfile | 7 +++++++ test/dynamic_loading/main.cpp | 10 ++++++++++ 2 files changed, 17 insertions(+) diff --git a/test/dynamic_loading/Jamfile b/test/dynamic_loading/Jamfile index 2dbf67e8..6d51a394 100644 --- a/test/dynamic_loading/Jamfile +++ b/test/dynamic_loading/Jamfile @@ -36,6 +36,13 @@ local os-name = [ os.name ] ; # named lib.dylib — no alias needed. # On Windows, the default boostcpp tag adds toolset/variant decorations to the # DLL name; we copy to the plain .dll name. +# +# NOTE: On Windows the test executable also links against the decorated import +# library (e.g. boost_openmethod-dl_test_method-vc145-mt-gd-x64-1_91.lib), +# which embeds the decorated DLL name in the import table. To avoid loading +# a second, independent copy of the DLL when boost::dll::shared_library opens +# the plain-named copy, main.cpp uses dll::symbol_location_ptr(get_fn()) to +# locate and load the already-in-memory decorated DLL directly. if $(os-name) = NT { # Strip everything from the first '-' or '.' after the base to get plain diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index a5d37768..38470f20 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -64,8 +64,18 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { dll::load_mode::append_decorations | dll::load_mode::search_system_folders; + // On Windows with b2, main.exe links against the decorated DLL + // (e.g. ...-vc145-mt-gd-x64-1_91.dll) via its import library, while + // Boost.DLL with append_decorations finds the plain-named copy — two + // distinct modules in the Windows loader. Load via the address of fn + // (imported via dllimport from the decorated DLL) to get the same module. +#ifdef _WIN32 + dll::shared_library method_lib( + dll::symbol_location_ptr(get_fn()), load_mode); +#else dll::shared_library method_lib( "boost_openmethod-dl_test_method", load_mode); +#endif auto& method_get_ids = method_lib.get_alias("method_get_ids"); auto& method_speak = method_lib.get_alias)>( From 7b337b05fe26ef9612e5515183e74211ec18714a Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 18 Apr 2026 12:06:12 -0400 Subject: [PATCH 54/74] mingw --- test/dynamic_loading/method.cpp | 20 +++++++++++++++++--- test/dynamic_loading/overrider.cpp | 18 ++++++++++++++++-- test/dynamic_loading/registry.cpp | 17 ++++++++++++----- 3 files changed, 45 insertions(+), 10 deletions(-) diff --git a/test/dynamic_loading/method.cpp b/test/dynamic_loading/method.cpp index 3e34eb66..5759577c 100644 --- a/test/dynamic_loading/method.cpp +++ b/test/dynamic_loading/method.cpp @@ -13,10 +13,15 @@ #include "method.hpp" #include -#include #include +#ifdef _WIN32 +#include +#else +#include +#endif + using namespace boost::openmethod; namespace mp11 = boost::mp11; @@ -30,7 +35,16 @@ BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { return "?"; } +#ifdef _WIN32 +extern "C" { + BOOST_SYMBOL_EXPORT const void* method_get_ids = (const void*)&get_ids; + BOOST_SYMBOL_EXPORT const void* method_get_fn = (const void*)&get_fn; + BOOST_SYMBOL_EXPORT const void* method_make_dog = (const void*)&make_dog; + BOOST_SYMBOL_EXPORT const void* method_call_speak = (const void*)&call_speak; +} +#else BOOST_DLL_ALIAS(get_ids, method_get_ids) BOOST_DLL_ALIAS(get_fn, method_get_fn) -BOOST_DLL_ALIAS(make_dog, method_make_dog); -BOOST_DLL_ALIAS(call_speak, method_call_speak); +BOOST_DLL_ALIAS(make_dog, method_make_dog) +BOOST_DLL_ALIAS(call_speak, method_call_speak) +#endif diff --git a/test/dynamic_loading/overrider.cpp b/test/dynamic_loading/overrider.cpp index 9edafed9..f3d7876d 100644 --- a/test/dynamic_loading/overrider.cpp +++ b/test/dynamic_loading/overrider.cpp @@ -8,7 +8,12 @@ #endif #include "method.hpp" + +#ifdef _WIN32 +#include +#else #include +#endif using namespace boost::openmethod; namespace mp11 = boost::mp11; @@ -19,7 +24,16 @@ BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { BOOST_OPENMETHOD_CLASSES(Animal, Dog); +#ifdef _WIN32 +extern "C" { + BOOST_SYMBOL_EXPORT const void* overrider_get_ids = (const void*)&get_ids; + BOOST_SYMBOL_EXPORT const void* overrider_get_fn = (const void*)&get_fn; + BOOST_SYMBOL_EXPORT const void* overrider_make_dog = (const void*)&make_dog; + BOOST_SYMBOL_EXPORT const void* overrider_call_speak = (const void*)&call_speak; +} +#else BOOST_DLL_ALIAS(get_ids, overrider_get_ids) BOOST_DLL_ALIAS(get_fn, overrider_get_fn) -BOOST_DLL_ALIAS(make_dog, overrider_make_dog); -BOOST_DLL_ALIAS(call_speak, overrider_call_speak); +BOOST_DLL_ALIAS(make_dog, overrider_make_dog) +BOOST_DLL_ALIAS(call_speak, overrider_call_speak) +#endif diff --git a/test/dynamic_loading/registry.cpp b/test/dynamic_loading/registry.cpp index 777db8c8..e4b3b868 100644 --- a/test/dynamic_loading/registry.cpp +++ b/test/dynamic_loading/registry.cpp @@ -14,21 +14,28 @@ #include -#include - using namespace boost::openmethod; namespace mp11 = boost::mp11; #ifdef _WIN32 +#include static_assert(std::is_same_v); +#else +#include #endif BOOST_OPENMETHOD_CLASSES(Animal, Dog); -BOOST_DLL_ALIAS(get_ids, registry_get_ids); +#ifdef _WIN32 +extern "C" { + BOOST_SYMBOL_EXPORT const void* registry_get_ids = (const void*)&get_ids; + BOOST_SYMBOL_EXPORT const void* registry_make_dog = (const void*)&make_dog; +} +#else +BOOST_DLL_ALIAS(get_ids, registry_get_ids) +BOOST_DLL_ALIAS(make_dog, registry_make_dog) +#endif void registry_initialize() { boost::openmethod::initialize(trace::from_env()); } - -BOOST_DLL_ALIAS(make_dog, registry_make_dog); From 062ea0750ce0d0f637880b363d072070d65515df Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sat, 18 Apr 2026 12:58:37 -0400 Subject: [PATCH 55/74] clang-win --- .../policies/default_error_handler.hpp | 2 ++ .../openmethod/policies/fast_perfect_hash.hpp | 2 ++ .../openmethod/policies/stderr_output.hpp | 2 ++ .../boost/openmethod/policies/vptr_map.hpp | 2 ++ .../boost/openmethod/policies/vptr_vector.hpp | 2 ++ include/boost/openmethod/preamble.hpp | 19 +++++++++++++++++++ test/dynamic_loading/main.cpp | 6 +++++- test/dynamic_loading/method.hpp | 2 ++ 8 files changed, 36 insertions(+), 1 deletion(-) diff --git a/include/boost/openmethod/policies/default_error_handler.hpp b/include/boost/openmethod/policies/default_error_handler.hpp index 3f6fcb49..f0153737 100644 --- a/include/boost/openmethod/policies/default_error_handler.hpp +++ b/include/boost/openmethod/policies/default_error_handler.hpp @@ -115,9 +115,11 @@ struct default_error_handler : error_handler { return prev ? prev : default_handler; } + BOOST_OPENMETHOD_DETAIL_SUPPRESS_DLLIMPORT_UNDEF_VAR static auto id() -> const void* { return &static_::handler; } + BOOST_OPENMETHOD_DETAIL_RESTORE_DLLIMPORT_UNDEF_VAR //! The default error handler function. //! diff --git a/include/boost/openmethod/policies/fast_perfect_hash.hpp b/include/boost/openmethod/policies/fast_perfect_hash.hpp index 5cc125e7..f536aea6 100644 --- a/include/boost/openmethod/policies/fast_perfect_hash.hpp +++ b/include/boost/openmethod/policies/fast_perfect_hash.hpp @@ -170,9 +170,11 @@ struct fast_perfect_hash : type_hash { } } + BOOST_OPENMETHOD_DETAIL_SUPPRESS_DLLIMPORT_UNDEF_VAR static auto id() -> const void* { return &static_::hash_fn; } + BOOST_OPENMETHOD_DETAIL_RESTORE_DLLIMPORT_UNDEF_VAR }; }; diff --git a/include/boost/openmethod/policies/stderr_output.hpp b/include/boost/openmethod/policies/stderr_output.hpp index 1c95c7cf..18a67e7e 100644 --- a/include/boost/openmethod/policies/stderr_output.hpp +++ b/include/boost/openmethod/policies/stderr_output.hpp @@ -32,9 +32,11 @@ struct stderr_output : output { //! A @ref LightweightOuputStream. // static detail::ostderr os; // now inherited from static_os + BOOST_OPENMETHOD_DETAIL_SUPPRESS_DLLIMPORT_UNDEF_VAR static auto id() -> const void* { return &fn::os; } + BOOST_OPENMETHOD_DETAIL_RESTORE_DLLIMPORT_UNDEF_VAR }; }; diff --git a/include/boost/openmethod/policies/vptr_map.hpp b/include/boost/openmethod/policies/vptr_map.hpp index 79547f19..d2decf9e 100644 --- a/include/boost/openmethod/policies/vptr_map.hpp +++ b/include/boost/openmethod/policies/vptr_map.hpp @@ -123,9 +123,11 @@ class vptr_map : public vptr { static_::vptrs.clear(); } + BOOST_OPENMETHOD_DETAIL_SUPPRESS_DLLIMPORT_UNDEF_VAR static auto id() -> const void* { return &static_::vptrs; } + BOOST_OPENMETHOD_DETAIL_RESTORE_DLLIMPORT_UNDEF_VAR }; }; diff --git a/include/boost/openmethod/policies/vptr_vector.hpp b/include/boost/openmethod/policies/vptr_vector.hpp index 28b30d33..33cffd05 100644 --- a/include/boost/openmethod/policies/vptr_vector.hpp +++ b/include/boost/openmethod/policies/vptr_vector.hpp @@ -190,6 +190,7 @@ struct vptr_vector : vptr { } } + BOOST_OPENMETHOD_DETAIL_SUPPRESS_DLLIMPORT_UNDEF_VAR static auto id() -> const void* { if constexpr (Registry::has_indirect_vptr) { return &static_::vptr_vector_indirect_vptrs; @@ -197,6 +198,7 @@ struct vptr_vector : vptr { return &static_::vptr_vector_vptrs; } } + BOOST_OPENMETHOD_DETAIL_RESTORE_DLLIMPORT_UNDEF_VAR }; }; diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index 5e8aa5cd..5530e1b3 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -963,6 +963,21 @@ namespace detail { template \ Type BOOST_PP_CAT(static_, ID)::ID; +// Clang warns at every usage site of a dllimport variable template because the +// definition is deliberately absent (it lives in the exporting DLL). Suppress +// this false positive using _Pragma inside the dllimport struct so the pragma +// is emitted in whatever file expands BOOST_OPENMETHOD_DETAIL_MAKE_STATICS. +#if defined(__clang__) +#define BOOST_OPENMETHOD_DETAIL_SUPPRESS_DLLIMPORT_UNDEF_VAR \ + _Pragma("clang diagnostic push") \ + _Pragma("clang diagnostic ignored \"-Wundefined-var-template\"") +#define BOOST_OPENMETHOD_DETAIL_RESTORE_DLLIMPORT_UNDEF_VAR \ + _Pragma("clang diagnostic pop") +#else +#define BOOST_OPENMETHOD_DETAIL_SUPPRESS_DLLIMPORT_UNDEF_VAR +#define BOOST_OPENMETHOD_DETAIL_RESTORE_DLLIMPORT_UNDEF_VAR +#endif + #if defined(_WIN32) #define BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(ID, ...) \ BOOST_OPENMETHOD_DETAIL_MAKE_STATICS_COMMON(ID, __VA_ARGS__) \ @@ -985,7 +1000,9 @@ namespace detail { Registry, Type, Guide, \ std::enable_if_t, dllimport>>> { \ using declspec = dllimport; \ + BOOST_OPENMETHOD_DETAIL_SUPPRESS_DLLIMPORT_UNDEF_VAR \ static BOOST_SYMBOL_IMPORT Type ID; \ + BOOST_OPENMETHOD_DETAIL_RESTORE_DLLIMPORT_UNDEF_VAR \ } #else #define BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(ID, ...) \ @@ -1114,9 +1131,11 @@ class registry : public detail::registry_base { using registry_type = registry; using declspec = typename static_::declspec; + BOOST_OPENMETHOD_DETAIL_SUPPRESS_DLLIMPORT_UNDEF_VAR static const void* id() { return static_cast(&static_::st.classes); } + BOOST_OPENMETHOD_DETAIL_RESTORE_DLLIMPORT_UNDEF_VAR template struct compiler; diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index 38470f20..a8367839 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -93,7 +93,11 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { auto method_dog = method_make_dog(); BOOST_TEST(main_dog.vptr() == method_dog.vptr()); #ifdef _WIN32 - BOOST_TEST(&typeid(*main_dog.get()) != &typeid(*method_dog.get())); + { + Animal* p1 = main_dog.get(); + Animal* p2 = method_dog.get(); + BOOST_TEST(&typeid(*p1) != &typeid(*p2)); + } #endif BOOST_TEST(method_speak(main_dog) == "?"); BOOST_TEST(method_speak(method_dog) == "?"); diff --git a/test/dynamic_loading/method.hpp b/test/dynamic_loading/method.hpp index e01257a7..607d6db6 100644 --- a/test/dynamic_loading/method.hpp +++ b/test/dynamic_loading/method.hpp @@ -22,10 +22,12 @@ BOOST_OPENMETHOD( speak, (boost::openmethod::virtual_ptr), const char*, METHOD_DECLSPEC); +BOOST_OPENMETHOD_DETAIL_SUPPRESS_DLLIMPORT_UNDEF_VAR inline auto get_fn() { return static_cast(&BOOST_OPENMETHOD_TYPE( speak, (boost::openmethod::virtual_ptr), const char*)::fn); } +BOOST_OPENMETHOD_DETAIL_RESTORE_DLLIMPORT_UNDEF_VAR inline auto call_speak(boost::openmethod::virtual_ptr animal) { return speak(animal); From a65d667a8b8d1e89d9c6356e5c79d3e6c52fa23c Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 19 Apr 2026 11:04:51 -0400 Subject: [PATCH 56/74] clang-win --- include/boost/openmethod/initialize.hpp | 7 ++++++ test/dynamic_loading/main.cpp | 31 +++++++++++++++++++------ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/include/boost/openmethod/initialize.hpp b/include/boost/openmethod/initialize.hpp index 066ac284..88e853c0 100644 --- a/include/boost/openmethod/initialize.hpp +++ b/include/boost/openmethod/initialize.hpp @@ -23,7 +23,14 @@ #include #include +#if defined(__clang__) +# pragma clang diagnostic push +# pragma clang diagnostic ignored "-Wshift-count-overflow" +#endif #include +#if defined(__clang__) +# pragma clang diagnostic pop +#endif #ifdef _MSC_VER #pragma warning(push) diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index a8367839..b8470fa2 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -14,8 +14,10 @@ #include #include #include +#include #include +#include using namespace boost::openmethod; namespace mp11 = boost::mp11; @@ -64,14 +66,25 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { dll::load_mode::append_decorations | dll::load_mode::search_system_folders; - // On Windows with b2, main.exe links against the decorated DLL - // (e.g. ...-vc145-mt-gd-x64-1_91.dll) via its import library, while - // Boost.DLL with append_decorations finds the plain-named copy — two - // distinct modules in the Windows loader. Load via the address of fn - // (imported via dllimport from the decorated DLL) to get the same module. #ifdef _WIN32 - dll::shared_library method_lib( - dll::symbol_location_ptr(get_fn()), load_mode); + // On Windows, DLL names are decorated (e.g. -vc145-mt-gd-x64-1_91.dll). + // Load the method DLL via its already-imported symbol to get exactly the + // same module handle (avoid loading the plain-named copy as a second + // module). Scan the same directory for the overrider by stem fragment. + auto method_path = dll::symbol_location_ptr(get_fn()); + namespace bfs = boost::filesystem; + auto find_dll = [&](const char* stem_fragment) { + for (auto& entry : bfs::directory_iterator(method_path.parent_path())) { + if (entry.path().extension() == ".dll" && + entry.path().stem().string().find(stem_fragment) != + std::string::npos) { + return entry.path(); + } + } + throw std::runtime_error( + std::string("DLL not found: ") + stem_fragment); + }; + dll::shared_library method_lib(method_path, load_mode); #else dll::shared_library method_lib( "boost_openmethod-dl_test_method", load_mode); @@ -102,8 +115,12 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { BOOST_TEST(method_speak(main_dog) == "?"); BOOST_TEST(method_speak(method_dog) == "?"); +#ifdef _WIN32 + dll::shared_library overrider_lib(find_dll("overrider"), load_mode); +#else dll::shared_library overrider_lib( "boost_openmethod-dl_test_overrider", load_mode); +#endif auto overrider_get_ids = overrider_lib.get_alias("overrider_get_ids"); auto overrider_speak = From 6f9824100d9823b1dd41621c3a03e8c7839bdc7d Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 19 Apr 2026 11:40:14 -0400 Subject: [PATCH 57/74] posix, mac: only when visibility = global --- test/dynamic_loading/Jamfile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/dynamic_loading/Jamfile b/test/dynamic_loading/Jamfile index 6d51a394..197839d5 100644 --- a/test/dynamic_loading/Jamfile +++ b/test/dynamic_loading/Jamfile @@ -25,6 +25,7 @@ import testing ; import os ; local os-name = [ os.name ] ; +local visibility-env = [ os.environ B2_VISIBILITY ] ; # After building each test shared library, create a plain-name alias so that # Boost.DLL's append_decorations load mode can find it at runtime. @@ -80,6 +81,9 @@ local RDYNAMIC = # linker deduplicates them across DSOs. MSVC ignores this property. local VISIBILITY = global ; +if ( $(os-name) = NT ) || ( $(visibility-env) = global ) +{ + # lib_registry: owns and exports the default registry's static policy state. # registry.cpp defines EXPORT_REGISTRY and INCLUDED_FROM itself. lib boost_openmethod-dl_test_registry @@ -163,3 +167,5 @@ run main.cpp $(plain-aliases) : dynamic_loading ; + +} # if NT || B2_VISIBILITY=global From e3d1fc333d351dda22358e11b7ab7f8f41d06c22 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 19 Apr 2026 12:28:17 -0400 Subject: [PATCH 58/74] no alias --- doc/modules/ROOT/pages/shared_libraries.adoc | 4 +- test/dynamic_loading/Jamfile | 79 +------------------- test/dynamic_loading/main.cpp | 55 +++++++------- 3 files changed, 35 insertions(+), 103 deletions(-) diff --git a/doc/modules/ROOT/pages/shared_libraries.adoc b/doc/modules/ROOT/pages/shared_libraries.adoc index be4a06d9..92d9e59b 100644 --- a/doc/modules/ROOT/pages/shared_libraries.adoc +++ b/doc/modules/ROOT/pages/shared_libraries.adoc @@ -80,8 +80,8 @@ include::{shared}/dynamic_main.cpp[tag=unload] ## Windows -On Windows, each module (executable or DLL) receives its own copy of global and -static variables by default. Without special measures, the registry state, +On Windows, by default, each module (executable or DLL) receives its own copy of +global variables. Without special measures, the registry state, dispatch tables, and method function pointers are duplicated: when a DLL's static constructors register classes and overriders, they populate the DLL's own copy of the registry, invisible to the main program. diff --git a/test/dynamic_loading/Jamfile b/test/dynamic_loading/Jamfile index 197839d5..4420b8a3 100644 --- a/test/dynamic_loading/Jamfile +++ b/test/dynamic_loading/Jamfile @@ -15,11 +15,6 @@ # └──────────────────────┐ # test executable ──linked──▶ lib_registry # ──dlopen──▶ lib_method, lib_overrider -# -# Key CMake properties reproduced here: -# CXX_VISIBILITY_PRESET = default → global -# VISIBILITY_INLINES_HIDDEN = OFF → (no extra flag needed) -# ENABLE_EXPORTS = ON → -rdynamic (non-Windows) import testing ; import os ; @@ -27,44 +22,6 @@ import os ; local os-name = [ os.name ] ; local visibility-env = [ os.environ B2_VISIBILITY ] ; -# After building each test shared library, create a plain-name alias so that -# Boost.DLL's append_decorations load mode can find it at runtime. -# -# On Linux (and other ELF platforms), boostcpp.tag appends .X.Y.Z to shared -# lib filenames; dlopen with append_decorations looks for the unversioned .so -# name, so we create a relative symlink. -# On macOS (Darwin), no version suffix is appended and the file is already -# named lib.dylib — no alias needed. -# On Windows, the default boostcpp tag adds toolset/variant decorations to the -# DLL name; we copy to the plain .dll name. -# -# NOTE: On Windows the test executable also links against the decorated import -# library (e.g. boost_openmethod-dl_test_method-vc145-mt-gd-x64-1_91.lib), -# which embeds the decorated DLL name in the import table. To avoid loading -# a second, independent copy of the DLL when boost::dll::shared_library opens -# the plain-named copy, main.cpp uses dll::symbol_location_ptr(get_fn()) to -# locate and load the already-in-memory decorated DLL directly. -if $(os-name) = NT -{ - # Strip everything from the first '-' or '.' after the base to get plain - # name, then copy the DLL alongside the decorated one. - actions plain-lib-alias - { - copy /Y "$(>[1])" "$(>[1]:D)\$(<:D=)" - } -} -else if $(os-name) != MACOSX -{ - # Create a plain .so symlink in the same directory as the versioned lib. - # $(>[1]:D) = directory of versioned lib - # $(>[1]:D=) = filename of versioned lib (e.g. libfoo.so.1.91.0) - # $(<:D=) = plain filename (e.g. libfoo.so) - actions plain-lib-alias - { - ln -sf "$(>[1]:D=)" "$(>[1]:D)/$(<:D=)" - } -} - # -rdynamic: export exe symbols to dynamically loaded libs so that # dlopen'd overrider can resolve symbols from the already-loaded method lib. local RDYNAMIC = @@ -114,40 +71,11 @@ lib boost_openmethod-dl_test_overrider windows:boost_openmethod-dl_test_registry ; -# Plain-name aliases: after building each shared lib, create a symlink/copy -# with the undecorated name so Boost.DLL's append_decorations can find it. -local plain-aliases ; -for local lib in - boost_openmethod-dl_test_registry - boost_openmethod-dl_test_method - boost_openmethod-dl_test_overrider -{ - local plain-name ; - if $(os-name) = NT - { - plain-name = $(lib).dll ; - } - else if $(os-name) != MACOSX - { - plain-name = lib$(lib).so ; - } - if $(plain-name) - { - make $(plain-name) - : $(lib) - : @plain-lib-alias - : shared - global - ; - plain-aliases += $(plain-name) ; - } -} - # Test executable: # sources (linked) : main.cpp, lib_registry, unit_test_framework, boost_dll -# input-files (built but not linked, placed in the same bin/ dir): -# lib_method, lib_overrider -# At runtime the test locates shared libs via dll::program_location().parent_path(). +# runtime-loaded : lib_method, lib_overrider +# main.cpp locates shared libs by scanning dll::program_location().parent_path() +# (POSIX) or dll::symbol_location_ptr(get_fn()).parent_path() (Windows). run main.cpp boost_openmethod-dl_test_registry /boost/test//boost_unit_test_framework/off @@ -164,7 +92,6 @@ run main.cpp windows:boost_openmethod-dl_test_method boost_openmethod-dl_test_method boost_openmethod-dl_test_overrider - $(plain-aliases) : dynamic_loading ; diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index b8470fa2..74d70352 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -62,32 +62,42 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { // statics) are visible globally and win over any locally-instantiated // copies when the overrider library is subsequently loaded. - constexpr auto load_mode = dll::load_mode::rtld_global | - dll::load_mode::append_decorations | - dll::load_mode::search_system_folders; + constexpr auto load_mode = dll::load_mode::rtld_global; + namespace bfs = boost::filesystem; + + // On Windows, load the method DLL via its already-imported symbol to get + // exactly the same module (avoid a second copy from the plain-named DLL). + // On POSIX/macOS, locate libraries by scanning the executable's directory. #ifdef _WIN32 - // On Windows, DLL names are decorated (e.g. -vc145-mt-gd-x64-1_91.dll). - // Load the method DLL via its already-imported symbol to get exactly the - // same module handle (avoid loading the plain-named copy as a second - // module). Scan the same directory for the overrider by stem fragment. auto method_path = dll::symbol_location_ptr(get_fn()); - namespace bfs = boost::filesystem; - auto find_dll = [&](const char* stem_fragment) { - for (auto& entry : bfs::directory_iterator(method_path.parent_path())) { - if (entry.path().extension() == ".dll" && - entry.path().stem().string().find(stem_fragment) != - std::string::npos) { - return entry.path(); - } + auto search_dir = method_path.parent_path(); +#else + auto search_dir = dll::program_location().parent_path(); +#endif + + auto find_lib = [&](const char* name_fragment) { + for (auto& entry : bfs::directory_iterator(search_dir)) { + auto fname = entry.path().filename().string(); + if (fname.find(name_fragment) == std::string::npos) + continue; +#ifdef _WIN32 + if (entry.path().extension() != ".dll") + continue; +#else + auto ext = entry.path().extension().string(); + if (fname.find(".so") == std::string::npos && ext != ".dylib") + continue; +#endif + return entry.path(); } - throw std::runtime_error( - std::string("DLL not found: ") + stem_fragment); + throw std::runtime_error(std::string("lib not found: ") + name_fragment); }; + +#ifdef _WIN32 dll::shared_library method_lib(method_path, load_mode); #else - dll::shared_library method_lib( - "boost_openmethod-dl_test_method", load_mode); + dll::shared_library method_lib(find_lib("method"), load_mode); #endif auto& method_get_ids = method_lib.get_alias("method_get_ids"); @@ -115,12 +125,7 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { BOOST_TEST(method_speak(main_dog) == "?"); BOOST_TEST(method_speak(method_dog) == "?"); -#ifdef _WIN32 - dll::shared_library overrider_lib(find_dll("overrider"), load_mode); -#else - dll::shared_library overrider_lib( - "boost_openmethod-dl_test_overrider", load_mode); -#endif + dll::shared_library overrider_lib(find_lib("overrider"), load_mode); auto overrider_get_ids = overrider_lib.get_alias("overrider_get_ids"); auto overrider_speak = From a96a84acfb96cb43fbc93acda31ec1246aff7775 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 19 Apr 2026 12:54:32 -0400 Subject: [PATCH 59/74] always use find_lib --- test/dynamic_loading/main.cpp | 45 ++++++++++------------------------- 1 file changed, 12 insertions(+), 33 deletions(-) diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index 74d70352..5c7c7df7 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -27,6 +27,17 @@ using method_fn = const void*(); BOOST_OPENMETHOD_CLASSES(Animal, Dog); +boost::filesystem::path find_lib( + const boost::filesystem::path& dir, const char* name_fragment) { + for (auto& entry : boost::filesystem::directory_iterator(dir)) { + auto fname = entry.path().filename().string(); + if (fname.find(name_fragment) != std::string::npos) { + return entry.path(); + } + } + throw std::runtime_error(std::string("lib not found: ") + name_fragment); +} + bool same_ids(const void** ids1, const void** ids2) { using std::setw; BOOST_TEST_MESSAGE( @@ -64,41 +75,9 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { constexpr auto load_mode = dll::load_mode::rtld_global; - namespace bfs = boost::filesystem; - - // On Windows, load the method DLL via its already-imported symbol to get - // exactly the same module (avoid a second copy from the plain-named DLL). - // On POSIX/macOS, locate libraries by scanning the executable's directory. -#ifdef _WIN32 auto method_path = dll::symbol_location_ptr(get_fn()); auto search_dir = method_path.parent_path(); -#else - auto search_dir = dll::program_location().parent_path(); -#endif - - auto find_lib = [&](const char* name_fragment) { - for (auto& entry : bfs::directory_iterator(search_dir)) { - auto fname = entry.path().filename().string(); - if (fname.find(name_fragment) == std::string::npos) - continue; -#ifdef _WIN32 - if (entry.path().extension() != ".dll") - continue; -#else - auto ext = entry.path().extension().string(); - if (fname.find(".so") == std::string::npos && ext != ".dylib") - continue; -#endif - return entry.path(); - } - throw std::runtime_error(std::string("lib not found: ") + name_fragment); - }; - -#ifdef _WIN32 dll::shared_library method_lib(method_path, load_mode); -#else - dll::shared_library method_lib(find_lib("method"), load_mode); -#endif auto& method_get_ids = method_lib.get_alias("method_get_ids"); auto& method_speak = method_lib.get_alias)>( @@ -125,7 +104,7 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { BOOST_TEST(method_speak(main_dog) == "?"); BOOST_TEST(method_speak(method_dog) == "?"); - dll::shared_library overrider_lib(find_lib("overrider"), load_mode); + dll::shared_library overrider_lib(find_lib(search_dir, "overrider"), load_mode); auto overrider_get_ids = overrider_lib.get_alias("overrider_get_ids"); auto overrider_speak = From 07e94ab8c92d4013e37cf87f8e59da58b5f8acad Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 19 Apr 2026 13:46:31 -0400 Subject: [PATCH 60/74] cml --- test/dynamic_loading/CMakeLists.txt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/dynamic_loading/CMakeLists.txt b/test/dynamic_loading/CMakeLists.txt index d8ef7dc9..812fc7a8 100644 --- a/test/dynamic_loading/CMakeLists.txt +++ b/test/dynamic_loading/CMakeLists.txt @@ -3,9 +3,8 @@ # See accompanying file LICENSE_1_0.txt # or copy at http://www.boost.org/LICENSE_1_0.txt) -if (NOT BUILD_SHARED_LIBS) - message(STATUS "Skipping dynamic_loading test: requires BUILD_SHARED_LIBS=ON") - return() +if (BUILD_SHARED_LIBS) + message(STATUS "Building shared library tests") endif() # lib_registry: exports the default registry's static policy state From 6795e57ee056bcba506f0307e37384064486e648 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 19 Apr 2026 14:18:24 -0400 Subject: [PATCH 61/74] wsl --- test/dynamic_loading/main.cpp | 20 +++++++++-------- test/dynamic_loading/method.cpp | 33 ++++++++++++++++++---------- test/dynamic_loading/overrider.cpp | 35 ++++++++++++++++++++---------- test/dynamic_loading/registry.cpp | 5 ----- 4 files changed, 57 insertions(+), 36 deletions(-) diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index 5c7c7df7..3d01e242 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -79,14 +79,16 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { auto search_dir = method_path.parent_path(); dll::shared_library method_lib(method_path, load_mode); auto& method_get_ids = - method_lib.get_alias("method_get_ids"); - auto& method_speak = method_lib.get_alias)>( + method_lib.get("method_get_ids"); + auto& method_speak = method_lib.get)>( "method_call_speak"); auto& method_make_dog = - method_lib.get_alias()>("method_make_dog"); - auto& method_get_fn = method_lib.get_alias("method_get_fn"); + method_lib.get()>("method_make_dog"); + auto& method_get_fn = method_lib.get("method_get_fn"); - BOOST_TEST(same_ids(get_ids(), method_get_ids())); + auto p = get_ids(); + auto q = method_get_ids(); + BOOST_TEST(same_ids(p, q)); BOOST_TEST(get_fn() == method_get_fn()); initialize(); @@ -106,15 +108,15 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { dll::shared_library overrider_lib(find_lib(search_dir, "overrider"), load_mode); auto overrider_get_ids = - overrider_lib.get_alias("overrider_get_ids"); + overrider_lib.get("overrider_get_ids"); auto overrider_speak = - overrider_lib.get_alias)>( + overrider_lib.get)>( "overrider_call_speak"); auto overrider_make_dog = - overrider_lib.get_alias()>( + overrider_lib.get()>( "overrider_make_dog"); auto overrider_get_fn = - overrider_lib.get_alias("overrider_get_fn"); + overrider_lib.get("overrider_get_fn"); BOOST_TEST(same_ids(get_ids(), overrider_get_ids())); BOOST_TEST(get_fn() == overrider_get_fn()); diff --git a/test/dynamic_loading/method.cpp b/test/dynamic_loading/method.cpp index 5759577c..b960b05f 100644 --- a/test/dynamic_loading/method.cpp +++ b/test/dynamic_loading/method.cpp @@ -35,16 +35,27 @@ BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { return "?"; } -#ifdef _WIN32 +#if defined(_MSC_VER) +#pragma warning(disable : 4190) // C-linkage function returns UDT +#elif defined(__clang__) +#pragma clang diagnostic ignored "-Wreturn-type-c-linkage" +#endif + extern "C" { - BOOST_SYMBOL_EXPORT const void* method_get_ids = (const void*)&get_ids; - BOOST_SYMBOL_EXPORT const void* method_get_fn = (const void*)&get_fn; - BOOST_SYMBOL_EXPORT const void* method_make_dog = (const void*)&make_dog; - BOOST_SYMBOL_EXPORT const void* method_call_speak = (const void*)&call_speak; +BOOST_SYMBOL_EXPORT const void* method_get_ids() { + return get_ids(); +} + +BOOST_SYMBOL_EXPORT const void* method_get_fn() { + return get_fn(); +} + +BOOST_SYMBOL_EXPORT unique_virtual_ptr method_make_dog() { + return make_dog(); +} + +BOOST_SYMBOL_EXPORT const char* +method_call_speak(boost::openmethod::virtual_ptr animal) { + return speak(animal); +} } -#else -BOOST_DLL_ALIAS(get_ids, method_get_ids) -BOOST_DLL_ALIAS(get_fn, method_get_fn) -BOOST_DLL_ALIAS(make_dog, method_make_dog) -BOOST_DLL_ALIAS(call_speak, method_call_speak) -#endif diff --git a/test/dynamic_loading/overrider.cpp b/test/dynamic_loading/overrider.cpp index f3d7876d..ba5411a3 100644 --- a/test/dynamic_loading/overrider.cpp +++ b/test/dynamic_loading/overrider.cpp @@ -24,16 +24,29 @@ BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { BOOST_OPENMETHOD_CLASSES(Animal, Dog); -#ifdef _WIN32 + +#if defined(_MSC_VER) +#pragma warning(disable : 4190) // C-linkage function returns UDT +#elif defined(__clang__) +#pragma clang diagnostic ignored "-Wreturn-type-c-linkage" +#endif + extern "C" { - BOOST_SYMBOL_EXPORT const void* overrider_get_ids = (const void*)&get_ids; - BOOST_SYMBOL_EXPORT const void* overrider_get_fn = (const void*)&get_fn; - BOOST_SYMBOL_EXPORT const void* overrider_make_dog = (const void*)&make_dog; - BOOST_SYMBOL_EXPORT const void* overrider_call_speak = (const void*)&call_speak; + +BOOST_SYMBOL_EXPORT const void* overrider_get_ids() { + return get_ids(); +} + +BOOST_SYMBOL_EXPORT const void* overrider_get_fn() { + return get_fn(); +} + +BOOST_SYMBOL_EXPORT unique_virtual_ptr overrider_make_dog() { + return make_dog(); +} + +BOOST_SYMBOL_EXPORT const char* +overrider_call_speak(boost::openmethod::virtual_ptr animal) { + return speak(animal); +} } -#else -BOOST_DLL_ALIAS(get_ids, overrider_get_ids) -BOOST_DLL_ALIAS(get_fn, overrider_get_fn) -BOOST_DLL_ALIAS(make_dog, overrider_make_dog) -BOOST_DLL_ALIAS(call_speak, overrider_call_speak) -#endif diff --git a/test/dynamic_loading/registry.cpp b/test/dynamic_loading/registry.cpp index e4b3b868..ace4c74a 100644 --- a/test/dynamic_loading/registry.cpp +++ b/test/dynamic_loading/registry.cpp @@ -26,15 +26,10 @@ static_assert(std::is_same_v); BOOST_OPENMETHOD_CLASSES(Animal, Dog); -#ifdef _WIN32 extern "C" { BOOST_SYMBOL_EXPORT const void* registry_get_ids = (const void*)&get_ids; BOOST_SYMBOL_EXPORT const void* registry_make_dog = (const void*)&make_dog; } -#else -BOOST_DLL_ALIAS(get_ids, registry_get_ids) -BOOST_DLL_ALIAS(make_dog, registry_make_dog) -#endif void registry_initialize() { boost::openmethod::initialize(trace::from_env()); From 04576b2b75a97594c070f92154228fc063ea4516 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 19 Apr 2026 14:30:07 -0400 Subject: [PATCH 62/74] win --- test/dynamic_loading/main.cpp | 22 +++++++++++----------- test/dynamic_loading/method.cpp | 5 +++-- test/dynamic_loading/overrider.cpp | 5 +++-- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index 3d01e242..0d22c5ac 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -83,18 +83,17 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { auto& method_speak = method_lib.get)>( "method_call_speak"); auto& method_make_dog = - method_lib.get()>("method_make_dog"); + method_lib.get&)>("method_make_dog"); auto& method_get_fn = method_lib.get("method_get_fn"); - auto p = get_ids(); - auto q = method_get_ids(); - BOOST_TEST(same_ids(p, q)); + BOOST_TEST(same_ids(get_ids(), method_get_ids())); BOOST_TEST(get_fn() == method_get_fn()); initialize(); auto main_dog = make_dog(); - auto method_dog = method_make_dog(); + unique_virtual_ptr method_dog; + method_make_dog(method_dog); BOOST_TEST(main_dog.vptr() == method_dog.vptr()); #ifdef _WIN32 { @@ -113,7 +112,7 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { overrider_lib.get)>( "overrider_call_speak"); auto overrider_make_dog = - overrider_lib.get()>( + overrider_lib.get&)>( "overrider_make_dog"); auto overrider_get_fn = overrider_lib.get("overrider_get_fn"); @@ -122,11 +121,12 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { BOOST_TEST(get_fn() == overrider_get_fn()); initialize(); - auto overrider_dog = overrider_make_dog(); + unique_virtual_ptr overrider_dog; + overrider_make_dog(overrider_dog); main_dog = make_dog(); // because its vptr was invalidated by initialize() - method_dog = method_make_dog(); // ditto + method_make_dog(method_dog); // ditto BOOST_TEST(main_dog.vptr() == overrider_dog.vptr()); - BOOST_TEST(overrider_speak(main_dog) == "woof"); - BOOST_TEST(overrider_speak(method_dog) == "woof"); - BOOST_TEST(overrider_speak(overrider_dog) == "woof"); + BOOST_TEST(std::string(overrider_speak(main_dog)) == "woof"); + BOOST_TEST(std::string(overrider_speak(method_dog)) == "woof"); + BOOST_TEST(std::string(overrider_speak(overrider_dog)) == "woof"); } diff --git a/test/dynamic_loading/method.cpp b/test/dynamic_loading/method.cpp index b960b05f..948f45d0 100644 --- a/test/dynamic_loading/method.cpp +++ b/test/dynamic_loading/method.cpp @@ -50,8 +50,9 @@ BOOST_SYMBOL_EXPORT const void* method_get_fn() { return get_fn(); } -BOOST_SYMBOL_EXPORT unique_virtual_ptr method_make_dog() { - return make_dog(); +BOOST_SYMBOL_EXPORT void +method_make_dog(unique_virtual_ptr& p) { + p = make_dog(); } BOOST_SYMBOL_EXPORT const char* diff --git a/test/dynamic_loading/overrider.cpp b/test/dynamic_loading/overrider.cpp index ba5411a3..64989a57 100644 --- a/test/dynamic_loading/overrider.cpp +++ b/test/dynamic_loading/overrider.cpp @@ -41,8 +41,9 @@ BOOST_SYMBOL_EXPORT const void* overrider_get_fn() { return get_fn(); } -BOOST_SYMBOL_EXPORT unique_virtual_ptr overrider_make_dog() { - return make_dog(); +BOOST_SYMBOL_EXPORT void +overrider_make_dog(unique_virtual_ptr& p) { + p = make_dog(); } BOOST_SYMBOL_EXPORT const char* From f7db9887acb105128e4cda94167948c70af42474 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 19 Apr 2026 14:33:26 -0400 Subject: [PATCH 63/74] cml --- test/dynamic_loading/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/dynamic_loading/CMakeLists.txt b/test/dynamic_loading/CMakeLists.txt index 812fc7a8..b8d84809 100644 --- a/test/dynamic_loading/CMakeLists.txt +++ b/test/dynamic_loading/CMakeLists.txt @@ -5,6 +5,8 @@ if (BUILD_SHARED_LIBS) message(STATUS "Building shared library tests") +else() + return() endif() # lib_registry: exports the default registry's static policy state From 0657adb3ca9624387afbc3dced012d31af0e3f3f Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 19 Apr 2026 14:44:03 -0400 Subject: [PATCH 64/74] wip --- test/dynamic_loading/main.cpp | 10 +++++----- test/dynamic_loading/method.cpp | 8 ++------ test/dynamic_loading/overrider.cpp | 7 ------- 3 files changed, 7 insertions(+), 18 deletions(-) diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index 0d22c5ac..59bf3ca3 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -29,7 +29,7 @@ BOOST_OPENMETHOD_CLASSES(Animal, Dog); boost::filesystem::path find_lib( const boost::filesystem::path& dir, const char* name_fragment) { - for (auto& entry : boost::filesystem::directory_iterator(dir)) { + for (auto entry : boost::filesystem::directory_iterator(dir)) { auto fname = entry.path().filename().string(); if (fname.find(name_fragment) != std::string::npos) { return entry.path(); @@ -78,13 +78,13 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { auto method_path = dll::symbol_location_ptr(get_fn()); auto search_dir = method_path.parent_path(); dll::shared_library method_lib(method_path, load_mode); - auto& method_get_ids = + auto method_get_ids = method_lib.get("method_get_ids"); - auto& method_speak = method_lib.get)>( + auto method_speak = method_lib.get)>( "method_call_speak"); - auto& method_make_dog = + auto method_make_dog = method_lib.get&)>("method_make_dog"); - auto& method_get_fn = method_lib.get("method_get_fn"); + auto method_get_fn = method_lib.get("method_get_fn"); BOOST_TEST(same_ids(get_ids(), method_get_ids())); BOOST_TEST(get_fn() == method_get_fn()); diff --git a/test/dynamic_loading/method.cpp b/test/dynamic_loading/method.cpp index 948f45d0..b8fa206d 100644 --- a/test/dynamic_loading/method.cpp +++ b/test/dynamic_loading/method.cpp @@ -35,13 +35,8 @@ BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { return "?"; } -#if defined(_MSC_VER) -#pragma warning(disable : 4190) // C-linkage function returns UDT -#elif defined(__clang__) -#pragma clang diagnostic ignored "-Wreturn-type-c-linkage" -#endif - extern "C" { + BOOST_SYMBOL_EXPORT const void* method_get_ids() { return get_ids(); } @@ -59,4 +54,5 @@ BOOST_SYMBOL_EXPORT const char* method_call_speak(boost::openmethod::virtual_ptr animal) { return speak(animal); } + } diff --git a/test/dynamic_loading/overrider.cpp b/test/dynamic_loading/overrider.cpp index 64989a57..c4677834 100644 --- a/test/dynamic_loading/overrider.cpp +++ b/test/dynamic_loading/overrider.cpp @@ -24,13 +24,6 @@ BOOST_OPENMETHOD_OVERRIDE(speak, (virtual_ptr), const char*) { BOOST_OPENMETHOD_CLASSES(Animal, Dog); - -#if defined(_MSC_VER) -#pragma warning(disable : 4190) // C-linkage function returns UDT -#elif defined(__clang__) -#pragma clang diagnostic ignored "-Wreturn-type-c-linkage" -#endif - extern "C" { BOOST_SYMBOL_EXPORT const void* overrider_get_ids() { From 2903cdbd04da868bb6f41b4a45bb69178e512452 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Mon, 20 Apr 2026 05:58:20 -0400 Subject: [PATCH 65/74] shift OK --- include/boost/openmethod/initialize.hpp | 7 ------- 1 file changed, 7 deletions(-) diff --git a/include/boost/openmethod/initialize.hpp b/include/boost/openmethod/initialize.hpp index 88e853c0..066ac284 100644 --- a/include/boost/openmethod/initialize.hpp +++ b/include/boost/openmethod/initialize.hpp @@ -23,14 +23,7 @@ #include #include -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wshift-count-overflow" -#endif #include -#if defined(__clang__) -# pragma clang diagnostic pop -#endif #ifdef _MSC_VER #pragma warning(push) From 85831c36b0f2302f594f9440f97b2abe63f42e0f Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Mon, 20 Apr 2026 06:13:00 -0400 Subject: [PATCH 66/74] cygwin --- include/boost/openmethod/preamble.hpp | 4 +- test/dynamic_loading/Jamfile | 2 + test/dynamic_loading/main.cpp | 53 ++++++++++++++++++++------- test/dynamic_loading/method.cpp | 4 +- test/dynamic_loading/method.hpp | 2 +- test/dynamic_loading/overrider.cpp | 2 +- test/dynamic_loading/registry.cpp | 2 +- test/dynamic_loading/registry.hpp | 2 +- 8 files changed, 50 insertions(+), 21 deletions(-) diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index 5530e1b3..96023ad6 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -946,7 +946,7 @@ struct initialize_aux; struct declspec {}; struct declspec_none : declspec {}; -#if defined(__MRDOCS__) || defined(_WIN32) +#if defined(__MRDOCS__) || defined(_WIN32) || defined(__CYGWIN__) struct dllexport : declspec {}; struct dllimport : declspec {}; #endif @@ -978,7 +978,7 @@ namespace detail { #define BOOST_OPENMETHOD_DETAIL_RESTORE_DLLIMPORT_UNDEF_VAR #endif -#if defined(_WIN32) +#if defined(_WIN32) || defined(__CYGWIN__) #define BOOST_OPENMETHOD_DETAIL_MAKE_STATICS(ID, ...) \ BOOST_OPENMETHOD_DETAIL_MAKE_STATICS_COMMON(ID, __VA_ARGS__) \ \ diff --git a/test/dynamic_loading/Jamfile b/test/dynamic_loading/Jamfile index 4420b8a3..db33f919 100644 --- a/test/dynamic_loading/Jamfile +++ b/test/dynamic_loading/Jamfile @@ -69,6 +69,7 @@ lib boost_openmethod-dl_test_overrider /boost/dll//boost_dll boost_openmethod-dl_test_method windows:boost_openmethod-dl_test_registry + cygwin:boost_openmethod-dl_test_registry ; # Test executable: @@ -90,6 +91,7 @@ run main.cpp android:"-ldl" windows:boost_openmethod-dl_test_registry windows:boost_openmethod-dl_test_method + cygwin:boost_openmethod-dl_test_method boost_openmethod-dl_test_method boost_openmethod-dl_test_overrider : dynamic_loading diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index 59bf3ca3..19a77cec 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -16,7 +16,9 @@ #include #include +#include #include +#include #include using namespace boost::openmethod; @@ -27,8 +29,8 @@ using method_fn = const void*(); BOOST_OPENMETHOD_CLASSES(Animal, Dog); -boost::filesystem::path find_lib( - const boost::filesystem::path& dir, const char* name_fragment) { +boost::filesystem::path +find_lib(const boost::filesystem::path& dir, const char* name_fragment) { for (auto entry : boost::filesystem::directory_iterator(dir)) { auto fname = entry.path().filename().string(); if (fname.find(name_fragment) != std::string::npos) { @@ -38,6 +40,29 @@ boost::filesystem::path find_lib( throw std::runtime_error(std::string("lib not found: ") + name_fragment); } +boost::filesystem::path find_lib_in_path(const char* name_fragment) { + const char* path_env = std::getenv("PATH"); + if (!path_env) { + throw std::runtime_error("PATH not set"); + } + std::istringstream ss(path_env); + std::string dir; + while (std::getline(ss, dir, ':')) { + if (dir.empty()) + continue; + boost::filesystem::path p(dir); + boost::system::error_code ec; + if (!boost::filesystem::is_directory(p, ec)) + continue; + try { + return find_lib(p, name_fragment); + } catch (...) { + } + } + throw std::runtime_error( + std::string("lib not found in PATH: ") + name_fragment); +} + bool same_ids(const void** ids1, const void** ids2) { using std::setw; BOOST_TEST_MESSAGE( @@ -75,13 +100,16 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { constexpr auto load_mode = dll::load_mode::rtld_global; +#ifdef _WIN32 auto method_path = dll::symbol_location_ptr(get_fn()); +#else + auto method_path = find_lib_in_path("test_method"); +#endif auto search_dir = method_path.parent_path(); dll::shared_library method_lib(method_path, load_mode); - auto method_get_ids = - method_lib.get("method_get_ids"); - auto method_speak = method_lib.get)>( - "method_call_speak"); + auto method_get_ids = method_lib.get("method_get_ids"); + auto method_speak = + method_lib.get)>("method_call_speak"); auto method_make_dog = method_lib.get&)>("method_make_dog"); auto method_get_fn = method_lib.get("method_get_fn"); @@ -95,7 +123,7 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { unique_virtual_ptr method_dog; method_make_dog(method_dog); BOOST_TEST(main_dog.vptr() == method_dog.vptr()); -#ifdef _WIN32 +#if defined(_WIN32) || defined(__CYGWIN__) { Animal* p1 = main_dog.get(); Animal* p2 = method_dog.get(); @@ -105,17 +133,16 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { BOOST_TEST(method_speak(main_dog) == "?"); BOOST_TEST(method_speak(method_dog) == "?"); - dll::shared_library overrider_lib(find_lib(search_dir, "overrider"), load_mode); + dll::shared_library overrider_lib( + find_lib(search_dir, "test_overrider"), load_mode); auto overrider_get_ids = overrider_lib.get("overrider_get_ids"); - auto overrider_speak = - overrider_lib.get)>( - "overrider_call_speak"); + auto overrider_speak = overrider_lib.get)>( + "overrider_call_speak"); auto overrider_make_dog = overrider_lib.get&)>( "overrider_make_dog"); - auto overrider_get_fn = - overrider_lib.get("overrider_get_fn"); + auto overrider_get_fn = overrider_lib.get("overrider_get_fn"); BOOST_TEST(same_ids(get_ids(), overrider_get_ids())); BOOST_TEST(get_fn() == overrider_get_fn()); diff --git a/test/dynamic_loading/method.cpp b/test/dynamic_loading/method.cpp index b8fa206d..6a25d99e 100644 --- a/test/dynamic_loading/method.cpp +++ b/test/dynamic_loading/method.cpp @@ -16,7 +16,7 @@ #include -#ifdef _WIN32 +#if defined(_WIN32) || defined(__CYGWIN__) #include #else #include @@ -25,7 +25,7 @@ using namespace boost::openmethod; namespace mp11 = boost::mp11; -#ifdef _WIN32 +#if defined(_WIN32) || defined(__CYGWIN__) static_assert(std::is_same_v); #endif diff --git a/test/dynamic_loading/method.hpp b/test/dynamic_loading/method.hpp index 607d6db6..bda305ee 100644 --- a/test/dynamic_loading/method.hpp +++ b/test/dynamic_loading/method.hpp @@ -8,7 +8,7 @@ #include -#if defined(_WIN32) +#if defined(_WIN32) || defined(__CYGWIN__) #if defined(EXPORT_METHOD) #define METHOD_DECLSPEC boost::openmethod::dllexport #else diff --git a/test/dynamic_loading/overrider.cpp b/test/dynamic_loading/overrider.cpp index c4677834..43914880 100644 --- a/test/dynamic_loading/overrider.cpp +++ b/test/dynamic_loading/overrider.cpp @@ -9,7 +9,7 @@ #include "method.hpp" -#ifdef _WIN32 +#if defined(_WIN32) || defined(__CYGWIN__) #include #else #include diff --git a/test/dynamic_loading/registry.cpp b/test/dynamic_loading/registry.cpp index ace4c74a..6509ea7d 100644 --- a/test/dynamic_loading/registry.cpp +++ b/test/dynamic_loading/registry.cpp @@ -17,7 +17,7 @@ using namespace boost::openmethod; namespace mp11 = boost::mp11; -#ifdef _WIN32 +#if defined(_WIN32) || defined(__CYGWIN__) #include static_assert(std::is_same_v); #else diff --git a/test/dynamic_loading/registry.hpp b/test/dynamic_loading/registry.hpp index 61941147..4c84ef00 100644 --- a/test/dynamic_loading/registry.hpp +++ b/test/dynamic_loading/registry.hpp @@ -8,7 +8,7 @@ #include -#if defined(_WIN32) +#if defined(_WIN32) || defined(__CYGWIN__) #if defined(EXPORT_REGISTRY) #define REGISTRY_DECLSPEC boost::openmethod::dllexport #else From 4394cfc11efc61984d295b4348d917766a8cfaa9 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Mon, 20 Apr 2026 06:20:43 -0400 Subject: [PATCH 67/74] cygwin --- test/dynamic_loading/main.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index 19a77cec..281ef246 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -100,12 +100,13 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { constexpr auto load_mode = dll::load_mode::rtld_global; -#ifdef _WIN32 - auto method_path = dll::symbol_location_ptr(get_fn()); -#else - auto method_path = find_lib_in_path("test_method"); -#endif - auto search_dir = method_path.parent_path(); +// #ifdef _WIN32 +// auto method_path = dll::symbol_location_ptr(get_fn); +// #else +// auto method_path = find_lib_in_path("test_method"); +// #endif + auto search_dir = boost::dll::program_location().parent_path(); + auto method_path = find_lib(search_dir, "test_method"); dll::shared_library method_lib(method_path, load_mode); auto method_get_ids = method_lib.get("method_get_ids"); auto method_speak = From ea7df818ef13983ca76dcc8ecdb31e0ac04bdb04 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Mon, 20 Apr 2026 08:00:53 -0400 Subject: [PATCH 68/74] wip --- test/dynamic_loading/main.cpp | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index 281ef246..64950861 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -40,29 +40,6 @@ find_lib(const boost::filesystem::path& dir, const char* name_fragment) { throw std::runtime_error(std::string("lib not found: ") + name_fragment); } -boost::filesystem::path find_lib_in_path(const char* name_fragment) { - const char* path_env = std::getenv("PATH"); - if (!path_env) { - throw std::runtime_error("PATH not set"); - } - std::istringstream ss(path_env); - std::string dir; - while (std::getline(ss, dir, ':')) { - if (dir.empty()) - continue; - boost::filesystem::path p(dir); - boost::system::error_code ec; - if (!boost::filesystem::is_directory(p, ec)) - continue; - try { - return find_lib(p, name_fragment); - } catch (...) { - } - } - throw std::runtime_error( - std::string("lib not found in PATH: ") + name_fragment); -} - bool same_ids(const void** ids1, const void** ids2) { using std::setw; BOOST_TEST_MESSAGE( From 2132d390f37e5e6b2b296bc308a119407d545b29 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Mon, 20 Apr 2026 18:34:49 -0400 Subject: [PATCH 69/74] DLL in same dir as exe --- test/dynamic_loading/Jamfile | 13 +++++++++---- test/dynamic_loading/main.cpp | 8 +------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/test/dynamic_loading/Jamfile b/test/dynamic_loading/Jamfile index db33f919..929fa01c 100644 --- a/test/dynamic_loading/Jamfile +++ b/test/dynamic_loading/Jamfile @@ -21,6 +21,7 @@ import os ; local os-name = [ os.name ] ; local visibility-env = [ os.environ B2_VISIBILITY ] ; +local windir = [ os.environ WINDIR ] ; # -rdynamic: export exe symbols to dynamically loaded libs so that # dlopen'd overrider can resolve symbols from the already-loaded method lib. @@ -38,7 +39,7 @@ local RDYNAMIC = # linker deduplicates them across DSOs. MSVC ignores this property. local VISIBILITY = global ; -if ( $(os-name) = NT ) || ( $(visibility-env) = global ) +if $(windir) || ( $(visibility-env) = global ) { # lib_registry: owns and exports the default registry's static policy state. @@ -48,6 +49,7 @@ lib boost_openmethod-dl_test_registry : shared $(VISIBILITY) /boost/dll//boost_dll + dynamic_loading.test ; # lib_method: exports the speak() open-method; imports registry from @@ -58,6 +60,7 @@ lib boost_openmethod-dl_test_method $(VISIBILITY) /boost/dll//boost_dll boost_openmethod-dl_test_registry + dynamic_loading.test ; # lib_overrider: adds the Dog overrider; dynamically loaded at runtime. @@ -70,13 +73,15 @@ lib boost_openmethod-dl_test_overrider boost_openmethod-dl_test_method windows:boost_openmethod-dl_test_registry cygwin:boost_openmethod-dl_test_registry + dynamic_loading.test ; # Test executable: # sources (linked) : main.cpp, lib_registry, unit_test_framework, boost_dll # runtime-loaded : lib_method, lib_overrider -# main.cpp locates shared libs by scanning dll::program_location().parent_path() -# (POSIX) or dll::symbol_location_ptr(get_fn()).parent_path() (Windows). +# All shared libraries are placed alongside the test executable via +# dynamic_loading.test, so main.cpp can find them by scanning +# dll::program_location().parent_path() on all platforms. run main.cpp boost_openmethod-dl_test_registry /boost/test//boost_unit_test_framework/off @@ -97,4 +102,4 @@ run main.cpp : dynamic_loading ; -} # if NT || B2_VISIBILITY=global +} # if Windows (WINDIR set) || B2_VISIBILITY=global diff --git a/test/dynamic_loading/main.cpp b/test/dynamic_loading/main.cpp index 64950861..b8a0dba0 100644 --- a/test/dynamic_loading/main.cpp +++ b/test/dynamic_loading/main.cpp @@ -16,9 +16,7 @@ #include #include -#include #include -#include #include using namespace boost::openmethod; @@ -77,12 +75,8 @@ BOOST_AUTO_TEST_CASE(test_shared_state) { constexpr auto load_mode = dll::load_mode::rtld_global; -// #ifdef _WIN32 -// auto method_path = dll::symbol_location_ptr(get_fn); -// #else -// auto method_path = find_lib_in_path("test_method"); -// #endif auto search_dir = boost::dll::program_location().parent_path(); + BOOST_TEST_MESSAGE("search_dir: " << search_dir); auto method_path = find_lib(search_dir, "test_method"); dll::shared_library method_lib(method_path, load_mode); auto method_get_ids = method_lib.get("method_get_ids"); From ddac200b30e7b8c3954cf2c4a21458c1b543af0e Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Tue, 21 Apr 2026 08:54:01 -0400 Subject: [PATCH 70/74] posix? --- test/dynamic_loading/Jamfile | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/dynamic_loading/Jamfile b/test/dynamic_loading/Jamfile index 929fa01c..8ebf13b9 100644 --- a/test/dynamic_loading/Jamfile +++ b/test/dynamic_loading/Jamfile @@ -39,9 +39,6 @@ local RDYNAMIC = # linker deduplicates them across DSOs. MSVC ignores this property. local VISIBILITY = global ; -if $(windir) || ( $(visibility-env) = global ) -{ - # lib_registry: owns and exports the default registry's static policy state. # registry.cpp defines EXPORT_REGISTRY and INCLUDED_FROM itself. lib boost_openmethod-dl_test_registry @@ -101,5 +98,3 @@ run main.cpp boost_openmethod-dl_test_overrider : dynamic_loading ; - -} # if Windows (WINDIR set) || B2_VISIBILITY=global From 3fc41a2fb14c196a5ea7af32fbc0b6d7d98c9f99 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 26 Apr 2026 12:15:41 -0400 Subject: [PATCH 71/74] b2: add boost_filesystem --- test/dynamic_loading/Jamfile | 1 + 1 file changed, 1 insertion(+) diff --git a/test/dynamic_loading/Jamfile b/test/dynamic_loading/Jamfile index 8ebf13b9..1d244e58 100644 --- a/test/dynamic_loading/Jamfile +++ b/test/dynamic_loading/Jamfile @@ -88,6 +88,7 @@ run main.cpp : shared $(RDYNAMIC) $(VISIBILITY) + /boost/filesystem//boost_filesystem linux:"-ldl" freebsd:"-ldl" android:"-ldl" From 4342495a7f3711748bce1df000051e7e5ebb42fc Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 26 Apr 2026 13:14:24 -0400 Subject: [PATCH 72/74] CI: skip incapable compilers --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2d97c38..d7518a9b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,7 +43,7 @@ jobs: uses: boostorg/boost-ci/.github/workflows/reusable.yml@master with: exclude_cxxstd: '98,03,0x,11,14' - exclude_compiler: gcc-4.9,gcc-5,gcc-6,gcc-7,clang-3.9,clang-4.0,clang-5.0,clang-6.0,clang-7,clang-16 + exclude_compiler: gcc-4.9,gcc-5,gcc-6,gcc-7,gcc-8,gcc-9,clang-3.9,clang-4.0,clang-5.0,clang-6.0,clang-7,clang-16 # exclude clang-16 because it seems to use the c++14 standard library on ubuntu-24.04: # include/c++/14/bits/stl_pair.h:410:35: error: no matching function for call to 'get' # maybe similar to this: https://github.com/actions/runner-images/issues/9679 From abaa3a0e47f2619748d611eb6e39f28fa4048abd Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 26 Apr 2026 13:20:21 -0400 Subject: [PATCH 73/74] dynamic_loading test: suppress warnings for deps --- test/dynamic_loading/Jamfile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/dynamic_loading/Jamfile b/test/dynamic_loading/Jamfile index 1d244e58..001157ee 100644 --- a/test/dynamic_loading/Jamfile +++ b/test/dynamic_loading/Jamfile @@ -45,7 +45,7 @@ lib boost_openmethod-dl_test_registry : registry.cpp : shared $(VISIBILITY) - /boost/dll//boost_dll + /boost/dll//boost_dll/off dynamic_loading.test ; @@ -55,7 +55,7 @@ lib boost_openmethod-dl_test_method : method.cpp : shared $(VISIBILITY) - /boost/dll//boost_dll + /boost/dll//boost_dll/off boost_openmethod-dl_test_registry dynamic_loading.test ; @@ -66,7 +66,7 @@ lib boost_openmethod-dl_test_overrider : overrider.cpp : shared $(VISIBILITY) - /boost/dll//boost_dll + /boost/dll//boost_dll/off boost_openmethod-dl_test_method windows:boost_openmethod-dl_test_registry cygwin:boost_openmethod-dl_test_registry @@ -82,13 +82,13 @@ lib boost_openmethod-dl_test_overrider run main.cpp boost_openmethod-dl_test_registry /boost/test//boost_unit_test_framework/off - /boost/dll//boost_dll + /boost/dll//boost_dll/off : : : shared $(RDYNAMIC) $(VISIBILITY) - /boost/filesystem//boost_filesystem + /boost/filesystem//boost_filesystem/off linux:"-ldl" freebsd:"-ldl" android:"-ldl" From d2f96c8cae474f8f9fabf93cea7ee02551bbbb49 Mon Sep 17 00:00:00 2001 From: Jean-Louis Leroy Date: Sun, 10 May 2026 13:16:09 -0400 Subject: [PATCH 74/74] move almost everything to method_base --- include/boost/openmethod/core.hpp | 269 +++++++++++++------------- include/boost/openmethod/preamble.hpp | 7 + 2 files changed, 144 insertions(+), 132 deletions(-) diff --git a/include/boost/openmethod/core.hpp b/include/boost/openmethod/core.hpp index f49b2d62..60894834 100644 --- a/include/boost/openmethod/core.hpp +++ b/include/boost/openmethod/core.hpp @@ -1872,8 +1872,8 @@ struct virtual_traits, Registry> { //! @return A lvalue reference to a `virtual_ptr` to the same object, cast //! to `Derived::element_type`. template - static auto cast(const virtual_ptr& ptr) - -> decltype(auto) { + static auto + cast(const virtual_ptr& ptr) -> decltype(auto) { return ptr.template cast(); } @@ -1918,8 +1918,8 @@ struct virtual_traits&, Registry> { //! @return A lvalue reference to a `virtual_ptr` to the same object, cast //! to `Derived::element_type`. template - static auto cast(const virtual_ptr& ptr) - -> decltype(auto) { + static auto + cast(const virtual_ptr& ptr) -> decltype(auto) { return ptr.template cast< typename std::remove_reference_t::element_type>(); } @@ -2002,9 +2002,8 @@ struct init_bad_call { } }; -template -using method_base = std::conditional_t< - Registry::has_deferred_static_rtti, deferred_method_info, method_info>; +template +class method_base; template struct parameter_traits { @@ -2145,35 +2144,16 @@ template< class Registry = BOOST_OPENMETHOD_DEFAULT_REGISTRY> class method; -//! Method with a specific id, signature and return type -//! -//! `method` implements an open-method that takes a parameter list - -//! `Parameters` - and returns a `ReturnType`. -//! -//! `Parameters` must contain at least one virtual parameter, i.e. a parameter -//! that has a type in the form `virtual_ptr` or `virtual\_`. -//! The dynamic types of the virtual arguments are taken into account to select -//! the overrider to call. -//! -//! @see method -//! -//! @tparam Id A type representing the method's name -//! @tparam ReturnType The return type of the method -//! @tparam Parameters The types of the parameters -//! @tparam Registry The registry of the method -template< - typename Id, typename... Parameters, typename ReturnType, class Registry> -class method - : public detail::method_base, - public detail::static_fn< - Registry, method> { +namespace detail { + +template +class method_base + : public std::conditional_t< + Registry::has_deferred_static_rtti, deferred_method_info, method_info> { template struct override_aux; - friend struct detail::static_fn; - - // Aliases used in implementation only. Everything extracted from template - // arguments is capitalized like the arguments themselves. + using MethodType = method; using RegistryType = Registry; using rtti = typename Registry::rtti; using DeclaredParameters = mp11::mp_list; @@ -2186,34 +2166,6 @@ class method -> ReturnType; public: - //! Method singleton - //! - //! The only instance of `method`. Its `operator()` is used to call - //! the method. - //static method fn; - // `fn` cannot be `inline static` becaused of MSVC (19.43) bug causing - // a "no appropriate default constructor available". - - //! Call the method - //! - //! Call the method with `args`. The types of the arguments are the same as - //! the method `Parameters...`, stripped from any `virtual\_` decorators. - //! - //! @param args The arguments for the method call - //! - //! @par Errors - //! - //! If `Registry` contains an @ref error_handler policy, call its `error` - //! function with an object of one of the following types: - //! - //! @li @ref not_implemented: No overrider is applicable. - //! @li @ref ambiguous_call: More than one overrider is applicable, and - //! none is more specialized than all the others. - //! - auto operator()(typename BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS - StripVirtualDecorator::type... args) const - -> ReturnType; - //! Check if a next most specialized overrider exists //! //! Return `true` if a next most specialized overrider after _Fn_ exists, @@ -2310,8 +2262,8 @@ class method template auto resolve_multi_first( - const ArgType& arg, const MoreArgTypes&... more_args) const - -> detail::word; + const ArgType& arg, + const MoreArgTypes&... more_args) const -> detail::word; template< std::size_t VirtualArg, typename MethodArgList, typename ArgType, @@ -2320,34 +2272,22 @@ class method vptr_type dispatch, const ArgType& arg, const MoreArgTypes&... more_args) const -> detail::word; - template - FunctionPointer resolve(const ArgType&... args) const; - - template - struct thunk; - - template - struct thunk; - - method(); - method(const method&) = delete; - method(method&&) = delete; - ~method(); - void resolve(); // virtual if Registry contains has_deferred_static_rtti - static BOOST_NORETURN auto - fn_not_implemented(detail::remove_virtual_... args) - -> ReturnType; + static BOOST_NORETURN auto fn_not_implemented( + detail::remove_virtual_... args) -> ReturnType; static BOOST_NORETURN auto fn_ambiguous(detail::remove_virtual_... args) -> ReturnType; + template + struct thunk; + template< auto Overrider, typename OverriderReturn, typename... OverriderParameters> struct thunk { - static auto fn(detail::remove_virtual_... arg) - -> ReturnType; + static auto + fn(detail::remove_virtual_... arg) -> ReturnType; using OverriderVirtualParameters = detail::overrider_virtual_types< DeclaredParameters, mp11::mp_list, Registry>; @@ -2364,9 +2304,6 @@ class method static type_id vp_type_ids[Arity]; }; - template - struct override_aux; - template struct override_aux { override_aux() { @@ -2375,36 +2312,99 @@ class method static override_impl impl; }; + + protected: + template + FunctionPointer resolve(const ArgType&... args) const; + + method_base(); + method_base(const method_base&) = delete; + method_base(method_base&&) = delete; + ~method_base(); +}; + +} // namespace detail + +//! Method with a specific id, signature and return type +//! +//! `method` implements an open-method that takes a parameter list - +//! `Parameters` - and returns a `ReturnType`. +//! +//! `Parameters` must contain at least one virtual parameter, i.e. a parameter +//! that has a type in the form `virtual_ptr` or `virtual\_`. +//! The dynamic types of the virtual arguments are taken into account to select +//! the overrider to call. +//! +//! @see method +//! +//! @tparam Id A type representing the method's name +//! @tparam ReturnType The return type of the method +//! @tparam Parameters The types of the parameters +//! @tparam Registry The registry of the method +template< + typename Id, typename... Parameters, typename ReturnType, class Registry> +class method + : public detail::method_base { + public: + //! Method singleton + //! + //! The only instance of `method`. Its `operator()` is used to call + //! the method. + static method fn; + + // `fn` cannot be `inline static` because of MSVC (19.43) bug causing + // a "no appropriate default constructor available". + + //! Call the method + //! + //! Call the method with `args`. The types of the arguments are the same as + //! the method `Parameters...`, stripped from any `virtual\_` decorators. + //! + //! @param args The arguments for the method call + //! + //! @par Errors + //! + //! If `Registry` contains an @ref error_handler policy, call its `error` + //! function with an object of one of the following types: + //! + //! @li @ref not_implemented: No overrider is applicable. + //! @li @ref ambiguous_call: More than one overrider is applicable, and + //! none is more specialized than all the others. + //! + auto operator()(typename BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS + StripVirtualDecorator::type... args) const + -> ReturnType; }; template< typename Id, typename... Parameters, typename ReturnType, class Registry> template -typename method::FunctionPointer - method::next; +typename detail::method_base:: + FunctionPointer + detail::method_base::next; -// template< -// typename Id, typename... Parameters, typename ReturnType, class Registry> -// method -// method::fn; +template< + typename Id, typename... Parameters, typename ReturnType, class Registry> +method + method::fn; template< typename Id, typename... Parameters, typename ReturnType, class Registry> template -type_id method::override_impl< - Function, FnReturnType>::vp_type_ids[Arity]; +type_id detail::method_base:: + override_impl::vp_type_ids[Arity]; template< typename Id, typename... Parameters, typename ReturnType, class Registry> template -typename method:: +typename detail::method_base:: template override_impl - method::override_aux< - Function, FnReturnType (*)(FnParameters...)>::impl; + detail::method_base:: + override_aux::impl; template< typename Id, typename... Parameters, typename ReturnType, class Registry> -method::method() { +detail::method_base::method_base() { using namespace policies; this->slots_strides_ptr = slots_strides; @@ -2425,9 +2425,11 @@ method::method() { template< typename Id, typename... Parameters, typename ReturnType, class Registry> -void method::resolve_type_ids() { +void detail::method_base:: + resolve_type_ids() { using namespace detail; - this->method_type_id = rtti::template static_type(); + this->method_type_id = + rtti::template static_type>(); this->return_type_id = rtti::template static_type>(); init_type_ids< @@ -2439,7 +2441,7 @@ void method::resolve_type_ids() { template< typename Id, typename... Parameters, typename ReturnType, class Registry> -method::~method() { +detail::method_base::~method_base() { Registry::static_::st.methods.remove(*this); } @@ -2453,19 +2455,18 @@ method::operator()( typename BOOST_OPENMETHOD_DETAIL_UNLESS_MRDOCS StripVirtualDecorator::type... args) const -> ReturnType { using namespace detail; - auto pf = resolve(parameter_traits::peek(args)...); + auto pf = this->resolve(parameter_traits::peek(args)...); - return pf( - std::forward::type>( - args)...); + return pf(std::forward::type>( + args)...); } template< typename Id, typename... Parameters, typename ReturnType, class Registry> template -BOOST_FORCEINLINE - typename method::FunctionPointer - method::resolve( +BOOST_FORCEINLINE typename detail::method_base< + Id, ReturnType(Parameters...), Registry>::FunctionPointer + detail::method_base::resolve( const ArgType&... args) const { using namespace detail; @@ -2488,8 +2489,8 @@ template< typename Id, typename... Parameters, typename ReturnType, class Registry> template BOOST_FORCEINLINE auto -method::vptr(const ArgType& arg) const - -> vptr_type { +detail::method_base::vptr( + const ArgType& arg) const -> vptr_type { if constexpr (detail::is_virtual_ptr) { return arg.vptr(); } else { @@ -2501,9 +2502,9 @@ template< typename Id, typename... Parameters, typename ReturnType, class Registry> template BOOST_FORCEINLINE auto -method::resolve_uni( - const ArgType& arg, const MoreArgTypes&... more_args) const - -> detail::word { +detail::method_base::resolve_uni( + const ArgType& arg, + const MoreArgTypes&... more_args) const -> detail::word { using namespace detail; using namespace policies; @@ -2521,9 +2522,10 @@ template< typename Id, typename... Parameters, typename ReturnType, class Registry> template BOOST_FORCEINLINE auto -method::resolve_multi_first( - const ArgType& arg, const MoreArgTypes&... more_args) const - -> detail::word { +detail::method_base:: + resolve_multi_first( + const ArgType& arg, + const MoreArgTypes&... more_args) const -> detail::word { using namespace detail; using namespace boost::mp11; @@ -2551,9 +2553,10 @@ template< std::size_t VirtualArg, typename MethodArgList, typename ArgType, typename... MoreArgTypes> BOOST_FORCEINLINE auto -method::resolve_multi_next( - vptr_type dispatch, const ArgType& arg, - const MoreArgTypes&... more_args) const -> detail::word { +detail::method_base:: + resolve_multi_next( + vptr_type dispatch, const ArgType& arg, + const MoreArgTypes&... more_args) const -> detail::word { using namespace detail; using namespace boost::mp11; @@ -2580,7 +2583,8 @@ method::resolve_multi_next( template< typename Id, typename... Parameters, typename ReturnType, class Registry> template -inline auto method::has_next() +inline auto +detail::method_base::has_next() -> bool { if (next == fn_not_implemented) { return false; @@ -2596,13 +2600,14 @@ inline auto method::has_next() template< typename Id, typename... Parameters, typename ReturnType, class Registry> BOOST_NORETURN auto -method::fn_not_implemented( - detail::remove_virtual_... args) -> ReturnType { +detail::method_base:: + fn_not_implemented( + detail::remove_virtual_... args) -> ReturnType { using namespace policies; if constexpr (Registry::has_error_handler) { no_overrider error; - detail::init_bad_call::fn( + detail::init_bad_call::fn( error, detail::parameter_traits::peek(args)...); Registry::error_handler::error(error); @@ -2614,13 +2619,13 @@ method::fn_not_implemented( template< typename Id, typename... Parameters, typename ReturnType, class Registry> BOOST_NORETURN auto -method::fn_ambiguous( +detail::method_base::fn_ambiguous( detail::remove_virtual_... args) -> ReturnType { using namespace policies; if constexpr (Registry::has_error_handler) { ambiguous_call error; - detail::init_bad_call::fn( + detail::init_bad_call::fn( error, detail::parameter_traits::peek(args)...); Registry::error_handler::error(error); @@ -2729,7 +2734,7 @@ template< typename Id, typename... Parameters, typename ReturnType, class Registry> template< auto Overrider, typename OverriderReturn, typename... OverriderParameters> -auto method:: +auto detail::method_base:: thunk::fn( detail::remove_virtual_... arg) -> ReturnType { using namespace detail; @@ -2746,7 +2751,7 @@ auto method:: template< typename Id, typename... Parameters, typename ReturnType, class Registry> template -method::override_impl< +detail::method_base::override_impl< Function, FnReturnType>::override_impl(FunctionPointer* p_next) { using namespace detail; @@ -2767,7 +2772,7 @@ method::override_impl< // zero-initalized static variable // coverity[uninit_use] if (overrider_info::method) { - BOOST_ASSERT(overrider_info::method == &method::fn); + BOOST_ASSERT(overrider_info::method == &MethodType::fn); return; } @@ -2779,14 +2784,14 @@ method::override_impl< #pragma GCC diagnostic pop #endif - overrider_info::method = &method::fn; + overrider_info::method = &MethodType::fn; if constexpr (!Registry::has_deferred_static_rtti) { resolve_type_ids(); } this->next = reinterpret_cast( - p_next ? p_next : &method::next); + p_next ? p_next : &MethodType::template next); using Thunk = thunk; this->pf = reinterpret_cast(Thunk::fn); @@ -2794,14 +2799,14 @@ method::override_impl< this->vp_begin = vp_type_ids; this->vp_end = vp_type_ids + Arity; - method::fn.overriders.push_back(*this); + MethodType::fn.overriders.push_back(*this); } template< typename Id, typename... Parameters, typename ReturnType, class Registry> template -void method::override_impl< - Function, FnReturnType>::resolve_type_ids() { +void detail::method_base:: + override_impl::resolve_type_ids() { using namespace detail; this->return_type = Registry::rtti::template static_type< diff --git a/include/boost/openmethod/preamble.hpp b/include/boost/openmethod/preamble.hpp index 96023ad6..3c180f01 100644 --- a/include/boost/openmethod/preamble.hpp +++ b/include/boost/openmethod/preamble.hpp @@ -1091,6 +1091,11 @@ struct declspec : declspec_policy { //! @li `Policy` must contain a `fn` metafunction. //! //! @see @ref policies +namespace detail { +template +class method_base; +} // namespace detail + template class registry : public detail::registry_base { @@ -1122,6 +1127,8 @@ class registry : public detail::registry_base { friend struct detail::use_class_aux; template friend class method; + template + friend class detail::method_base; using static_ = detail::static_st< registry, detail::registry_state>, declspec_guide>;