From d4ceedf553f49f81a50589236652ca6b6e42fe1e Mon Sep 17 00:00:00 2001 From: "Enzo Persillet (Tutez)" Date: Mon, 2 Nov 2026 20:41:01 +0100 Subject: [PATCH] Declare the Prime string functions in CFFIPrime.h included after CFFI.h CFFIAPI.h declares hxs_encoding, hxs_utf16, alloc_hxs_utf8 and the other Prime string functions only under HXCPP_PRIME, which CFFIPrime.h defines before it includes CFFI.h. An extension that includes CFFI.h first, as SteamWrap does, gets the API without them, and CFFIPrime.h's own inclusion of CFFI.h then does nothing. Since 1e6417d6, the ordinary wrapper of a DEFINE_PRIME function uses three of them, so such an extension no longer compiled. CFFIAPI.h now marks that it declared them, and with HX_CFFI_PRIME_ONLY declares them alone. CFFIPrime.h includes it that way when they are missing, so the wrapper keeps Unicode strings whatever the order. An ndll resolves each function the first time it is called, so declaring them later changes nothing else. Under neko, CFFI.h included first leaves out the defaults of alloc_hxs_utf8 and hxs_utf16, but hxs_encoding's default answers UTF-8, so neither is called. Tests: test/regression/CffiPrimeAfterCffi builds an ndll that includes CFFI.h before CFFIPrime.h and passes strings that are not ASCII through the wrapper. Before, the ndll failed to compile. The other Cffi* cases pass, and both include orders compile with IMPLEMENT_API, STATIC_LINK and neither. Co-Authored-By: Claude Opus 5.5 --- .gitignore | 1 + Changes.md | 1 + include/hx/CFFIAPI.h | 8 ++++++++ include/hx/CFFIPrime.h | 8 ++++++++ .../CffiPrimeAfterCffi/BuildNative.hx | 18 ++++++++++++++++++ test/regression/CffiPrimeAfterCffi/Main.hx | 18 ++++++++++++++++++ test/regression/CffiPrimeAfterCffi/build.hxml | 2 ++ .../CffiPrimeAfterCffi/native/Bridge.cpp | 7 +++++++ .../CffiPrimeAfterCffi/native/Build.xml | 11 +++++++++++ test/regression/CffiPrimeAfterCffi/stdout.txt | 7 +++++++ test/regression/emscripten-skip.txt | 1 + 11 files changed, 82 insertions(+) create mode 100644 test/regression/CffiPrimeAfterCffi/BuildNative.hx create mode 100644 test/regression/CffiPrimeAfterCffi/Main.hx create mode 100644 test/regression/CffiPrimeAfterCffi/build.hxml create mode 100644 test/regression/CffiPrimeAfterCffi/native/Bridge.cpp create mode 100644 test/regression/CffiPrimeAfterCffi/native/Build.xml create mode 100644 test/regression/CffiPrimeAfterCffi/stdout.txt diff --git a/.gitignore b/.gitignore index 1be4045fd..f748effde 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ test/extern-lib/gen-externs test/cffi/project/ndll test/regression/CffiNekoNullBoolWithoutInit/native/ndll test/regression/CffiNekoValueTags/native/ndll +test/regression/CffiPrimeAfterCffi/native/ndll test/regression/CffiPrimeUnicodeFallback/native/ndll test/regression/CffiQuietMissingPrimitive/native/ndll test/snippets/messagebox/cpp diff --git a/Changes.md b/Changes.md index f934df9bf..a516fbf5a 100644 --- a/Changes.md +++ b/Changes.md @@ -2,6 +2,7 @@ libhxcpp 6.0.0 (unreleased) ------------------------------------------------------------ +* Fixed a native extension that includes hx/CFFI.h before hx/CFFIPrime.h, as SteamWrap does, failing to compile with hxs_encoding, hxs_utf16 and alloc_hxs_utf8 not declared: CFFI.h included first declares the CFFI API without HXCPP_PRIME, so without the Prime string functions that the ordinary wrapper of a DEFINE_PRIME function now uses. CFFIPrime.h declares them then * Fixed a thread that throws while the process exits crashing it on musl, as one with a failing cpp.Lib.load did about once in 800 exits on Alpine: libgcc's unwinder frees the table it finds frames in when the library finalizers run, and other threads still ran Haxe code then. The exit now parks every other thread that runs Haxe code, at its next safe point or as it leaves a GC-free zone, waiting up to 100 ms for one in a loop that does not allocate. A thread let go during the exit stays parked instead of ending (libhxcpp #423) * Fixed programs that use sys.ssl without sockets failing to link on Linux with an undefined reference to hxcpp_alloc_kind: hxcpp_std, linked a second time after hxcpp_ssl, came after the runtime's second copy. The runtime is linked once more after all the libraries linked twice, which keep their order * Fixed closing, reading or polling a socket handle that a library made in stock hxcpp's layout, as CrossByte's accept does, reading past the handle: the runtime took its own wrapper's later fields from it, and a close wrote through what followed it in memory. Only the descriptor is read from such a handle, and the runtime's wrapper no longer shares its name, and with it its vtable, with a library's diff --git a/include/hx/CFFIAPI.h b/include/hx/CFFIAPI.h index 6a05ada73..3951390d2 100644 --- a/include/hx/CFFIAPI.h +++ b/include/hx/CFFIAPI.h @@ -2,8 +2,12 @@ This bit of Macro magic is used to define extern function pointers in ndlls, define stub implementations that link back to the hxcpp dll and glue up the implementation in the hxcpp runtime. + + HX_CFFI_PRIME_ONLY declares the Prime string functions alone, for + CFFIPrime.h when CFFI.h was included before it, without them. */ +#ifndef HX_CFFI_PRIME_ONLY DEFFUNC_1(void,val_throw,value) DEFFUNC_0(void,hx_error) DEFFUNC_3(void,hx_fail,const char *,const char *,int) @@ -47,8 +51,10 @@ DEFFUNC_1(const char *,val_string,value) DEFFUNC_1(wchar_t *,val_dup_wstring,value) DEFFUNC_1(char *,val_dup_string,value) DEFFUNC_2(char *,alloc_string_data,const char *,int) +#endif #ifdef HXCPP_PRIME +#define HX_CFFI_PRIME_DECLARED DEFFUNC_2(HxString,alloc_hxs_wchar,const wchar_t *,int) DEFFUNC_2(HxString,alloc_hxs_utf16,const char16_t *,int) DEFFUNC_2(HxString,alloc_hxs_utf8,const char *,int) @@ -60,6 +66,7 @@ DEFFUNC_2(const char16_t *,hxs_utf16,const HxString &,hx::IStringAlloc *) DEFFUNC_1(hx::StringEncoding,hxs_encoding,const HxString &) #endif +#ifndef HX_CFFI_PRIME_ONLY // Array access - generic DEFFUNC_1(value,alloc_array,int) @@ -185,5 +192,6 @@ DEFFUNC_0(bool, gc_try_unblocking) // Used for finding functions in static libraries DEFFUNC_2(int, hx_register_prim, const char *, void*) +#endif diff --git a/include/hx/CFFIPrime.h b/include/hx/CFFIPrime.h index 39153f8fc..66516d98f 100644 --- a/include/hx/CFFIPrime.h +++ b/include/hx/CFFIPrime.h @@ -166,6 +166,14 @@ struct HxString }; #include "CFFI.h" + +// CFFI.h included before this header declared its API without HXCPP_PRIME, +// so without the string functions that ToValue and ValueToHxString use. +#if !defined(HX_CFFI_PRIME_DECLARED) && !defined(IGNORE_CFFI_API_H) +#define HX_CFFI_PRIME_ONLY +#include "CFFIAPI.h" +#undef HX_CFFI_PRIME_ONLY +#endif #endif #ifndef HXCPP_JS_PRIME diff --git a/test/regression/CffiPrimeAfterCffi/BuildNative.hx b/test/regression/CffiPrimeAfterCffi/BuildNative.hx new file mode 100644 index 000000000..3397f1615 --- /dev/null +++ b/test/regression/CffiPrimeAfterCffi/BuildNative.hx @@ -0,0 +1,18 @@ +#if macro +import haxe.macro.Context; +import haxe.macro.Expr; +class BuildNative { + public static function run():Array { + var args=["run","hxcpp","Build.xml"]; + for(define in ["HXCPP_M32","HXCPP_M64","HXCPP_ARM64","HXCPP_DEBUG"]) { + if(Context.defined(define)) args.push("-D"+define); + } + var original=Sys.getCwd(); + Sys.setCwd("native"); + var status=Sys.command("haxelib",args); + Sys.setCwd(original); + if(status!=0) Context.error("Native fixture build failed",Context.currentPos()); + return Context.getBuildFields(); + } +} +#end \ No newline at end of file diff --git a/test/regression/CffiPrimeAfterCffi/Main.hx b/test/regression/CffiPrimeAfterCffi/Main.hx new file mode 100644 index 000000000..8e6d3369d --- /dev/null +++ b/test/regression/CffiPrimeAfterCffi/Main.hx @@ -0,0 +1,18 @@ +// The ndll includes CFFI.h before CFFIPrime.h +@:build(BuildNative.run()) +class Main { + static function main() { + cpp.Lib.pushDllSearchPath(haxe.io.Path.join([haxe.io.Path.directory(Sys.programPath()),"../native/ndll",cpp.Lib.getBinDirectory()])); + var fallback=cpp.Lib.load("afterprime","afterEcho",1); + var fast=cpp.Prime.load("afterprime","afterEcho","ss",false); + for(text in ["plain","café","\u{1f600}"]) { + var ordinary:String=fallback(text); + var direct:String=fast(text); + Sys.println("direct Prime preserves the string: "+(direct==text)); + Sys.println("CFFI fallback preserves the same string: "+(ordinary==text)); + } + var makeFallback=cpp.Lib.load("afterprime","afterMakeText",0); + var made:String=makeFallback(); + Sys.println("CFFI fallback preserves a native Unicode result: "+(made=="日本語")); + } +} diff --git a/test/regression/CffiPrimeAfterCffi/build.hxml b/test/regression/CffiPrimeAfterCffi/build.hxml new file mode 100644 index 000000000..b484e67e7 --- /dev/null +++ b/test/regression/CffiPrimeAfterCffi/build.hxml @@ -0,0 +1,2 @@ +-m Main +--cpp bin diff --git a/test/regression/CffiPrimeAfterCffi/native/Bridge.cpp b/test/regression/CffiPrimeAfterCffi/native/Bridge.cpp new file mode 100644 index 000000000..a181e7257 --- /dev/null +++ b/test/regression/CffiPrimeAfterCffi/native/Bridge.cpp @@ -0,0 +1,7 @@ +#define IMPLEMENT_API +#include +#include +HxString afterEcho(HxString value) { return value; } +DEFINE_PRIME1(afterEcho); +HxString afterMakeText() { return alloc_hxs_utf8("\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e",9); } +DEFINE_PRIME0(afterMakeText); diff --git a/test/regression/CffiPrimeAfterCffi/native/Build.xml b/test/regression/CffiPrimeAfterCffi/native/Build.xml new file mode 100644 index 000000000..9ba51d74b --- /dev/null +++ b/test/regression/CffiPrimeAfterCffi/native/Build.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/test/regression/CffiPrimeAfterCffi/stdout.txt b/test/regression/CffiPrimeAfterCffi/stdout.txt new file mode 100644 index 000000000..38194bb43 --- /dev/null +++ b/test/regression/CffiPrimeAfterCffi/stdout.txt @@ -0,0 +1,7 @@ +direct Prime preserves the string: true +CFFI fallback preserves the same string: true +direct Prime preserves the string: true +CFFI fallback preserves the same string: true +direct Prime preserves the string: true +CFFI fallback preserves the same string: true +CFFI fallback preserves a native Unicode result: true diff --git a/test/regression/emscripten-skip.txt b/test/regression/emscripten-skip.txt index a631e8a55..65524a54e 100644 --- a/test/regression/emscripten-skip.txt +++ b/test/regression/emscripten-skip.txt @@ -9,6 +9,7 @@ ArrayConcurrentString: threads AttachedThreadId: threads (std::thread, which needs a pthreads build) CffiNekoNullBoolWithoutInit: runs neko, which loads an ndll the test builds CffiNekoValueTags: runs neko, which loads an ndll the test builds +CffiPrimeAfterCffi: loads an ndll it builds, and Emscripten links statically CffiPrimeUnicodeFallback: loads an ndll it builds, and Emscripten links statically CffiQuietMissingPrimitive: loads an ndll it builds, and Emscripten links statically CppiaArrayAccess: its host is the first --next section, which -D emscripten does not reach