denise-ffi
A stable C ABI for Denise — a cdylib for hosts that are not written in
Rust.
Denise's own backends are Rust and need none of this. This crate exists for the
other direction: a Win32 control inside an MFC application, a WinForms or VB6 host
reaching it through the ActiveX shim, an NSView in a Cocoa app, a Python or C#
panel on an embedded box. All of them speak C.
The shape of it
The host owns the window and the pixel buffer; Denise owns the widget tree and draws into whatever it is handed.
DeniseUi *ui = ;
uint64_t root = ;
;
/* per frame */
;
if
uint32_t message;
while
There is no Surface here and no event loop. Both belong to the host, and a
library that tried to own either would be unembeddable in exactly the places this
is for.
Rules the whole ABI keeps
- Handles are opaque. A
DeniseUi *comes fromdenise_ui_newand goes todenise_ui_free. Nothing else may free it. - A node is a
uint64_t, and0is never valid. Ids carry a generation, so an id kept past a remove fails to resolve rather than addressing whoever took the slot. - A message is a
uint32_tchosen by the host, and0means no message — a button given0emits nothing, which is what lets a widget exist without one. - Strings are NUL-terminated UTF-8 both ways. Invalid UTF-8 is
DENISE_ERR_INVALID, not a replacement character: silently mangling a host's text is worse than refusing it. - A negative return is a status, and every status has a message from
denise_status_message. - Nothing is thread-safe. One
DeniseUibelongs to the thread it was created on, which for every host this targets is the UI thread.
Panics do not cross
Every entry point catches unwinding and returns DENISE_ERR_PANIC. A panic is a
bug in Denise, and a bug in Denise should not take down a host process with unsaved
work in three other windows. The call did nothing; the Ui should be treated as
suspect and freed.
The header is the contract
include/denise.h is written by hand, not generated. A generated header follows
whatever the Rust happens to say this week, which is the opposite of what a stable
ABI means: the header is the thing that must not move, and the Rust is what gets
checked against it. tests/header.rs does the checking — every exported symbol
appears in both, with the same numbers for every key, role and constant.
DENISE_ABI_VERSION is bumped when a signature, a constant or a meaning changes.
Added functions do not bump it. A host that checks nothing else should check this.
Building against it
examples/ has a C program and a Makefile that links the cdylib and renders to
a PPM — which is also what CI runs, on every push, as the C ABI links and runs.
The crate is crate-type = ["cdylib", "rlib"], so a Rust caller can use it directly
too. unsafe is necessarily permitted here; every block carries a // SAFETY:
comment.
Where this sits
Wraps denise-ui.
denise-activex is the COM layer that
uses this shape for VB6 and MFC hosts.
Status
M5 complete. Part of Denise — see the repository README for the whole picture.
MIT licensed.