FFI Design
The design of bbx_audio's C FFI layer.
Goals
- Safety - Prevent memory errors across language boundary
- Simplicity - Minimal API surface
- Performance - Zero-copy audio processing
- Portability - Works with any C-compatible language
Opaque Handle Pattern
Rust types are hidden behind opaque pointers:
typedef struct BbxGraph BbxGraph; // Opaque - never dereference
C++ only sees the handle, never the Rust struct:
BbxGraph* handle = bbx_graph_create();
// handle is a type-erased pointer
Handle Lifecycle
Creation
#![allow(unused)] fn main() { #[no_mangle] pub extern "C" fn bbx_graph_create() -> *mut BbxGraph { let inner = Box::new(GraphInner::new()); Box::into_raw(inner) as *mut BbxGraph } }
Destruction
#![allow(unused)] fn main() { #[no_mangle] pub extern "C" fn bbx_graph_destroy(handle: *mut BbxGraph) { if !handle.is_null() { unsafe { drop(Box::from_raw(handle as *mut GraphInner)); } } } }
Error Handling
Return codes instead of exceptions:
typedef enum BbxError {
BBX_ERROR_OK = 0,
BBX_ERROR_NULL_POINTER = 1,
BBX_ERROR_INVALID_PARAMETER = 2,
// ...
} BbxError;
Check in C++:
BbxError err = bbx_graph_prepare(handle, ...);
if (err != BBX_ERROR_OK) {
// Handle error
}
Zero-Copy Processing
Audio buffers are passed by pointer:
void bbx_graph_process(
BbxGraph* handle,
const float* const* inputs, // Pointer to pointer
float* const* outputs,
...
);
Rust converts to slices without copying:
#![allow(unused)] fn main() { unsafe { let input_slice = std::slice::from_raw_parts(inputs[ch], num_samples); let output_slice = std::slice::from_raw_parts_mut(outputs[ch], num_samples); } }