apple_block.h
An Apple block, built by hand, for the APIs that take one.
#include <sonore/apple_block.h>- Guides: Platforms and formats
Design notes
Why this exists
A few of Apple's audio APIs take or hand out a BLOCK -- clang's closure extension, written ^(args) { ... } -- where everything else in them takes a function pointer and a context:
AudioComponentInstantiate its completion handler (host_au.h)
kAudioUnitProperty_RequestViewController an AUv3's editor (host_au.h)
kAudioUnitProperty_RenderContextObserver the render thread's workgroup
(au_wrapper.h)
This SDK is plain C++ with no .mm file anywhere, and the ^ syntax needs -fblocks. Apple clang turns that on by default, but the only compiler that reads the Mac code in this loop -- MSVC, against tests/au_shim -- cannot parse it, and code no compiler reads is how au_wrapper.h spent weeks unbuildable. So a block is built here the way the compiler builds one, from the published ABI, exactly as the Cocoa files send messages through objc_msgSend rather than in Objective-C.
The ABI
clang's "Block Implementation Specification" (Block-ABI-Apple), unchanged since Mac OS X 10.6. Every block begins
void* isa; the block's class
int flags;
int reserved;
R (*invoke)(block*, args...); called with the block itself first
descriptor*; { reserved, size } at minimum
and its captured variables follow. Calling a block is loading invoke and calling it with the block's own address as the first argument -- which is how the captures are reached.
Why every block here is GLOBAL, captures and all
A block handed to an API is copied, retained or released under rules that are documented unevenly: AudioUnitGetProperty's "Copy rule", ARC's objc_retainBlock, a plain Block_copy. A block whose flags carry BLOCK_IS_GLOBAL is immune to every one of them: _Block_copy returns the same pointer, _Block_release does nothing, and -[__NSGlobalBlock__ retain], -release and -copy are all identities. So nothing anybody does to it can free it, or copy it somewhere its captures are not.
The price is that the block lives where WE put it -- inside an object of ours -- and that object has to outlive every call the API can make. Each caller says how it guarantees that; host_au.h keeps a request alive until its completion has run, and au_wrapper.h keeps the observer inside the instance the OS stops rendering before it disposes.
What it cannot do
arm64e. Pointer authentication signs a block's invoke pointer with the block's address, and a hand-built block has no signature. Third-party code on macOS is arm64 or x86_64 -- arm64e is reserved for Apple's own binaries -- so this is refused at compile time rather than crashing at the first call.
Compiled, never run
There is no Mac in this loop. tests/mac_host_selftest.cpp is the program a Mac runs to check this file: it hands one of these to _Block_copy and calls the result.
Nothing public is declared here: the header supports the others in its area, and its notes above say how.