macro_rules! export_bytes_getter {
(
$(#[$meta:meta])*
$slot:path,
$name:ident($handle:ident: $handle_ty:ty, $key:ident, out $out_len:ident $(,)?),
$body:block
) => { ... };
}Expand description
Declare an entry point that hands out an owned byte buffer selected by a C-string key.
The signature is fixed — (handle, key, out_len) -> *mut u8 — because that is the
shape every consumer of this ABI already exports, and only the names come from the call
site. An entry point needing a different shape is written by hand.
$body evaluates to Result<Vec<u8>, FfiError>. The macro moves that into a
Box<[u8]>, writes its length through out_len, and hands over the pointer; the
matching export_free_bytes! reclaims it from the same two
values.
An empty Vec is a success like any other: it is handed over as a non-null pointer with
out_len set to 0, distinct from the null this returns on failure. A caller that
treats a zero length as absence will misread an empty result as one that was never
produced.
§Why every argument is checked before the closure
The failure path writes *out_len = 0, so when an argument is rejected is
observable. All three null checks therefore happen before
catch runs, and a rejected argument leaves the caller’s length
variable exactly as they left it — “not attempted” stays distinguishable from
“attempted and produced nothing”.
That is also why $body reads the key with
ffi::c_str_utf8 rather than
with_c_str!: the null check is already done, and the UTF-8
conversion belongs inside the closure, where its failure does zero the length.
unparser_shared::export_bytes_getter!(
/// The bytes of the entry named `name`.
///
/// # Safety
///
/// - `doc` must be a valid handle.
/// - `name` must be a valid null-terminated UTF-8 string.
/// - `out_len` must point to storage for the length.
/// - The returned pointer must be freed with `demo5_free_bytes`.
LAST_ERROR,
demo5_entry(doc: Demo5Document, name, out out_len),
{
let name = unsafe { unparser_shared::ffi::c_str_utf8(name) }?;
match unsafe { (*doc).inner.entries.get(name) } {
Some(bytes) => Ok(bytes.clone()),
None => Err((unparser_shared::kind::OTHER, format!("no entry {name}"))),
}
}
);
let mut entries = HashMap::new();
entries.insert("a.bin".to_string(), vec![1u8, 2, 3]);
let doc = Box::into_raw(Box::new(Demo5Document { inner: Archive { entries } }));
let name = CString::new("a.bin").unwrap();
let mut out_len = 0usize;
let bytes = unsafe { demo5_entry(doc, name.as_ptr(), &mut out_len) };
assert_eq!(out_len, 3);
assert_eq!(unsafe { std::slice::from_raw_parts(bytes, out_len) }, [1, 2, 3]);
unsafe {
demo5_free_bytes(bytes, out_len);
demo5_free_document(doc);
}