From 04072e58799149f171ffc54db368341563fc3738 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 15 Jun 2026 19:59:38 +0000 Subject: [PATCH 1/2] Auto-preload box2dxt.so from the engine folder on Linux Builds on the merged loader-assist (b2LoadNativeLib): the extension now locates the running engine's own folder (realpath /proc/self/exe), strips to its directory, and preloads /box2dxt.so by absolute path before any c:box2dxt> binding resolves -- run from the first-call gateways (b2Version, checkABI). The engine's later bare-name load then finds the already-resident copy, so a user just drops box2dxt.so beside the OXT engine (or bundles it in a standalone) -- no /usr/lib, no sudo, no LD_LIBRARY_PATH, no script calls. Off Linux realpath('/proc/self/exe') returns nothing and _ensureLib no-ops, so Windows/macOS keep loading the library from beside the stack/app as before. Requires the library soname to be box2dxt.so (build -DBOX2DXT_BARE_SONAME=ON). No new C ABI symbol; b2Version() stays 4. Verified locally: bare-soname build still emits box2dxt.so with soname box2dxt.so; .livecodescript gates pass. The .lcb cannot be compiled headless -- needs an OXT load to confirm the realpath/dlopen bindings resolve and the engine reuses the preloaded copy. https://claude.ai/code/session_01EVV5fGeXqtWs8WDrmgWNku --- src/box2dxt.lcb | 53 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/src/box2dxt.lcb b/src/box2dxt.lcb index 54a2b6a..43099fe 100644 --- a/src/box2dxt.lcb +++ b/src/box2dxt.lcb @@ -442,8 +442,8 @@ private foreign handler _query_normal_y(in pI as CInt) returns CDouble binds to private foreign handler _query_fraction(in pI as CInt) returns CDouble binds to "c:box2dxt>b2lc_query_fraction!cdecl" -- ---- native loader assist (Linux) ---------------------------------- --- The ONLY two bindings that do NOT target the box2dxt shim: they bind to --- the platform's own dynamic loader (dlopen/dlerror), resolved from the +-- The ONLY three bindings that do NOT target the box2dxt shim: they bind to +-- the platform's own loader/libc (dlopen/dlerror/realpath), resolved from the -- host process's global symbol table. That is the whole point -- they must -- be callable BEFORE box2dxt.so is loadable, so a script can preload the -- shim by absolute path. On Linux the engine hands the bare name @@ -459,6 +459,10 @@ private foreign handler _query_fraction(in pI as CInt) returns CDouble binds to -- dlopen still lives in libdl -> "c:libdl.so.2>dlopen!cdecl". private foreign handler _dlopen(in pPath as ZStringNative, in pFlags as CInt) returns optional Pointer binds to "c:>dlopen!cdecl" private foreign handler _dlerror() returns optional ZStringNative binds to "c:>dlerror!cdecl" +-- realpath("/proc/self/exe", nil) -> the running engine's own executable path +-- on Linux; used to locate the engine folder for the automatic preload below +-- (_ensureLib). Returns nothing off Linux (no /proc), self-gating the feature. +private foreign handler _realpath(in pPath as ZStringNative, in pResolved as optional Pointer) returns optional ZStringNative binds to "c:>realpath!cdecl" -- small bool -> int helper (no foreign call, so no unsafe needed) private handler bi(in pB as Boolean) returns Integer @@ -468,12 +472,56 @@ private handler bi(in pB as Boolean) returns Integer return 0 end handler +-- ---- automatic engine-folder preload (Linux) ----------------------- +-- Make the extension load box2dxt.so from the ENGINE'S OWN folder, so a user +-- just drops the library beside the OXT engine (or bundles it in a standalone) +-- and it works -- no /usr/lib, no sudo, no LD_LIBRARY_PATH, no script calls. +-- We read the engine's own path (realpath /proc/self/exe), strip to its +-- folder, and preload "/box2dxt.so" by absolute path BEFORE any +-- c:box2dxt> binding resolves; the engine's later bare-name load then finds +-- the already-resident copy. Off Linux realpath returns nothing and this +-- no-ops (Windows/macOS already load from beside the stack/app). Requires the +-- library's soname to be "box2dxt.so" (-DBOX2DXT_BARE_SONAME) or the resident +-- copy will not match the bare-name request. Cheap to repeat (dlopen of a +-- resident lib just bumps its refcount), so it runs straight from the natural +-- first-call gateways (b2Version, checkABI) with no module-state guard. +private handler _ensureLib() + variable tExe as optional String + variable tPath as String + variable tLen as Integer + variable tCut as Integer + variable i as Integer + variable tLib as String + variable tHandle as optional Pointer + unsafe + put _realpath("/proc/self/exe", nothing) into tExe + end unsafe + if tExe is not nothing then + put tExe into tPath + put the number of chars in tPath into tLen + put 0 into tCut + repeat with i from 1 up to tLen + if char i of tPath is "/" then + put i into tCut + end if + end repeat + if tCut > 0 then + put char 1 to tCut of tPath into tLib + put tLib & "box2dxt.so" into tLib + unsafe + put _dlopen(tLib, 258) into tHandle + end unsafe + end if + end if +end handler + -- ===================================================================== -- Public API (callable from xTalk / OpenXTalk Script) -- ===================================================================== public handler b2Version() returns Integer variable tR as Integer + _ensureLib() unsafe put _abi_version() into tR end unsafe @@ -486,6 +534,7 @@ end handler -- clear, catchable error instead. private handler checkABI() variable tV as Integer + _ensureLib() unsafe put _abi_version() into tV end unsafe From 58b6ee4a7c571151b12e7a1b6d01f206f7b02158 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 15 Jun 2026 21:31:56 +0000 Subject: [PATCH 2/2] Make engine-folder preload an explicit Linux helper (not auto-fired) The previous commit auto-called _ensureLib from b2Version/checkABI, which run on every platform -- but _ensureLib calls realpath (POSIX), so on Windows that binding would fail to resolve and make b2Version()/b2NewWorld() throw. Back out the auto-fire and expose the engine-folder preload as an explicit, Linux-only public handler b2LoadNativeLibHere(): it locates the engine via realpath /proc/self/exe and preloads /box2dxt.so. Callers gate it on the platform is Linux (the Kit will, once the preload is confirmed on Linux); b2Version/checkABI are back to their original cross-platform-safe form. No new C ABI symbol; b2Version() stays 4. .livecodescript gates pass; the .lcb still needs an OXT load to confirm the bindings compile and resolve. https://claude.ai/code/session_01EVV5fGeXqtWs8WDrmgWNku --- src/box2dxt.lcb | 72 ++++++++++++++++++++++++++----------------------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/src/box2dxt.lcb b/src/box2dxt.lcb index 43099fe..cb2a01c 100644 --- a/src/box2dxt.lcb +++ b/src/box2dxt.lcb @@ -460,8 +460,8 @@ private foreign handler _query_fraction(in pI as CInt) returns CDouble binds to private foreign handler _dlopen(in pPath as ZStringNative, in pFlags as CInt) returns optional Pointer binds to "c:>dlopen!cdecl" private foreign handler _dlerror() returns optional ZStringNative binds to "c:>dlerror!cdecl" -- realpath("/proc/self/exe", nil) -> the running engine's own executable path --- on Linux; used to locate the engine folder for the automatic preload below --- (_ensureLib). Returns nothing off Linux (no /proc), self-gating the feature. +-- on Linux; used by the b2LoadNativeLibHere helper below to locate the engine +-- folder. Returns nothing off Linux (no /proc). Only ever called on Linux. private foreign handler _realpath(in pPath as ZStringNative, in pResolved as optional Pointer) returns optional ZStringNative binds to "c:>realpath!cdecl" -- small bool -> int helper (no foreign call, so no unsafe needed) @@ -472,20 +472,22 @@ private handler bi(in pB as Boolean) returns Integer return 0 end handler --- ---- automatic engine-folder preload (Linux) ----------------------- --- Make the extension load box2dxt.so from the ENGINE'S OWN folder, so a user --- just drops the library beside the OXT engine (or bundles it in a standalone) --- and it works -- no /usr/lib, no sudo, no LD_LIBRARY_PATH, no script calls. --- We read the engine's own path (realpath /proc/self/exe), strip to its --- folder, and preload "/box2dxt.so" by absolute path BEFORE any --- c:box2dxt> binding resolves; the engine's later bare-name load then finds --- the already-resident copy. Off Linux realpath returns nothing and this --- no-ops (Windows/macOS already load from beside the stack/app). Requires the --- library's soname to be "box2dxt.so" (-DBOX2DXT_BARE_SONAME) or the resident --- copy will not match the bare-name request. Cheap to repeat (dlopen of a --- resident lib just bumps its refcount), so it runs straight from the natural --- first-call gateways (b2Version, checkABI) with no module-state guard. -private handler _ensureLib() +-- ---- engine-folder preload (Linux helper) -------------------------- +-- Locate the running engine's OWN folder (realpath /proc/self/exe), strip to +-- its directory, and preload "/box2dxt.so" by absolute path -- so a user +-- can just drop the library beside the OXT engine (or bundle it in a +-- standalone) and a later bare-name bind resolves to it (no /usr/lib, no sudo, +-- no LD_LIBRARY_PATH). Returns 1 if a library was loaded, else 0. +-- +-- LINUX ONLY, and deliberately NOT auto-fired: realpath/dlopen are POSIX, so +-- call this ONLY when the platform is Linux (e.g. the Kit gates it with +-- `the platform is "Linux"`). It is intentionally NOT invoked from +-- b2Version/checkABI -- those run on every platform, and touching the POSIX +-- bindings on Windows/macOS would error (those platforms already load the +-- library from beside the stack/app anyway). Requires the library's soname to +-- be "box2dxt.so" (-DBOX2DXT_BARE_SONAME) or the resident copy will not match +-- the engine's bare-name request. +public handler b2LoadNativeLibHere() returns Integer variable tExe as optional String variable tPath as String variable tLen as Integer @@ -496,23 +498,29 @@ private handler _ensureLib() unsafe put _realpath("/proc/self/exe", nothing) into tExe end unsafe - if tExe is not nothing then - put tExe into tPath - put the number of chars in tPath into tLen - put 0 into tCut - repeat with i from 1 up to tLen - if char i of tPath is "/" then - put i into tCut - end if - end repeat - if tCut > 0 then - put char 1 to tCut of tPath into tLib - put tLib & "box2dxt.so" into tLib - unsafe - put _dlopen(tLib, 258) into tHandle - end unsafe + if tExe is nothing then + return 0 + end if + put tExe into tPath + put the number of chars in tPath into tLen + put 0 into tCut + repeat with i from 1 up to tLen + if char i of tPath is "/" then + put i into tCut end if + end repeat + if tCut is 0 then + return 0 end if + put char 1 to tCut of tPath into tLib + put tLib & "box2dxt.so" into tLib + unsafe + put _dlopen(tLib, 258) into tHandle + end unsafe + if tHandle is nothing then + return 0 + end if + return 1 end handler -- ===================================================================== @@ -521,7 +529,6 @@ end handler public handler b2Version() returns Integer variable tR as Integer - _ensureLib() unsafe put _abi_version() into tR end unsafe @@ -534,7 +541,6 @@ end handler -- clear, catchable error instead. private handler checkABI() variable tV as Integer - _ensureLib() unsafe put _abi_version() into tV end unsafe