C FFI layer for thetadatadx — exposes the Rust SDK as extern "C" functions.
Compiled as both cdylib (shared library) and staticlib (archive). Consumed by the C++ (RAII) and TypeScript/Node.js (napi-rs) SDKs, and available to any third-party C/C++/Go/etc. consumer that wants to roll their own wrapper against the tdx_* symbols.
Surface coverage: the FFI layer exposes all three ThetaData surfaces — MDDS (historical), FPSS (streaming), and FLATFILES (whole-universe daily blobs). The flat-files entry points are
tdx_flatfile_request_decoded(pull + decode into an opaque row-list),tdx_flatfile_rows_to_arrow_ipc(serialise to Arrow IPC bytes),tdx_flatfile_request_to_path(raw vendor bytes straight to disk), and the matching_rowlist_free/_bytes_freecleanup helpers.
cargo build --release -p thetadatadx-ffiProduces:
target/release/libthetadatadx_ffi.so(Linux)target/release/libthetadatadx_ffi.dylib(macOS)target/release/libthetadatadx_ffi.a(static, all platforms)
| Handle | Create | Free |
|---|---|---|
TdxCredentials |
tdx_credentials_new, tdx_credentials_from_file |
tdx_credentials_free |
TdxConfig |
tdx_config_production, tdx_config_dev |
tdx_config_free |
TdxClient |
tdx_client_connect |
tdx_client_free |
TdxUnified |
tdx_unified_connect |
tdx_unified_free |
TdxFpssHandle |
tdx_fpss_connect |
tdx_fpss_free |
Every historical endpoint is available as tdx_stock_*, tdx_option_*, tdx_index_*, tdx_calendar_*, tdx_interest_rate_* functions. Each takes a *const TdxClient handle and returns a typed #[repr(C)] struct array (e.g. TdxEodTickArray, TdxOhlcTickArray). Callers must free with the corresponding tdx_*_array_free function. List endpoints return TdxStringArray (freed with tdx_string_array_free).
tdx_unified_historical() returns a borrowed *const TdxClient from a unified handle - same session, no double auth.
| Function | Description |
|---|---|
tdx_unified_set_callback |
Register the push-mode user callback on the unified handle. Mutually exclusive with tdx_unified_start_streaming_iter. |
tdx_unified_subscribe |
Polymorphic subscribe — takes TdxSubscriptionRequest (per-contract or full-stream) |
tdx_unified_unsubscribe |
Polymorphic unsubscribe — takes TdxSubscriptionRequest |
tdx_unified_is_streaming |
Check if FPSS connection is live |
tdx_unified_active_subscriptions |
List active subscriptions (typed TdxSubscriptionArray) |
tdx_unified_await_drain |
Block until the previous session's consumer has finished firing the callback (drain barrier) |
tdx_unified_reconnect |
Reconnect FPSS, drain the previous generation, and re-subscribe everything that was active |
tdx_unified_stop_streaming |
Stop streaming, historical stays alive |
tdx_unified_free |
Free the unified handle |
| Function | Description |
|---|---|
tdx_unified_start_streaming_iter |
Start FPSS in pull-iter mode; returns an opaque TdxFpssEventIterator*. Mutually exclusive with tdx_unified_set_callback. |
tdx_fpss_event_iter_next |
Pop next event with timeout (0=poll, positive ms=block-with-deadline). Returns 0=event filled / 1=timeout / -1=terminal end-of-stream. |
tdx_fpss_event_iter_close |
Mark iterator closed; subsequent _next returns -1 once queue drains. |
tdx_fpss_event_iter_free |
Free the iterator handle. |
| Function | Description |
|---|---|
tdx_fpss_connect |
Connect standalone FPSS client |
tdx_fpss_set_callback |
Register the push-mode user callback |
tdx_fpss_subscribe |
Polymorphic subscribe — takes TdxSubscriptionRequest |
tdx_fpss_unsubscribe |
Polymorphic unsubscribe — takes TdxSubscriptionRequest |
tdx_fpss_is_authenticated |
Check if FPSS is authenticated |
tdx_fpss_active_subscriptions |
List active subscriptions (typed TdxSubscriptionArray) |
tdx_fpss_dropped_events |
Cumulative count of events the TLS reader could not publish into the Disruptor ring |
tdx_fpss_await_drain |
Block until the previous session's consumer has finished firing the callback |
tdx_fpss_reconnect |
Reconnect FPSS, drain the previous generation, and re-subscribe everything that was active |
tdx_fpss_shutdown |
Shut down FPSS client |
tdx_fpss_free |
Free the FPSS handle |
All functions that can fail return null on error. Call tdx_last_error() to get the error message (valid until the next FFI call on the same thread).
- Opaque handles are heap-allocated via
Box::into_raw, freed viaBox::from_rawin the corresponding*_freefunction. - Data endpoints return typed
#[repr(C)]struct arrays (e.g.TdxEodTickArray { data, len }) - free with the correspondingtdx_*_array_freefunction. - List endpoints return
TdxStringArray- free withtdx_string_array_free. tdx_fpss_active_subscriptionsreturns*mut TdxSubscriptionArray- free withtdx_subscription_array_free.tdx_last_error()returns a borrowed pointer - do NOT free it.tdx_unified_historical()returns a borrowed pointer - do NOT free it.
- All functions check for null handles before dereferencing.
- Mutex locks use poison recovery (
unwrap_or_else(|e| e.into_inner())). TdxClientis#[repr(transparent)]overMddsClientfor safe pointer casting.
Every extern "C" function (145 fns — 84 in ffi/src/lib.rs plus 61 generator-emitted in ffi/src/endpoint_with_options.rs) is wrapped in the ffi_boundary! macro, which std::panic::catch_unwind(AssertUnwindSafe(|| { ... }))s the body. Rust panics no longer cross the C ABI — the payload is downcast to String, routed through tracing::error! on target thetadatadx::ffi::panic, stored in the thread-local LAST_ERROR slot accessed by tdx_last_error(), and the function returns the caller-declared default (ptr::null_mut() / -1 / 0 / sentinel-empty-array). Before this wrapper every panic on Rust 1.81+ aborted the host process; pre-1.81 it was undefined behaviour.
Regression tests live at ffi/tests/panic_boundary.rs; the tdx_test_panic_{str,string} symbols used for testing are gated behind a testing-panic-boundary cargo feature so the production cdylib never ships them.