C++ integration
Compile a C++ application against the Babel native SDK’s public C header. This guide explains the C++ example , a direct compiler command and an equivalent CMake configuration.
Babel Licensing Native SDK 12.2.0 (C ABI 2) is available for macOS arm64, Linux x64 and Windows x64. The setup script downloads the pinned archive for your platform and verifies its SHA-256 before installation.
Select your platform
The SDK distributes one runtime library for each platform. Botan is embedded; users do not install or ship a Botan or SQLite library. Windows also receives a link-time import library for MSVC/Rust; that file is not another runtime DLL.
| Platform package | Runtime file | Build environment |
|---|---|---|
| macOS arm64, macOS 15+ | .sdk/lib/libBabelLicensingNative.dylib | Apple Silicon toolchain; Xcode command-line tools for compiled clients |
| Linux x64, glibc | .sdk/lib/libBabelLicensingNative.so | x86_64 compiler/toolchain; Ubuntu 22.04 is the initial build baseline |
| Windows x64 | .sdk/bin/BabelLicensingNative.dll | x64 process; MSVC import library in .sdk/lib/BabelLicensingNative.lib |
The installer selects the current OS/architecture, verifies that platform’s archive checksum and metadata, and rejects a different package. Linux checks its minimum glibc version; a musl/Alpine package is not provided. The native runtime uses the OS certificate trust store and system facilities. The release archives are available from the 12.2.0 SDK release .
Linux: install, build and run
Install Python 3.9 or later and the language toolchain. Rust needs Cargo/rustc 1.85 or newer plus a C linker; Go needs Go 1.22 or newer plus GCC/Clang for cgo; C++ needs a C++20 compiler. On Ubuntu, build-essential supplies the C/C++ compiler and linker. Python clients need no C/C++ compiler. The OS needs its normal CA certificate package for HTTPS.
git clone https://github.com/babelfornet/native-licensing-examples.git
cd native-licensing-examples
python3 scripts/setup-sdk.py
python3 scripts/run-example.py cppThe shared runner builds the selected client and runs it from the repository root. It does not build the SDK or download dependencies. A successful offline run prints License verified. Feature Pro enabled. The equivalent direct command is:
mkdir -p .build
c++ -std=c++20 -Isdk/include cpp/main.cpp -L.sdk/lib -lBabelLicensingNative \
-Wl,-rpath,"$PWD/.sdk/lib" -o .build/cpp-example
./.build/cpp-exampleThe Rust build script and Go cgo directives set the runtime path on Linux. C++ explicitly supplies -L for link-time lookup and -Wl,-rpath for runtime lookup. Python selects .so in runtime_path(). If you deploy a compiled client elsewhere, ship the single .so in your application layout and configure an appropriate runtime path, such as $ORIGIN/../lib for a sibling lib/ directory.
Windows: install, build and run
Use x64 Python 3.9 or later. For C++ or Rust/MSVC, install Visual Studio’s Desktop development with C++ workload and open an x64 Native Tools terminal. Rust uses the x86_64-pc-windows-msvc toolchain. Go/cgo additionally needs a MinGW-compatible GCC or Clang toolchain, such as MSYS2 UCRT64 or LLVM-MinGW; cl.exe is not Go’s cgo C compiler.
From PowerShell in the example checkout:
python scripts/setup-sdk.py
python scripts/run-example.py cppThe Python runner makes .sdk/bin available to the child process through its PATH. The C++ runner uses cl when available, Rust links the MSVC import .lib, and Go links the C ABI with its cgo compiler. To build and run directly:
New-Item -ItemType Directory -Force .build | Out-Null
$env:PATH = "$PWD\.sdk\bin;$env:PATH"
cl /nologo /std:c++20 /EHsc /MT /Isdk/include cpp/main.cpp /Fe:.build\cpp-example.exe /link /LIBPATH:.sdk\lib BabelLicensingNative.lib
& .\.build\cpp-example.exeFor deployment, copy BabelLicensingNative.dll next to the executable, or put its directory on the application’s controlled DLL search path. Python loads its explicit absolute DLL path through ctypes.CDLL, using the C calling convention. Do not copy a Linux .so or macOS .dylib onto Windows, and do not distribute the MSVC import .lib as a runtime dependency.
The numbered sections below show the macOS reference build and explain the shared integration code. Offline, activation and floating logic is identical on all three systems; the loader and linker settings above account for the platform differences.
1. Check the compiler
The sample builds with a C++20 compiler. On macOS, install the Xcode command-line tools if they are not already available. Python 3 is needed for SDK setup; CMake is optional if you use the direct compiler command.
xcode-select -p
c++ --version
python3 --version
uname -mUse an arm64 compiler and process for the supplied arm64 library. The public API uses C types, not C++ classes, so your application does not include Botan headers or depend on its C++ interface. C++20 is the tested sample configuration, not a C++ language requirement imposed on Python, Go or Rust callers of this C ABI.
2. Install the runtime and find the header
git clone https://github.com/babelfornet/native-licensing-examples.git
cd native-licensing-examples
python3 scripts/setup-sdk.pyRun python3 scripts/setup-sdk.py to download the pinned platform archive. To use a previously downloaded ZIP, add --archive /absolute/path/to/sdk.zip. Both paths verify SHA-256.
The build uses these paths:
sdk/include/babel/babel_licensing.h public C declarations
.sdk/lib/libBabelLicensingNative.dylib native implementation
cpp/main.cpp example applicationThe C++ source includes:
#include <babel/babel_licensing.h>The include search path must therefore be sdk/include, not sdk/include/babel. The SDK archive also carries a matching header under .sdk/include.
3. Compile and link directly
Run from the repository root:
mkdir -p .build
ROOT="$(pwd -P)"
c++ -std=c++20 \
-Isdk/include \
cpp/main.cpp \
-L.sdk/lib -lBabelLicensingNative \
-Wl,-rpath,"$ROOT/.sdk/lib" \
-o .build/cpp-example
./.build/cpp-example| Argument | Purpose |
|---|---|
-std=c++20 | Selects the language mode used by the sample. |
-Isdk/include | Finds the public header during compilation. |
cpp/main.cpp | Compiles the application source. |
-L.sdk/lib | Finds the native library during linking. |
-lBabelLicensingNative | Links the library by its base name. |
-Wl,-rpath,... | Records a search path for the macOS dynamic loader. |
-o .build/cpp-example | Names the resulting executable. |
The include path does not locate the library, and the link path alone does not tell the loader where to find it at runtime. All three path settings have a separate role.
./run.sh cpp performs the same compile-and-run operation. It does not add another licensing implementation.
4. Use CMake instead
The example repository includes this CMakeLists.txt. It links the platform SDK and copies the single Babel runtime beside the executable:
cmake_minimum_required(VERSION 3.20)
project(BabelNativeExample LANGUAGES CXX)
set(BABEL_NATIVE_SDK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/.sdk"
CACHE PATH "Directory containing the native SDK")
add_library(BabelLicensingNative SHARED IMPORTED)
if(WIN32)
set_target_properties(BabelLicensingNative PROPERTIES
IMPORTED_IMPLIB "${BABEL_NATIVE_SDK_DIR}/lib/BabelLicensingNative.lib"
IMPORTED_LOCATION "${BABEL_NATIVE_SDK_DIR}/bin/BabelLicensingNative.dll")
else()
find_library(BABEL_NATIVE_LIBRARY
NAMES BabelLicensingNative
PATHS "${BABEL_NATIVE_SDK_DIR}/lib"
NO_DEFAULT_PATH REQUIRED)
set_target_properties(BabelLicensingNative PROPERTIES
IMPORTED_LOCATION "${BABEL_NATIVE_LIBRARY}")
endif()
add_executable(native-example cpp/main.cpp)
target_compile_features(native-example PRIVATE cxx_std_20)
target_include_directories(native-example PRIVATE "${BABEL_NATIVE_SDK_DIR}/include")
target_link_libraries(native-example PRIVATE BabelLicensingNative)
if(MSVC)
set_property(TARGET native-example PROPERTY
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
elseif(APPLE)
set_target_properties(native-example PROPERTIES BUILD_RPATH "@loader_path")
else()
set_target_properties(native-example PROPERTIES BUILD_RPATH "$ORIGIN")
endif()
# The deployed app needs only the Babel runtime next to its executable.
add_custom_command(TARGET native-example POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
"$<TARGET_FILE:BabelLicensingNative>" "$<TARGET_FILE_DIR:native-example>"
VERBATIM)Then configure, build and run:
cmake -S . -B .build/cmake -DCMAKE_BUILD_TYPE=Release
cmake --build .build/cmake --config Release
./.build/cmake/native-exampleOn Windows, run these commands in an x64 Visual Studio developer terminal. A multi-configuration generator places the executable under Release; the build copies the DLL beside it:
cmake -S . -B .build/cmake
cmake --build .build/cmake --config Release
& .\.build\cmake\Release\native-example.exeNinja is a single-configuration generator; with -G Ninja -DCMAKE_BUILD_TYPE=Release, run .build/cmake/native-example.exe instead. macOS and Linux record a loader-relative runtime path. When distributing the example, copy the executable and its adjacent Babel runtime together; no Botan runtime is needed. Keep the signed application license and trusted publisher key in your application’s expected locations.
NO_DEFAULT_PATH prevents the search from selecting an unrelated library elsewhere on the machine. If the SDK is installed outside the checkout, configure it explicitly:
cmake -S . -B .build/cmake \
-DCMAKE_BUILD_TYPE=Release \
-DBABEL_NATIVE_SDK_DIR=/absolute/path/to/the/sdkThe directory must contain include/babel/babel_licensing.h and lib/libBabelLicensingNative.dylib. The CMake variable configures SDK discovery, not the location of your customer license file.
5. Read bounded input and check the ABI
The full main.cpp opens each input in binary mode, checks its size before allocation and rejects embedded NUL bytes. Its limits are 1 MiB for XML and 16 KiB for the publisher public key.
Before using a file, it checks:
if (babel_abi_version() != 2) {
throw std::runtime_error("This example requires native ABI v2");
}The native functions return babel_status. The example’s success helper throws a local C++ exception when a status is not BABEL_OK:
static void success(babel_status status, const char* operation) {
if (status != BABEL_OK) {
throw std::runtime_error(
std::string(operation) + " failed (status " +
std::to_string(status) + ")");
}
}These exceptions are application error handling. The native C API reports its failures through status codes rather than exposing C++ exceptions across the ABI.
6. Keep one owner for the native handle
Loading produces an opaque handle through an output parameter:
babel_license* raw = nullptr;
success(babel_license_load(text.c_str(), &raw), "License parsing");
std::unique_ptr<babel_license, decltype(&babel_license_free)>
handle(raw, babel_license_free);The unique_ptr custom deleter calls the library’s own deallocator. Do not call delete or std::free on the opaque pointer. unique_ptr releases it on success and when a later check throws, so the main validation flow does not need separate cleanup branches.
std::string::c_str() provides NUL-terminated bytes while its string is alive. The example keeps its text and key variables alive across the calls. It passes the key separately from the XML: the publisher trust anchor is never selected from the document being checked.
7. Authenticate and validate before using Pro
success(
babel_license_verify(handle.get(), "RSA", key.c_str()),
"Publisher signature verification");
success(
babel_license_validate(handle.get()),
"Runtime license validation");
if (!babel_license_feature_enabled(handle.get(), "Pro")) {
throw std::runtime_error("Requested feature is absent or expired");
}Verification proves the publisher signature against the supplied trusted key. Runtime validation checks license expiry and refuses restrictions that the native implementation does not support. The feature query also enforces feature expiry.
Parsing success does not enable a feature. A failed verification clears previous authentication. Do not continue after a failed check, and do not use diagnostic getters as proof that a license is valid.
The demo takes the key path as an argument to let you try your publisher. For a product, pin the key in the application or trusted configuration. The private signing key belongs only on the publisher’s machine.
8. Run the resulting application
The default run uses demo/perpetual.xml, demo/publisher-key.txt, algorithm RSA and feature Pro:
./.build/cpp-exampleExpected output:
License verified. Feature Pro enabled.Select another file or your own publisher:
./.build/cpp-example demo/trial.xml
./.build/cpp-example demo/perpetual.xml
./.build/cpp-example \
/absolute/path/customer.xml /absolute/path/publisher-key.txt RSA Pro
./.build/cpp-example demo/expired.xml
./.build/cpp-example demo/no-feature.xml
./.build/cpp-example demo/restricted.xmlThe last three commands are expected to fail with a nonzero exit status. Status 10 means license expiry; 4 means an unsupported restriction. A missing Pro feature is refused even if the file has a valid signature.
Run from the repository root for the default relative file paths, or supply absolute paths when launching from another directory. You do not need a compiler or .NET to start the built executable, but its configured native runtime must remain available.
Runtime files and troubleshooting
Inspect the linked binary and runtime:
otool -L .build/cpp-example
otool -l .build/cpp-example
file .build/cpp-example .sdk/lib/libBabelLicensingNative.dylibotool -L shows the library reference; otool -l includes the LC_RPATH entries used to resolve @rpath. Distribute libBabelLicensingNative.dylib at the configured runtime path. Botan is embedded in this file; only macOS system libraries appear as runtime dependencies.
The development command uses an absolute runtime path to the checkout. Moving only the executable does not package the SDK with it. A distributable application must install the native libraries and configure its runtime search path for that layout; rebuild the application if its development SDK location changes.
| Error | Likely configuration problem |
|---|---|
babel/babel_licensing.h not found | Wrong -I path or missing header. |
library not found for -lBabelLicensingNative | Wrong -L path or SDK not installed. |
Library not loaded | Missing runtime rpath or libBabelLicensingNative.dylib. |
| Architecture mismatch | Intel application and arm64 SDK, or the reverse. |
Status 3 | Wrong publisher key or altered signed data. |
For publisher setup, see Issue your own files. For all sample checks, run python3 scripts/test-examples.py from the repository root.
Run activation and floating licensing
The online modes use the same application and the same single SDK runtime as the offline example. They need a service endpoint, a customer user key and a publisher public key pinned by your application. Do not supply the license used to evaluate or purchase Babel Licensing itself: these are licenses for your own application.
For a self-contained local protocol demonstration, start the fixture in a separate terminal from the repository root:
python3 scripts/demo-service.pyThe fixture listens only on 127.0.0.1:8765, reads signed demo payloads, and allocates one disposable floating seat. It needs Python 3, but no .NET runtime, database or signing private key. It is a protocol fixture, not a deployable Licensing Service.
In your application terminal, run:
export BABEL_SERVICE_URL=http://127.0.0.1:8765/
export BABEL_ALLOW_LOOPBACK_HTTP=1
export BABEL_MACHINE_ID=native-example-machine
export BABEL_PUBLISHER_KEY_FILE="$PWD/demo/publisher-key.txt"
export BABEL_ALGORITHM=RSA
export BABEL_CLIENT_ID=native-example-cpp
export BABEL_USER_KEY=demo-activation
./run.sh cpp --online activation
export BABEL_USER_KEY=demo-floating
./run.sh cpp --online floatingActivation prints a verified grant, validates it again with the service, then deactivates it. Floating prints a verified grant, renews it twice, then releases its seat. Both exit with an error when acquisition, signature verification, machine validation, feature entitlement or a network operation fails.
Use your actual Licensing Service
Configure an activation or floating license for your application in Babel Licensing Service, with a supported RSA or ECDSA P-256 publisher key and a Pro feature (or adapt the feature name). The service must return a valid signed XML or compact license. Stateful trial run counts/days-from-first-use, domain, usage and custom restrictions remain unsupported; an absolute expiry is supported.
unset BABEL_ALLOW_LOOPBACK_HTTP BABEL_MACHINE_ID
export BABEL_SERVICE_URL=https://licensing.example.com/
export BABEL_PUBLISHER_KEY_FILE=/absolute/path/your-publisher-public-key.txt
export BABEL_ALGORITHM=RSA
# Supply BABEL_USER_KEY from your application's secure configuration.
./run.sh cpp --online activation
# Or use a floating user key and --online floating.Replace the endpoint with your own service root. The SDK appends v1/auth/login and v1/license/*. It authenticates with the user key, then uses a bearer token for service requests. Credentials are read from the environment in these examples and are not printed or passed on the command line. Production applications should use their own secure configuration mechanism.
With BABEL_MACHINE_ID unset, the example calls babel_hardware_key. Keep the machine identity and BABEL_CLIENT_ID stable for an activation; choose distinct client identities for concurrently running floating clients. The fixed machine ID above exists only to match the signed local fixture. Pin the public key in your application or trusted configuration; never trust the key supplied inside a server response.
HTTPS always verifies certificates and hostnames; redirects are not followed. Plain HTTP requires the explicit loopback flag and cannot be used with a remote hostname. The sample timeout is 15 seconds per HTTP request; authentication and the following service request each have their own bound.
Integration code and lifecycle
// Error checking and exception cleanup are in cpp/online.hpp.
success(babel_online_activate(client, userKey.c_str()), "Activate");
success(babel_online_check(client), "Check before work");
if (babel_online_feature_enabled(client, "Pro") != 1)
throw std::runtime_error("Pro is not licensed");
success(babel_online_validate_activation(client), "Validate with service");
success(babel_online_deactivate(client), "Deactivate");cpp/main.cpp includes cpp/online.hpp; keep that file next to the main source. The same C++20 command and CMake target shown above compile all three licensing modes. No extra include directory, linker flag or runtime library is needed for the online API. std::unique_ptr<babel_online_client, decltype(&babel_online_free)> owns the native session. The exception path attempts cleanup before the owner frees it.
For floating, the code calls babel_online_request_floating, reads babel_online_heartbeat_after_ms, waits with std::this_thread::sleep_for(std::chrono::milliseconds(delay)), then calls babel_online_heartbeat and babel_online_check. It repeats twice before babel_online_release.
The session API retains the verified license and floating token internally. Use babel_online_check immediately before protected work and babel_online_feature_enabled for feature access. Do not extract or cache a Boolean from the initial acquisition and use it indefinitely.
For a running floating application, schedule heartbeats before the returned deadline throughout the protected session. The two-heartbeat loop is only the executable demonstration. Stop protected work on any heartbeat/check failure. There is no background renewal, offline grace or persistent activation cache in this version. A failed heartbeat invalidates the local grant immediately; a lease cannot be revived after its deadline and must be acquired again.
Release floating seats or deactivate activations explicitly when the application workflow requires it. The examples do this when they exit so repeated evaluations do not consume seats. A product can retain an activation between runs by keeping stable identities and activating/validating it again at startup; this SDK does not save a signed license or token to disk. Freeing a handle releases memory and performs no network request.
If release/deactivation fails, local use is still disabled. Retry cleanup with the same live handle while it exists; a floating token eventually expires at the service. Handle destruction, process termination and network failure do not prove that the server has received cleanup.
Online refusal scenarios
| Status | Meaning and application action |
|---|---|
2 / 3 | Malformed response/license or bad publisher signature: refuse protected work. |
4 | Unsupported algorithm, curve or restriction: adjust the issuing template, never bypass the check. |
10 | Signed license expired: obtain a valid application license. |
11 | Network, TLS, timeout or response-size failure: stop protected work and report the failure. |
12 / 13 / 14 | Credentials rejected, HTTP/service failure or grant refused: check service policy and seat availability. |
15 | No active grant, released/expired lease or failed heartbeat: stop use and acquire a new lease as appropriate. |
16 | Signed hardware binding does not match the configured machine: refuse activation. |
Run python3 scripts/test-online-examples.py to build/run all four languages against local activation, floating renewal/release, denied grant, hardware mismatch and unsupported-restriction cases. The native SDK separately tests malformed JSON, tampered signed payloads, oversized responses, timeout, redirects, invalid TLS and lease loss. These protocol-fixture checks do not replace acceptance tests against your deployed Licensing Service.
Online commands in Windows PowerShell
The service and user key settings have the same meaning on every OS. For the supplied loopback fixture, open one terminal for python scripts/demo-service.py, then use another:
$env:BABEL_SERVICE_URL = "http://127.0.0.1:8765/"
$env:BABEL_ALLOW_LOOPBACK_HTTP = "1"
$env:BABEL_MACHINE_ID = "native-example-machine"
$env:BABEL_PUBLISHER_KEY_FILE = "$PWD\demo\publisher-key.txt"
$env:BABEL_ALGORITHM = "RSA"
$env:BABEL_CLIENT_ID = "native-example-cpp"
$env:BABEL_USER_KEY = "demo-activation"
python scripts/run-example.py cpp --online activation
$env:BABEL_USER_KEY = "demo-floating"
python scripts/run-example.py cpp --online floatingFor your actual HTTPS Licensing Service, remove the loopback flag and fixed demo machine identity, then supply your service root, publisher key and application user key. Run the same --online commands. Linux uses the shell export commands in the online section and can use python3 scripts/run-example.py instead of ./run.sh.
Remove-Item Env:BABEL_ALLOW_LOOPBACK_HTTP -ErrorAction SilentlyContinue
Remove-Item Env:BABEL_MACHINE_ID -ErrorAction SilentlyContinue
$env:BABEL_SERVICE_URL = "https://your-licensing-service.example/"
$env:BABEL_PUBLISHER_KEY_FILE = "C:\YourApplication\publisher-public-key.txt"
# Supply BABEL_USER_KEY through your application's secure configuration.
python scripts/run-example.py cpp --online activationRun python scripts/test-examples.py and python scripts/test-online-examples.py on Windows (or python3 on Linux/macOS) to exercise the same accepted/refused paths in all four bindings.