Companion language bindings (C++ and C#) #
wolfram's core SDK must remain pure C23. The C23 core (libwolfram) is the
single source of truth: it owns transport, crypto, DAG-CBOR, DNS, and the
server. Companion languages are consumer layers that link libwolfram and
call into it — they must never re-implement signing, hashing, serialization,
transport, or server logic.
This document records the agreed conventions for the two C-adjacent bindings:
C++ (wolfram-cpp) and C# (Wolfram.Interop).
Hard constraints (apply to every binding) #
- Wrappers only — never reimplement. The C++ and C# layers are thin
pass-through wrappers over
libwolfram. They must not reimplement any wolfram logic: no signing/hashing (libsecp256k1/OpenSSL), no DAG-CBOR or CAR (libcbor), no transport/DNS (libcurl/c-ares), no server (libmicrohttpd). Every wrapper ultimately calls alibwolframfunction; the binding layer only adds ownership (RAII/SafeHandle), error mapping, and idiomatic naming. If wolfram has an honest stub (WF_ERR_INVALID_ARG+TODO), the binding surfaces that as an error/exception — never a fabricated success. - Ownership mirrored 1:1. wolfram exposes a uniform contract: every owned
output type
wf_Thas a matchingwf_T_free, and JSON strings usewf_*_json_free(char*)(freed viacJSON_free, notfree/delete). Each binding wraps these with its native ownership primitive. - Status handling. The
wf_statusenum (_result.h:WF_OK = 0,WF_ERR_INVALID_ARG,WF_ERR_ALLOC,WF_ERR_NETWORK, …) maps to the language's native error model;WF_OKis success. - Encoding is UTF-8. All
char*strings are UTF-8. Marshalling must be explicit; do not rely on platform defaults (UTF-16 in .NET/JS). - Optional modules are feature-gated.
WOLFRAM_BUILD_SERVER,WOLFRAM_BUILD_STORE,WOLFRAM_BUILD_STORE_CRYPTOchange which symbols exist inlibwolfram; bindings gate those surfaces accordingly. - Public headers are
extern "C". Both languages consume the existinginclude/wolfram/*.hdirectly (C++ includes them; C# parses them with ClangSharp). No hand-maintained signature duplication where a generator fits.
C++ — wolfram-cpp (header-only RAII layer) #
Implemented as cpp/wolfram-cpp/ (build with -DWOLFRAM_BUILD_CPP=ON; the
wolfram-cpp-smoke test exercises it offline). C++ includes the C headers
directly, so there is no binding generator for the wrapper itself — just a
thin RAII layer:
wolfram/unique_handle.hpp—unique_handle<T, Free>RAII owner mirroring wolfram'swf_T+wf_T_freecontract (move-only,get()/release()/reset()).wolfram/cstring.hpp— RAII owner of a heapchar*; default deleter isstd::free, pass awf_*_json_freefor cJSON-backed strings.wolfram/status.hpp—wf_status→std::error_codevia a customerror_category, pluswolfram::require(wf_status)that throwsstd::system_error.wf_statusisis_error_code_enum-enabled so it constructsstd::error_codedirectly.tools/gen_owners.cpp— scansinclude/wolfram/*.hforvoid wf_<T>_free(...)and emitswolfram/generated_owners.hppwith onewf_<T>_handletypedef per owned type (372 handles from 51 headers). Regenerate after changing headers.wolfram/wolfram.hpp— umbrella include.
Core changes required for C++ consumption #
The C public headers were made C++-clean as part of adding this wrapper:
- Unified the two status enums:
wf_status(xrpc.h) is now the single canonical enum (full 27-code set, values preserved from the oldwf_error);wf_error(in_result.h) is atypedefalias ofwf_status. Previously the same enumerators existed in both enums with divergent values (e.g.WF_ERR_RATE_LIMITwas 8 vs 21), which broke any translation unit that included both. _result.hnow wraps its declarations inextern "C"and no longer has a straytypetoken beforestatic inline(a latent bug that also failed to compile in C++).project(... LANGUAGES C CXX)so the C++ target is buildable.
C# — Wolfram.Interop (P/Invoke, AOT-capable) #
Implemented as dotnet/Wolfram.Interop/ (build + test with dotnet test dotnet/Wolfram.Interop.Tests). Pure pass-through wrappers — no crypto,
transport, DAG-CBOR, or server logic is reimplemented; every call forwards to
libwolfram.
- Raw tier (
Internal/Raw.cs) —LibraryImport(source-generated, NativeAOT/trim-safe) P/Invoke declarations, 1:1 with the C ABI. UTF-8 strings explicit (StringMarshalling = Utf8/UnmanagedType.LPUTF8Str),nuintforsize_t, opaque handles asIntPtr. A combinedDllImportResolverloadslibwolfram(honoringWOLFRAM_NATIVE_LIB) and the platform libc (only to free owned strings the SDK returns). - Managed tier —
XrpcClientHandle : SafeHandleownswf_xrpc_client*(released viawf_xrpc_client_free; no rawIntPtrescapes).Statusmirrorswf_status;WolframExceptionis raised on non-Ok(honest stubs surface here, never faked).Wolfram/WolframClientgive an idiomatic API. - Regeneration —
tools/generate.rspis a ClangSharpPInvokeGenerator response file to regenerate/expand the raw tier frominclude/wolfram/*.h(the hand-writtenRaw.cscurrently covers the wrapped functions).
Layout #
cpp/wolfram-cpp/— header-only C++ RAII wrapper (linkslibwolfram).dotnet/Wolfram.Interop/— raw P/Invoke tier + hand-written managed layer (linkslibwolfram).
Both consume the same built libwolfram; neither ships crypto/transport code.