From 2e09b0fcea8f874f42f48b2950725fc2efbaff1d Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Thu, 26 Feb 2026 18:47:25 -0800 Subject: [PATCH 1/6] Add concurrent module init test for issue #2027 Adds test_concurrent_init (static) and test_concurrent_init_dyn (dynamic) targets that spawn 64 threads x 3 rounds, each calling NEED_ALL_DEFAULT_MODULES + Module::Initialize/Shutdown concurrently. Reproduces the race condition reported in #2027 on Linux/GCC. Passes on Windows/MSVC (TLS implementation differs). --- CMakeLists.txt | 13 +++ tests/threading/test_concurrent_init.cpp | 104 +++++++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 tests/threading/test_concurrent_init.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a06d536f7..9eb619c79b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1019,6 +1019,19 @@ get_target_property(DAS_FMT_LIBS das-fmt LINK_LIBRARIES) # MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/utils/daScript/MacOSXBundleInfo.plist.in) #ENDIF() + # Thread-safety test (issue #2027) — static link + add_executable(test_concurrent_init ${PROJECT_SOURCE_DIR}/tests/threading/test_concurrent_init.cpp) + TARGET_LINK_LIBRARIES(test_concurrent_init libDaScript ${SRC_LIBRARIES} ${DAS_MODULES_LIBS}) + target_include_directories(test_concurrent_init PRIVATE ${NEED_MODULES_PATH}) + SETUP_CPP11(test_concurrent_init) + + # Thread-safety test (issue #2027) — dynamic link (DLL) + add_executable(test_concurrent_init_dyn ${PROJECT_SOURCE_DIR}/tests/threading/test_concurrent_init.cpp) + TARGET_LINK_LIBRARIES(test_concurrent_init_dyn libDaScriptDyn ${SRC_LIBRARIES}) + ADD_DEPENDENCIES(test_concurrent_init_dyn libDaScriptDyn ${DAS_DYN_MODULES_LIBS}) + target_include_directories(test_concurrent_init_dyn PRIVATE ${NEED_MODULES_PATH}) + SETUP_CPP11(test_concurrent_init_dyn) + endif() # This list should be significantly reduced, most of the files, except aot related should be private. diff --git a/tests/threading/test_concurrent_init.cpp b/tests/threading/test_concurrent_init.cpp new file mode 100644 index 0000000000..914e9a361a --- /dev/null +++ b/tests/threading/test_concurrent_init.cpp @@ -0,0 +1,104 @@ +// Test for issue #2027: Race condition in concurrent module registration. +// +// Multiple threads call NEED_ALL_DEFAULT_MODULES + Module::Initialize() +// simultaneously, which triggers SIGSEGV / access violations due to +// unprotected shared state inside module constructors. +// +// Build: cmake --build build --config Release --target test_concurrent_init +// Run: bin/Release/test_concurrent_init.exe + +#include "daScript/daScript.h" +#include "daScript/misc/platform.h" + +#include +#include +#include +#include +#include +#include + +using namespace das; + +// ── helpers ──────────────────────────────────────────────────────────── +static std::mutex coutMtx; + +template +static void log(Args&&... args) { + std::lock_guard lk(coutMtx); + (std::cout << ... << std::forward(args)) << "\n" << std::flush; +} + +// ── worker ───────────────────────────────────────────────────────────── +// Register every module that daslang_static uses — the same set the real +// compiler loads. This maximises the chance of hitting shared-state races +// inside module constructors. +static void workerThread(int id, std::atomic& ok, std::atomic& fail) { + try { + // Use the same macro as the issue #2027 reporter's repro case + NEED_ALL_DEFAULT_MODULES; + NEED_MODULE(Module_UriParser); + NEED_MODULE(Module_JobQue); + Module::Initialize(); + + // Quick sanity: make sure the "$" (builtin) module exists. + auto *m = Module::require("$"); + if (!m) { + log("[Thread ", id, "] FAIL – builtin module not found"); + ++fail; + } else { + ++ok; + } + + Module::Shutdown(); + } catch (const std::exception& e) { + log("[Thread ", id, "] EXCEPTION: ", e.what()); + ++fail; + } catch (...) { + log("[Thread ", id, "] UNKNOWN EXCEPTION"); + ++fail; + } +} + +// ── main ─────────────────────────────────────────────────────────────── +int main() { + // ── Test 1: sequential (baseline) ────────────────────────────────── + { + log("=== Test 1: sequential init (10 iterations) ==="); + std::atomic ok{0}, fail{0}; + for (int i = 0; i < 10; ++i) { + workerThread(i, ok, fail); + } + log("sequential: ", ok.load(), " ok, ", fail.load(), " fail"); + if (fail.load() != 0) { + log("FAILED (sequential should never fail)"); + return 1; + } + log("=== Test 1 PASSED ===\n"); + } + + // ── Test 2: concurrent (should crash without the fix) ────────────── + for (int round = 0; round < 3; ++round) { + constexpr int N = 64; + log("=== Test 2 round ", round, ": concurrent init (", N, " threads) ==="); + std::atomic ok{0}, fail{0}; + std::vector threads; + threads.reserve(N); + + for (int i = 0; i < N; ++i) { + threads.emplace_back(workerThread, 1000 + round * N + i, std::ref(ok), std::ref(fail)); + } + for (auto& t : threads) { + t.join(); + } + + log("concurrent: ", ok.load(), " ok, ", fail.load(), " fail"); + if (fail.load() != 0) { + log("FAILED at round ", round); + return 1; + } + log("=== Test 2 round ", round, " PASSED ===\n"); + } + + log("\nAll tests passed."); + return 0; +} From 9bd9aa9afcd9966805b13b5433e61be30f70691d Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Thu, 26 Feb 2026 19:46:18 -0800 Subject: [PATCH 2/6] Fix dasClangBind module for both static and dynamic builds - CMakeLists.txt: Replace ADD_DAS_STATIC_MODULE_LIB with ADD_MODULE_LIB to create both static (libDasModuleClangBind) and shared (dasModuleClangBind) targets. Add link libraries, include directories, and SETUP_CPP11 for the shared target. - dasClangBind.cpp: Add REGISTER_DYN_MODULE macro to export the dynamic module registrator function (register_dyn_Module_dasClangBind) needed by the dynamic loading system. - .das_module: New descriptor file that registers the shared module DLL and native .das path for dynamic builds. --- modules/dasClangBind/.das_module | 10 ++++++++++ modules/dasClangBind/CMakeLists.txt | 6 +++++- modules/dasClangBind/src/dasClangBind.cpp | 3 +++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 modules/dasClangBind/.das_module diff --git a/modules/dasClangBind/.das_module b/modules/dasClangBind/.das_module new file mode 100644 index 0000000000..12ff7d651c --- /dev/null +++ b/modules/dasClangBind/.das_module @@ -0,0 +1,10 @@ +options gen2 +require fio + +[export] +def initialize(project_path : string) { + if (das_is_dll_build()) { + register_dynamic_module("{project_path}/dasModuleClangBind.shared_module", "Module_dasClangBind") + } + register_native_path("cbind", "cbind_boost", "{project_path}/cbind/cbind_boost.das") +} diff --git a/modules/dasClangBind/CMakeLists.txt b/modules/dasClangBind/CMakeLists.txt index 77e273c7eb..37ffd0d848 100644 --- a/modules/dasClangBind/CMakeLists.txt +++ b/modules/dasClangBind/CMakeLists.txt @@ -55,14 +55,17 @@ IF ((NOT DAS_CLANG_BIND_INCLUDED) AND (${Clang_FOUND}) AND ((NOT ${DAS_CLANG_BIN ADD_MODULE_CPP(dasClangBind) # ADD_MODULE_NATIVE(CLANG_BIND_boost) - ADD_DAS_STATIC_MODULE_LIB(libDasModuleClangBind cbind_mod ${DAS_CLANG_BIND_MODULE_SRC} ${DAS_CLANG_BIND_MODULE_PLATFORM_SRC}) + ADD_MODULE_LIB(libDasModuleClangBind dasModuleClangBind ${DAS_CLANG_BIND_MODULE_SRC} ${DAS_CLANG_BIND_MODULE_PLATFORM_SRC}) IF(APPLE) TARGET_LINK_LIBRARIES(libDasModuleClangBind PRIVATE ${CLANG_BIND_LIBRARIES} ${LIBCLANG_LIB}/libclang.dylib) + TARGET_LINK_LIBRARIES(dasModuleClangBind PRIVATE ${CLANG_BIND_LIBRARIES} ${LIBCLANG_LIB}/libclang.dylib) ELSE() TARGET_LINK_LIBRARIES(libDasModuleClangBind PRIVATE ${CLANG_BIND_LIBRARIES} libclang) + TARGET_LINK_LIBRARIES(dasModuleClangBind PRIVATE ${CLANG_BIND_LIBRARIES} libclang) ENDIF() # ADD_DEPENDENCIES(libDasModuleClangBind) TARGET_INCLUDE_DIRECTORIES(libDasModuleClangBind PUBLIC ${CLANG_BIND_INCLUDE_DIR} ${CLANG_INCLUDE_DIRS} ${LLVM_INCLUDE_DIRS}) + TARGET_INCLUDE_DIRECTORIES(dasModuleClangBind PUBLIC ${CLANG_BIND_INCLUDE_DIR} ${CLANG_INCLUDE_DIRS} ${LLVM_INCLUDE_DIRS}) IF(WIN32) set(DAS_CLANG_DLLS @@ -88,4 +91,5 @@ ENDIF() ADD_MODULE_DAS(cbind cbind cbind_boost) SETUP_CPP11(libDasModuleClangBind) + SETUP_CPP11(dasModuleClangBind) ENDIF() diff --git a/modules/dasClangBind/src/dasClangBind.cpp b/modules/dasClangBind/src/dasClangBind.cpp index 872107ea70..b344fe1c26 100644 --- a/modules/dasClangBind/src/dasClangBind.cpp +++ b/modules/dasClangBind/src/dasClangBind.cpp @@ -30,6 +30,9 @@ bool Module_dasClangBind::initDependencies() { initMain(); return true; } + +REGISTER_DYN_MODULE(Module_dasClangBind,Module_dasClangBind); + } REGISTER_MODULE_IN_NAMESPACE(Module_dasClangBind,das); From 2ccd5f5e8242da8701a19fda9ec97f118cfcc9aa Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Thu, 26 Feb 2026 20:22:41 -0800 Subject: [PATCH 3/6] Add namespace-safe module macros (DECLARE_MODULE / PULL_MODULE) Resolves #2024 NEED_MODULE / NEED_ALL_DEFAULT_MODULES cannot be used inside C++ namespaces because the embedded extern declaration binds to the enclosing namespace scope. New macros (additive, existing macros unchanged): - DECLARE_MODULE / DECLARE_ALL_DEFAULT_MODULES file-scope forward decl - PULL_MODULE / PULL_ALL_DEFAULT_MODULES namespace-safe registration CMake now generates external_declare.inc and external_pull.inc alongside external_need.inc for plugin modules. Includes tutorial 22 (namespace integration), RST docs, and embedding doc updates. --- CMakeLists.txt | 24 ++- doc/source/reference/embedding/cpp_api.rst | 10 ++ doc/source/reference/tutorials.rst | 1 + ...tegration_cpp_22_namespace_integration.rst | 166 ++++++++++++++++++ include/daScript/daScriptModule.h | 50 ++++++ skills/cpp_integration.md | 6 + .../cpp/22_namespace_integration.cpp | 96 ++++++++++ tutorials/integration/cpp/CMakeLists.txt | 7 + web/CMakeLists.txt | 22 ++- 9 files changed, 380 insertions(+), 2 deletions(-) create mode 100644 doc/source/reference/tutorials/integration_cpp_22_namespace_integration.rst create mode 100644 tutorials/integration/cpp/22_namespace_integration.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 9a06d536f7..86b1b49948 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -284,8 +284,12 @@ SET(NEED_MODULES_PATH "${CMAKE_CURRENT_BINARY_DIR}/include") SET(DAS_MODULES_RESOLVE_INC ${NEED_MODULES_PATH}/modules/external_resolve.inc) SET(DAS_MODULES_NEED_INC ${NEED_MODULES_PATH}/modules/external_need.inc) +SET(DAS_MODULES_DECLARE_INC ${NEED_MODULES_PATH}/modules/external_declare.inc) +SET(DAS_MODULES_PULL_INC ${NEED_MODULES_PATH}/modules/external_pull.inc) FILE(WRITE ${DAS_MODULES_RESOLVE_INC}.temp "") FILE(WRITE ${DAS_MODULES_NEED_INC}.temp "") +FILE(WRITE ${DAS_MODULES_DECLARE_INC}.temp "") +FILE(WRITE ${DAS_MODULES_PULL_INC}.temp "") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}) set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/lib) @@ -381,6 +385,8 @@ ENDMACRO() FUNCTION(ADD_MODULE_CPP cpp) FILE(APPEND ${DAS_MODULES_NEED_INC}.temp "NEED_MODULE(Module_${cpp});\n") + FILE(APPEND ${DAS_MODULES_DECLARE_INC}.temp "DECLARE_MODULE(Module_${cpp});\n") + FILE(APPEND ${DAS_MODULES_PULL_INC}.temp "PULL_MODULE(Module_${cpp});\n") ENDFUNCTION() FUNCTION(ADD_MODULE_NATIVE native) @@ -428,7 +434,21 @@ ADD_CUSTOM_COMMAND( COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DAS_MODULES_RESOLVE_INC}.temp ${DAS_MODULES_RESOLVE_INC} ) -ADD_CUSTOM_TARGET(need_and_resolve ALL DEPENDS ${DAS_MODULES_NEED_INC} ${DAS_MODULES_RESOLVE_INC}) +ADD_CUSTOM_COMMAND( + DEPENDS ${DAS_MODULES_DECLARE_INC}.temp + OUTPUT ${DAS_MODULES_DECLARE_INC} + VERBATIM + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DAS_MODULES_DECLARE_INC}.temp ${DAS_MODULES_DECLARE_INC} +) + +ADD_CUSTOM_COMMAND( + DEPENDS ${DAS_MODULES_PULL_INC}.temp + OUTPUT ${DAS_MODULES_PULL_INC} + VERBATIM + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DAS_MODULES_PULL_INC}.temp ${DAS_MODULES_PULL_INC} +) + +ADD_CUSTOM_TARGET(need_and_resolve ALL DEPENDS ${DAS_MODULES_NEED_INC} ${DAS_MODULES_RESOLVE_INC} ${DAS_MODULES_DECLARE_INC} ${DAS_MODULES_PULL_INC}) # libUriParser @@ -923,6 +943,8 @@ endif() SET(DAS_DASCRIPT_MAIN_SRC ${PROJECT_SOURCE_DIR}/utils/daScript/main.cpp ${DAS_MODULES_NEED_INC} + ${DAS_MODULES_DECLARE_INC} + ${DAS_MODULES_PULL_INC} CACHE INTERNAL "DAS_DASCRIPT_MAIN_SRC" ) diff --git a/doc/source/reference/embedding/cpp_api.rst b/doc/source/reference/embedding/cpp_api.rst index 411db75d82..0a1f193222 100644 --- a/doc/source/reference/embedding/cpp_api.rst +++ b/doc/source/reference/embedding/cpp_api.rst @@ -43,6 +43,16 @@ Derive from ``Module``, register bindings in the constructor, and use The host uses ``NEED_MODULE(Module_MyMod)`` before ``Module::Initialize()``. Scripts access it with ``require my_module_name``. +.. note:: + + ``NEED_MODULE`` contains an ``extern`` declaration that binds to the + enclosing namespace. If your initialization code lives inside a C++ + namespace, use the namespace-safe pair ``DECLARE_MODULE`` (file scope) + and ``PULL_MODULE`` (inside the namespace) instead. For external + modules, CMake also generates ``external_declare.inc`` and + ``external_pull.inc`` alongside the traditional ``external_need.inc``. + See :ref:`tutorial_integration_cpp_namespace_integration`. + See :ref:`tutorial_integration_cpp_custom_modules` for a complete example. diff --git a/doc/source/reference/tutorials.rst b/doc/source/reference/tutorials.rst index 8beee941e8..4d191f8bf2 100644 --- a/doc/source/reference/tutorials.rst +++ b/doc/source/reference/tutorials.rst @@ -135,6 +135,7 @@ and a companion ``.das`` script in ``tutorials/integration/cpp/``. tutorials/integration_cpp_19_class_adapters.rst tutorials/integration_cpp_20_standalone_contexts.rst tutorials/integration_cpp_21_threading.rst + tutorials/integration_cpp_22_namespace_integration.rst .. _tutorials_macros: diff --git a/doc/source/reference/tutorials/integration_cpp_22_namespace_integration.rst b/doc/source/reference/tutorials/integration_cpp_22_namespace_integration.rst new file mode 100644 index 0000000000..5eeea04e70 --- /dev/null +++ b/doc/source/reference/tutorials/integration_cpp_22_namespace_integration.rst @@ -0,0 +1,166 @@ +.. _tutorial_integration_cpp_namespace_integration: + +.. index:: + single: Tutorial; C++ Integration; Namespace Integration + +========================================== + C++ Integration: Namespace Integration +========================================== + +This tutorial shows how to initialize daslang modules when your code lives +inside a C++ namespace. + +Topics covered: + +* Why ``NEED_MODULE`` fails inside a namespace +* ``DECLARE_MODULE`` / ``PULL_MODULE`` — the namespace-safe alternative +* ``DECLARE_ALL_DEFAULT_MODULES`` / ``PULL_ALL_DEFAULT_MODULES`` + + +Prerequisites +============= + +* Tutorial 1 completed (:ref:`tutorial_integration_cpp_hello_world`) — + basic compile → simulate → eval cycle. + + +The problem +=========== + +The ``NEED_MODULE`` macro expands to an ``extern`` declaration followed +by a call: + +.. code-block:: cpp + + extern DAS_API das::Module * register_Module_BuiltIn(); + *das::ModuleKarma += unsigned(intptr_t(register_Module_BuiltIn())); + +When this macro is used inside a C++ namespace, the ``extern`` +declaration is placed in that namespace's scope. The linker then looks +for ``MyApp::register_Module_BuiltIn()`` instead of the global +``::register_Module_BuiltIn()`` defined in the library: + +.. code-block:: cpp + + namespace MyApp { + void init() { + NEED_ALL_DEFAULT_MODULES; // ❌ linker error! + das::Module::Initialize(); + } + } + +The solution is to split the declaration and the call into two separate +macros. + + +``DECLARE_MODULE`` / ``PULL_MODULE`` +==================================== + +``DECLARE_MODULE(ClassName)`` — forward-declares the global-scope +``register_*`` function. Must be placed at file or global scope (outside +any namespace). + +``PULL_MODULE(ClassName)`` — performs the registration call using the +``::`` prefix to explicitly reference the global-scope function. Safe +inside any namespace, class, or function body. + +Convenience wrappers ``DECLARE_ALL_DEFAULT_MODULES`` and +``PULL_ALL_DEFAULT_MODULES`` cover every built-in module. + + +The tutorial +============ + +The tutorial runs the same ``01_hello_world.das`` script as Tutorial 1, +but all daslang calls happen inside ``namespace MyApp``. + +.. literalinclude:: ../../../../tutorials/integration/cpp/22_namespace_integration.cpp + :language: cpp + :caption: tutorials/integration/cpp/22_namespace_integration.cpp + + +How it works +============ + +1. ``DECLARE_ALL_DEFAULT_MODULES`` at file scope forward-declares every + default module's ``register_*`` function as a global-scope symbol. + +2. Inside ``MyApp::initialize()``, ``PULL_ALL_DEFAULT_MODULES`` calls + each ``::register_Module_*()`` function — the ``::`` prefix bypasses + the enclosing namespace. + +3. ``Module::Initialize()`` and the rest of the daslang API continue to + work normally inside the namespace — only module registration has the + namespace restriction. + + +Custom modules +============== + +For custom modules, use ``DECLARE_MODULE`` at file scope alongside the +convenience macros: + +.. code-block:: cpp + + DECLARE_ALL_DEFAULT_MODULES; + DECLARE_MODULE(Module_MyMod); + + namespace MyApp { + void init() { + PULL_ALL_DEFAULT_MODULES; + PULL_MODULE(Module_MyMod); + das::Module::Initialize(); + } + } + + +External (plugin) modules +========================= + +CMake generates three ``.inc`` files for every module registered with +``ADD_MODULE_CPP``: + +``external_need.inc`` + Contains ``NEED_MODULE(...)`` — the traditional all-in-one macro + (works only at global scope). + +``external_declare.inc`` + Contains ``DECLARE_MODULE(...)`` — include at file scope. + +``external_pull.inc`` + Contains ``PULL_MODULE(...)`` — include inside any namespace or + function body. + +For namespace-safe code, replace: + +.. code-block:: cpp + + // old (global scope only) + #include "modules/external_need.inc" + +with: + +.. code-block:: cpp + + // file scope + #include "modules/external_declare.inc" + + namespace MyApp { + void init() { + // inside namespace + #include "modules/external_pull.inc" + } + } + + +Build and run +============= + +.. code-block:: bash + + cmake --build build --config Release --target integration_cpp_22 + +.. code-block:: + + $ bin/Release/integration_cpp_22 + Hello, World! diff --git a/include/daScript/daScriptModule.h b/include/daScript/daScriptModule.h index 9e88eca4b3..35736b2498 100644 --- a/include/daScript/daScriptModule.h +++ b/include/daScript/daScriptModule.h @@ -24,3 +24,53 @@ namespace das NEED_MODULE(Module_DASBIND); \ NEED_MODULE(Module_Network); +// DECLARE_MODULE / PULL_MODULE — namespace-safe alternatives to NEED_MODULE. +// +// NEED_MODULE places an `extern` declaration at the current scope, which +// fails inside a C++ namespace (the linker looks for Namespace::register_… +// instead of the global-scope function). +// +// Use DECLARE_MODULE at global/file scope to forward-declare the register +// function, then PULL_MODULE inside any namespace or function body to call it. +// +// Example: +// DECLARE_ALL_DEFAULT_MODULES; // file scope +// namespace MyApp { +// void init() { +// PULL_ALL_DEFAULT_MODULES; // OK — works inside namespace +// das::Module::Initialize(); +// } +// } + +#define DECLARE_MODULE(ClassName) \ + extern DAS_API das::Module * register_##ClassName () + +#define PULL_MODULE(ClassName) \ + *das::ModuleKarma += unsigned(intptr_t(::register_##ClassName())) + +#define DECLARE_ALL_DEFAULT_MODULES \ + DECLARE_MODULE(Module_BuiltIn); \ + DECLARE_MODULE(Module_Math); \ + DECLARE_MODULE(Module_Raster); \ + DECLARE_MODULE(Module_Strings); \ + DECLARE_MODULE(Module_Rtti); \ + DECLARE_MODULE(Module_Ast); \ + DECLARE_MODULE(Module_Debugger); \ + DECLARE_MODULE(Module_Jit); \ + DECLARE_MODULE(Module_FIO); \ + DECLARE_MODULE(Module_DASBIND); \ + DECLARE_MODULE(Module_Network) + +#define PULL_ALL_DEFAULT_MODULES \ + PULL_MODULE(Module_BuiltIn); \ + PULL_MODULE(Module_Math); \ + PULL_MODULE(Module_Raster); \ + PULL_MODULE(Module_Strings); \ + PULL_MODULE(Module_Rtti); \ + PULL_MODULE(Module_Ast); \ + PULL_MODULE(Module_Debugger); \ + PULL_MODULE(Module_Jit); \ + PULL_MODULE(Module_FIO); \ + PULL_MODULE(Module_DASBIND); \ + PULL_MODULE(Module_Network) + diff --git a/skills/cpp_integration.md b/skills/cpp_integration.md index 8e85674650..03df0a2bc1 100644 --- a/skills/cpp_integration.md +++ b/skills/cpp_integration.md @@ -51,6 +51,12 @@ REGISTER_MODULE(Module_MyMod); The host uses `NEED_MODULE(Module_MyMod)` before `Module::Initialize()`. Scripts access it via `require my_module_name`. +> **Namespace caveat**: `NEED_MODULE` contains an `extern` declaration that binds to the enclosing C++ namespace. If your initialization code lives inside a namespace, use the namespace-safe pair instead: +> - `DECLARE_MODULE(ClassName)` / `DECLARE_ALL_DEFAULT_MODULES` — at file scope (outside namespaces) +> - `PULL_MODULE(ClassName)` / `PULL_ALL_DEFAULT_MODULES` — inside the namespace +> +> See `tutorials/integration/cpp/22_namespace_integration.cpp` for a working example. + > **Dynamic binary support**: C++ modules also need a `.das_module` descriptor for the dynamic binary (`daslang`). See `skills/dynamic_modules.md` for the full pattern — you'll need `register_dynamic_module` to load the `.shared_module` DLL. ## Callbacks — `TBlock<>`, `TFunc<>`, `TLambda<>`, `das_invoke*` diff --git a/tutorials/integration/cpp/22_namespace_integration.cpp b/tutorials/integration/cpp/22_namespace_integration.cpp new file mode 100644 index 0000000000..8fdf7307ed --- /dev/null +++ b/tutorials/integration/cpp/22_namespace_integration.cpp @@ -0,0 +1,96 @@ +// Tutorial 22 — Namespace Integration +// +// Demonstrates how to initialize daslang modules inside a C++ namespace. +// +// The standard NEED_MODULE / NEED_ALL_DEFAULT_MODULES macros place an +// `extern` declaration at the current scope. When that scope is a C++ +// namespace, the linker looks for MyNamespace::register_Module_BuiltIn() +// instead of the global ::register_Module_BuiltIn(), causing link errors. +// +// The solution is the DECLARE_MODULE / PULL_MODULE macro pair: +// • DECLARE_MODULE — forward-declares the register function (must be at +// file/global scope so it refers to the global symbol) +// • PULL_MODULE — calls the register function using ::qualified name +// (safe inside any namespace, class, or function body) +// +// Convenience wrappers DECLARE_ALL_DEFAULT_MODULES and PULL_ALL_DEFAULT_MODULES +// cover all built-in modules at once. + +#include "daScript/daScript.h" + +using namespace das; + +#define SCRIPT_NAME "/tutorials/integration/cpp/01_hello_world.das" + +// Step 1: Forward-declare module functions at global/file scope. +// This ensures the extern declarations refer to the global-scope symbols +// defined by REGISTER_MODULE / REGISTER_MODULE_IN_NAMESPACE in the library. +DECLARE_ALL_DEFAULT_MODULES; + +// For external (plugin) modules, include the generated file: +// #include "modules/external_declare.inc" +// (mirrors external_need.inc but uses DECLARE_MODULE instead of NEED_MODULE) + +// ------------------------------------------------------------------ +// Everything below lives inside a namespace — PULL_MODULE works here. +// ------------------------------------------------------------------ +namespace MyApp { + +void runScript() { + TextPrinter tout; + ModuleGroup dummyLibGroup; + auto fAccess = make_smart(); + auto program = compileDaScript(getDasRoot() + SCRIPT_NAME, + fAccess, tout, dummyLibGroup); + if (program->failed()) { + tout << "Compilation failed:\n"; + for (auto & err : program->errors) { + tout << reportError(err.at, err.what, err.extra, err.fixme, err.cerr); + } + return; + } + Context ctx(program->getContextStackSize()); + if (!program->simulate(ctx, tout)) { + tout << "Simulation failed:\n"; + for (auto & err : program->errors) { + tout << reportError(err.at, err.what, err.extra, err.fixme, err.cerr); + } + return; + } + auto fnTest = ctx.findFunction("test"); + if (!fnTest) { + tout << "Function 'test' not found\n"; + return; + } + ctx.evalWithCatch(fnTest, nullptr); + if (auto ex = ctx.getException()) { + tout << "Script exception: " << ex << "\n"; + } +} + +void initialize() { + // Step 2: Pull (register) the modules. PULL_ALL_DEFAULT_MODULES uses + // ::register_Module_*() — the :: prefix ensures the call resolves to + // the global-scope function regardless of the enclosing namespace. + PULL_ALL_DEFAULT_MODULES; + + // For external (plugin) modules, include the generated file: + // #include "modules/external_pull.inc" + // (mirrors external_need.inc but uses PULL_MODULE instead of NEED_MODULE) + + Module::Initialize(); +} + +void shutdown() { + Module::Shutdown(); +} + +} // namespace MyApp + +// main() delegates to the namespaced functions. +int main(int, char * []) { + MyApp::initialize(); + MyApp::runScript(); + MyApp::shutdown(); + return 0; +} diff --git a/tutorials/integration/cpp/CMakeLists.txt b/tutorials/integration/cpp/CMakeLists.txt index 10e45ed636..1d100ba0d2 100644 --- a/tutorials/integration/cpp/CMakeLists.txt +++ b/tutorials/integration/cpp/CMakeLists.txt @@ -191,6 +191,13 @@ DAS_CPP_INTEGRATION_TUTORIAL(integration_cpp_21 ${CMAKE_CURRENT_SOURCE_DIR}/tutorials/integration/cpp/21_threading.cpp ) +################################# +# Tutorial 22 — Namespace Integration +################################# +DAS_CPP_INTEGRATION_TUTORIAL(integration_cpp_22 + ${CMAKE_CURRENT_SOURCE_DIR}/tutorials/integration/cpp/22_namespace_integration.cpp +) + # Install C++ integration tutorial source files file(GLOB CPP_INTEGRATION_TUTORIAL_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/tutorials/integration/cpp/*.cpp diff --git a/web/CMakeLists.txt b/web/CMakeLists.txt index 0e7af9c06d..d84e4ec7bf 100644 --- a/web/CMakeLists.txt +++ b/web/CMakeLists.txt @@ -160,8 +160,12 @@ ENDIF() SET(DAS_MODULES_RESOLVE_INC ${PROJECT_SOURCE_DIR}/../include/modules/external_resolve.inc) SET(DAS_MODULES_NEED_INC ${PROJECT_SOURCE_DIR}/../include/modules/external_need.inc) +SET(DAS_MODULES_DECLARE_INC ${PROJECT_SOURCE_DIR}/../include/modules/external_declare.inc) +SET(DAS_MODULES_PULL_INC ${PROJECT_SOURCE_DIR}/../include/modules/external_pull.inc) FILE(WRITE ${DAS_MODULES_RESOLVE_INC}.temp "") FILE(WRITE ${DAS_MODULES_NEED_INC}.temp "") +FILE(WRITE ${DAS_MODULES_DECLARE_INC}.temp "") +FILE(WRITE ${DAS_MODULES_PULL_INC}.temp "") SET(DAS_MODULES_LIBS) SET(DAS_ALL_EXAMPLES) @@ -179,6 +183,8 @@ FOREACH(_module ${_modules}) FUNCTION(ADD_MODULE_CPP cpp) FILE(APPEND ${DAS_MODULES_NEED_INC}.temp "NEED_MODULE(Module_${cpp});\n") + FILE(APPEND ${DAS_MODULES_DECLARE_INC}.temp "DECLARE_MODULE(Module_${cpp});\n") + FILE(APPEND ${DAS_MODULES_PULL_INC}.temp "PULL_MODULE(Module_${cpp});\n") ENDFUNCTION() FUNCTION(ADD_MODULE_NATIVE native) @@ -213,7 +219,21 @@ ADD_CUSTOM_COMMAND( COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DAS_MODULES_RESOLVE_INC}.temp ${DAS_MODULES_RESOLVE_INC} ) -ADD_CUSTOM_TARGET(need_and_resolve ALL DEPENDS ${DAS_MODULES_NEED_INC} ${DAS_MODULES_RESOLVE_INC}) +ADD_CUSTOM_COMMAND( + DEPENDS ${DAS_MODULES_DECLARE_INC}.temp + OUTPUT ${DAS_MODULES_DECLARE_INC} + VERBATIM + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DAS_MODULES_DECLARE_INC}.temp ${DAS_MODULES_DECLARE_INC} +) + +ADD_CUSTOM_COMMAND( + DEPENDS ${DAS_MODULES_PULL_INC}.temp + OUTPUT ${DAS_MODULES_PULL_INC} + VERBATIM + COMMAND ${CMAKE_COMMAND} -E copy_if_different ${DAS_MODULES_PULL_INC}.temp ${DAS_MODULES_PULL_INC} +) + +ADD_CUSTOM_TARGET(need_and_resolve ALL DEPENDS ${DAS_MODULES_NEED_INC} ${DAS_MODULES_RESOLVE_INC} ${DAS_MODULES_DECLARE_INC} ${DAS_MODULES_PULL_INC}) # libUriParser From 1fc159cb3dfc25ad4e023bb6daab78afbf08c78b Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Thu, 26 Feb 2026 20:27:53 -0800 Subject: [PATCH 4/6] Fix doc build: replace unicode emoji with ASCII in RST code block --- .../tutorials/integration_cpp_22_namespace_integration.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/reference/tutorials/integration_cpp_22_namespace_integration.rst b/doc/source/reference/tutorials/integration_cpp_22_namespace_integration.rst index 5eeea04e70..f1a13fcc03 100644 --- a/doc/source/reference/tutorials/integration_cpp_22_namespace_integration.rst +++ b/doc/source/reference/tutorials/integration_cpp_22_namespace_integration.rst @@ -44,7 +44,7 @@ for ``MyApp::register_Module_BuiltIn()`` instead of the global namespace MyApp { void init() { - NEED_ALL_DEFAULT_MODULES; // ❌ linker error! + NEED_ALL_DEFAULT_MODULES; // FAILS: linker error! das::Module::Initialize(); } } From 810e047aec5bf22fbf9f2d4fb47feb5d4f180862 Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Thu, 26 Feb 2026 23:06:20 -0800 Subject: [PATCH 5/6] Fix release CI: remove defunct daslang_dyn target, fix dasHV build config - release.yml: Remove 'daslang_dyn' target (renamed to 'daslang'). Building 'daslang' already pulls in all shared modules via ADD_DEPENDENCIES. - dasHV: Pass --config to ExternalProject build/install steps to fix Debug/Release CRT mismatch on multi-config generators. --- .github/workflows/release.yml | 5 ++--- modules/dasHV | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4a5a194efb..50ba23f1a7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -207,7 +207,7 @@ jobs: cmake --no-warn-unused-cli -B./build -DCMAKE_BUILD_TYPE:STRING=${{ matrix.cmake_preset }} \ -G "${{ matrix.cmake_generator }}" $ACTIVE_MODULES cd build - ninja daslang daslang_dyn + ninja daslang ;; *) export CXXFLAGS="-Wno-elaborated-enum-base" # glfw module fails due to mac-os includes @@ -217,7 +217,7 @@ jobs: -G "${{ matrix.cmake_generator }}" $ACTIVE_MODULES cd build - ninja daslang daslang_dyn + ninja daslang ;; esac ;; @@ -235,7 +235,6 @@ jobs: cmake --no-warn-unused-cli -B./build -G "${{ matrix.cmake_generator }}" -T host=${{ matrix.architecture == 32 && 'x86' || 'x64' }} -A ${{ matrix.architecture_string }} \ $ACTIVE_MODULES -DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" cmake --build ./build --config ${{ matrix.cmake_preset }} --target daslang - cmake --build ./build --config ${{ matrix.cmake_preset }} --target daslang_dyn ;; esac diff --git a/modules/dasHV b/modules/dasHV index a494ce1584..6f4eb74472 160000 --- a/modules/dasHV +++ b/modules/dasHV @@ -1 +1 @@ -Subproject commit a494ce1584f86bfdde6d67a63a790cefb6f844a6 +Subproject commit 6f4eb7447268bb81347404ed3aa44e5d8ce71f0c From a9a77868bd44dde2b1d82aa77f5cc616dc9105fe Mon Sep 17 00:00:00 2001 From: Boris Batkin Date: Fri, 27 Feb 2026 00:00:38 -0800 Subject: [PATCH 6/6] fix(release): build both daslang and daslang_static targets in CI The release install step requires static module libraries (e.g., libDasModuleAudio.lib) which are only built as dependencies of daslang_static. Previously only the daslang target was built, which only builds shared modules. Now both targets are built so cmake --install can find all required files. --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 50ba23f1a7..6162ca5003 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -207,7 +207,7 @@ jobs: cmake --no-warn-unused-cli -B./build -DCMAKE_BUILD_TYPE:STRING=${{ matrix.cmake_preset }} \ -G "${{ matrix.cmake_generator }}" $ACTIVE_MODULES cd build - ninja daslang + ninja daslang daslang_static ;; *) export CXXFLAGS="-Wno-elaborated-enum-base" # glfw module fails due to mac-os includes @@ -217,7 +217,7 @@ jobs: -G "${{ matrix.cmake_generator }}" $ACTIVE_MODULES cd build - ninja daslang + ninja daslang daslang_static ;; esac ;; @@ -234,7 +234,7 @@ jobs: cmake) cmake --no-warn-unused-cli -B./build -G "${{ matrix.cmake_generator }}" -T host=${{ matrix.architecture == 32 && 'x86' || 'x64' }} -A ${{ matrix.architecture_string }} \ $ACTIVE_MODULES -DCMAKE_TOOLCHAIN_FILE="$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake" - cmake --build ./build --config ${{ matrix.cmake_preset }} --target daslang + cmake --build ./build --config ${{ matrix.cmake_preset }} --target daslang --target daslang_static ;; esac