Spec index | Previous: Async & Coroutine Policy | Next: .NET
C Bindings Implementation Blueprint¶
What this chapter defines — the shape the C binding must take on top of the
core/include/zlink.hABI, and its review rules.
This document defines the shape the C binding must take. It does not
duplicate every public function signature — the concrete public API
contract is core/include/zlink.h.
In C, the native ABI itself is the binding contract. bindings/c does not
add a second contract/runtime layer on top of the core C API. The C
implementation is considered aligned once the public header, native
library behavior, tests, samples, packaging, and perf runner all match this
document and the rules in core/include/zlink.h.
The shared bindings architecture map still holds as review vocabulary.
core, messaging, sockets, eventing, service, and errors are the conceptual
areas a reviewer uses when reading the C header. C expresses these areas
not as separate contracts/ and runtime/ folders, but through header
sections, type/function prefixes, tests, samples, and documentation
sections.
The shared file-granularity policy is only review vocabulary for C header
sections and helper files. Because core/include/zlink.h remains the ABI
baseline, it does not require wrapper-style contracts/ or runtime/
folders.
| Section | Covers |
|---|---|
| Public contract source | The boundary between public ABI, internal implementation, and documentation's role |
| Repository structure | The paths used when changing the C binding |
| API change procedure | The order for adding or changing a feature |
| Library shape | Native ABI function naming, flag, and multipart rules |
| Interface shape exceptions | Where the higher-level binding wrapper rules don't apply to C |
| Byte HWM and Auto-HWM | Core ABI byte-HWM configuration, calculation, and admission rules |
| Receive flow state | The receive-flow state enum, function, results, and monitor surface |
| Required feature coverage | The header feature groups a review checks |
| Spot Get-Or-New | The zlink_spot_node_spot_get_or_new contract |
| Ownership and lifecycle | Handle/message ownership transfer rules |
| Error and Result policy | The C result domain, and why no exceptions |
| Performance policy | Why C is the performance baseline for the other bindings |
| Implementation checklist | What to confirm before declaring alignment |
| Actor and Spot route results | The route-result structs and the routing helper policy |
Public contract source¶
- Public contract:
core/include/zlink.h. - Public ABI: the exported
zlink_*functions, public structs, enums, constants, callback typedefs, and ownership rules that header declares. - Internal implementation: files under
core/src/, private helper headers, generated bridge files, build scripts, test helpers. - Documentation's role: this README describes the C shape and review rules. The header owns the exact list of binding signatures.
No second C facade is introduced on top of zlink.h. A local alias function
that just forwards to another zlink_* function, an alternate option bag,
or a compatibility wrapper is not part of the binding contract.
Repository structure¶
Use the following paths consistently when changing the C binding.
- Public contract:
core/include/zlink.h. - Runtime implementation:
core/src/. - Native artifacts:
core/build. - Bindings include projection:
bindings/c/include/(when packaging needs installed headers). - Tests:
bindings/c/tests/. - Samples:
bindings/c/samples/. - perf:
bindings/c/perf/. - C API mapping, sample/test support, and perf policy live under
bindings/c/.
Temporary build directories and generated output are not contract locations.
zlink/
+-- bindings/
| +-- c/
| | +-- include/
| | +-- tests/
| | +-- samples/
| | +-- perf/
+-- core/
| +-- include/
| | +-- zlink.h
| +-- src/
| +-- build/
API change procedure¶
When adding or changing a C feature:
- Add or update the public declaration in
core/include/zlink.h. - Implement the behavior under
core/src/. - Update the errno/result documentation if the result domain changed.
- Add a test that includes only the public header.
- Update a sample only if the user-facing shape changed.
- Update the perf runner only if the measured behavior changed.
- Rebuild
core/buildbefore interpreting C perf results.
Library shape¶
The C binding keeps the native ABI shape.
- Function names use
zlink_*andsnake_case. - Blocking vs. non-blocking behavior is chosen with a flag such as
ZLINK_DONTWAIT, not a separate publictry_*function. - The send path returns
zlink_submit_result_tor a documented request result. - The recv path returns
zlink_recv_result_tand fills a caller-owned output storage per the header contract. - Multipart payloads use a
zlink_msg_tarray and part count in one call. - Routing APIs use an explicit routing id parameter and an explicit output routing id storage.
- A callback API exposes a C function pointer and userdata only when the public header declares it.
Higher-level object convenience forms such as Received.Reply(...),
Socket.Send().Message(...).Submit(), and Spot.Publish(topic) do not
apply to C. Those shapes belong to the higher-level bindings.
Interface shape exceptions¶
C is the ABI baseline and does not adopt the wrapper-binding interface rules.
- Receive and subscribe use the output parameters
zlink.hdeclares. - Send, publish, request, and reply use explicit
zlink_*functions and flags. - C does not expose an operation builder, a staged interface, or a fluent helper object.
- The wrapper-binding rules for public static facades, builder convenience helpers, and contract/runtime folders do not apply to C. A public C helper must be declared in
core/include/zlink.h; otherwise it is an internal helper. - C samples and perf include the public header and call the public C ABI directly.
Byte HWM and Auto-HWM¶
In C, the Core ABI provides the HWM (the queue byte threshold) contract directly. ZLINK_OPT_SNDHWM
and ZLINK_OPT_RCVHWM are uint64_t accounted-byte limits, and
zlink_set_option() and zlink_get_option() receive exactly 8 bytes of
storage. The manual default is 4,096,000 bytes, and 0 means unlimited.
The context memory limit and Core budget use byte-valued uint64_t options; the profile uses
its canonical option. Planning, manual overrides, and admission follow
Core HWM calculation and admission.
In zlink_monitor_status_t ABI version 4, planned, applied, and deferred HWM
values and in-flight usage use bytes. snd_pending_msgs and
rcv_pending_msgs are display counts, not admission inputs. No message-unit,
slot, size-cap, or connection-bucket diagnostic is exposed.
zlink_socket_monitor_open_options_t.monitor_hwm_bytes is the only monitor
queue HWM option. Zero selects the Core default; a positive value is forwarded
unchanged as the exact byte limit. No message-count alias or conversion is
provided.
Receive flow state¶
C exposes zlink_receive_flow_state_t and the following Core function unchanged.
typedef enum zlink_receive_flow_state_t
{
ZLINK_RECEIVE_FLOW_RUNNING = 0,
ZLINK_RECEIVE_FLOW_PAUSED = 1
} zlink_receive_flow_state_t;
ZLINK_EXPORT zlink_config_result_t zlink_socket_set_receive_flow_state (
void *handle_, zlink_receive_flow_state_t state_);
The return value is zlink_config_result_t; zlink_errno() provides the detailed error.
State, result, and monitor projection follow the common receive-flow contract.
The Core monitoring ABI
owns the C enum, event, flag, and status-field declarations.
Required feature coverage¶
A C review checks the following groups in core/include/zlink.h.
- Runtime, version, capability lookup, context lifecycle, context options.
- Message lifecycle, message data access, copy/move/adopt rules, attribute lookup.
- Socket lifecycle, bind/connect, disconnect, options, TLS helpers, routing id, send, receive, request, reply, publish, subscribe, stream API.
- Eventing API: monitor, poller, timer, pull receive, and readiness semantics.
- SPOT node, SPOT handle, topology snapshot, actor, service-layer API.
- error/result enums and errno mapping.
If a feature exists in core/include/zlink.h, the C binding exposes it
directly through the public header. A feature not in that header is not
public C API.
Spot Get-Or-New¶
bindings/c/include/zlink.h exposes zlink_spot_node_spot_get_or_new(...)
with the same signature and result contract as the core public header.
This function atomically gets or creates a local logical Spot by routing
id, and returns both a caller-owned Spot facade handle and a
was-it-created flag.
This API does not join an actor to the Spot — join remains a separate service operation.
Ownership and lifecycle¶
A C caller owns memory explicitly. The header makes ownership transfer explicit at every boundary.
- A
zlink_msg_tvalue is initialized before use and closed exactly once. - An API that moves or adopts message storage documents the source object's state after the call.
- A recv API fills caller-provided storage. The caller closes any message part whose ownership was transferred to it.
- A handle is closed through the matching
zlink_*_close,zlink_*_destroy,zlink_ctx_term, or a documented lifecycle function. - Pull receive and poller use do not require the caller to know private worker, socket, or inproc endpoint details.
Error and Result policy¶
The C binding reports public results as a C result domain, not exceptions.
- Transient no-data is reported as a documented recv result.
- Transient backpressure is reported as a documented submit result.
- Configuration, bind, connect, close, request, handler, and recv failures map to the result and errno rules in
zlink.h. - The public header does not require a caller to inspect private implementation state to classify a failure.
Performance policy¶
The C binding is the performance baseline for the other bindings.
- The hot path uses the caller-provided whole-message array directly and does not build another intermediate collection containing the same payload.
- The send/recv, request, dispatch, poller, timer, stream, SPOT, and actor paths do not add hidden sleeps, busy waits, thread joins, reflection-like dynamic dispatch, coarse global locks, or avoidable copies.
- The perf runner and samples include only the public header.
bindings/c/perfmeasurescore/build's runtime unless the perf policy explicitly says otherwise.
Implementation checklist¶
Before declaring the C binding aligned:
core/include/zlink.hdeclares the exact public C ABI.- The header documentation and the errno/result documentation agree.
- Public tests and samples compile without private headers.
- A higher-level binding can implement its own public shape without calling private C helpers.
- Perf output prints the runtime library path and does not run against a stale
core/buildruntime.
Actor and Spot route results¶
The C binding exposes the core route-result structs directly as public ABI.
zlink_actor_route_tcarries a resolved Actor ref. Besidesactor.node_rid, it includescurrent_spot_ridandcurrent_spot_kind.zlink_spot_route_tcarries the requestedspot_rid,owner_node_rid, andspot_kind.zlink_spot_kind_tdistinguishes an Entry Spot from a user Spot. An invalid kind is not a successful Actor or Spot route result.- A C sample that routes by Actor id first resolves the Actor, then passes
actor.node_ridandcurrent_spot_ridto the existing Spot routed API.
The C binding does not add zlink_router_send_actor,
zlink_router_request_actor, or an Actor-to-ROUTER request helper.
Actor-directed delivery is handled by route lookup followed by the
existing Spot routed send/request.
Pull completion and STREAM packets¶
The C header and library ABI version follows Core release metadata.
C exposes REQUEST and WRITABLE through a zlink_completion_t output.
Core completion pull and ownership
owns the receive and cleanup functions, readiness, and single drain owner.
SEND/REQUEST results, IDs, wait tokens, input retention, and resubmission conditions follow
Core whole-message send and
Core request.
C adds no language terminal or completion registry.
A ROUTER REQUEST receive returns a nonzero zlink_reply_token_t. The token is an opaque capability
scoped to the responder ROUTER socket and source logical RID. A DATA token is 0. After receiving every
part of a REQUEST, the application passes the source RID and token unchanged to reply.
STREAM selects RAW or PACKET receive mode before its first successful bind/connect. In PACKET
mode, zlink_stream_recv_packet() moves owned messages into caller-prepared empty header/body outputs
and returns the source RID as a borrowed view that remains valid until the next data-receive entry.
Public interface¶
typedef uint64_t zlink_completion_id_t;
typedef uint64_t zlink_reply_token_t;
typedef enum zlink_completion_kind_t {
ZLINK_COMPLETION_SEND = 1,
ZLINK_COMPLETION_REQUEST = 2,
ZLINK_COMPLETION_WRITABLE = 3
} zlink_completion_kind_t;
typedef enum zlink_send_complete_result_t {
ZLINK_SEND_ADMITTED = 0,
ZLINK_SEND_TERMINAL = 202
} zlink_send_complete_result_t;
typedef struct zlink_completion_t {
uint32_t struct_size;
zlink_completion_kind_t kind;
zlink_completion_id_t completion_id;
void *user_context;
zlink_routing_id_t peer_rid;
zlink_send_complete_result_t send_result;
int send_terminal_errno;
zlink_request_result_t request_result;
zlink_msg_t *reply_parts;
size_t reply_part_count;
} zlink_completion_t;
ZLINK_EXPORT zlink_submit_result_t zlink_send(
void *s_,
zlink_msg_t *parts_,
size_t part_count_,
zlink_send_flags_t flags_,
void *user_context_,
zlink_completion_id_t *completion_id_out_);
ZLINK_EXPORT zlink_submit_result_t zlink_send_rid(
void *s_,
const zlink_routing_id_t *target_rid_,
zlink_msg_t *parts_,
size_t part_count_,
zlink_send_flags_t flags_,
void *user_context_,
zlink_completion_id_t *completion_id_out_);
ZLINK_EXPORT zlink_submit_result_t zlink_request(
void *s_,
const zlink_routing_id_t *target_router_rid_or_null_,
zlink_msg_t *parts_,
size_t part_count_,
zlink_send_flags_t flags_,
uint32_t timeout_ms_,
void *user_context_,
zlink_completion_id_t *completion_id_out_);
ZLINK_EXPORT zlink_submit_result_t zlink_reply(
void *router_,
const zlink_routing_id_t *source_rid_,
zlink_reply_token_t reply_token_,
zlink_msg_t *parts_,
size_t part_count_);
ZLINK_EXPORT zlink_recv_result_t zlink_completion_recv(
void *s_,
zlink_completion_t *completion_out_,
zlink_recv_flags_t flags_);
ZLINK_EXPORT void zlink_completion_close(
zlink_completion_t *completion_);
ZLINK_EXPORT zlink_recv_result_t zlink_router_recv(
void *router_,
const zlink_routing_id_t **source_rid_out_,
zlink_reply_token_t *reply_token_out_,
zlink_msg_t *parts_out_,
size_t parts_capacity_,
size_t *part_count_out_,
zlink_recv_flags_t flags_);
typedef enum zlink_stream_recv_mode_t {
ZLINK_STREAM_RECV_MODE_UNSPECIFIED = 0,
ZLINK_STREAM_RECV_MODE_RAW = 1,
ZLINK_STREAM_RECV_MODE_PACKET = 2
} zlink_stream_recv_mode_t;
typedef enum zlink_stream_option_t {
ZLINK_STREAM_OPT_NOTIFY = 0x3501,
ZLINK_STREAM_OPT_RECV_MODE = 0x3502
} zlink_stream_option_t;
ZLINK_EXPORT zlink_recv_result_t zlink_stream_recv_packet(
void *stream_,
const zlink_routing_id_t **source_rid_out_,
zlink_msg_t *header_out_,
zlink_msg_t *body_out_,
zlink_recv_flags_t flags_);
The caller zero-initializes a zlink_completion_t output and sets struct_size. After a successful
recv, the caller closes SEND records as well with zlink_completion_close(). For a REQUEST OK
payload, reply_parts is a Core allocator base; the caller does not free the array directly. Close
releases remaining messages and the array, then restores an empty aggregate while preserving
struct_size.
Only the following names and values appear in the public enum for pending options.
The public C ABI contains no zlink_send_async*, zlink_send_complete_handler,
zlink_send_complete_handler_fn, send/request/recv/STREAM/monitor/timer callback types or registration
functions, dealer/router-specific request/reply or exact-pair APIs, or _v2 recv. Pending options also
contain no SEND_PENDING name.
Monitor and timer use a pull lifecycle. Monitor provides zlink_socket_monitor_open(),
zlink_socket_monitor_recv(), zlink_monitor_status(), and zlink_monitor_close(). Timer provides
zlink_timer_new(), zlink_timer_start(), zlink_timer_stop(), zlink_timer_recv(), and
zlink_timer_destroy(). Monitor connection_id is used only for diagnostics and correlation, not as a
send/reply target or reconnect fence.
Implementation and contract-test verification requirements¶
Verify the following using only the public C ABI, return values, errno, and poller events. Each item maps to one contract test.
Submit and completion
Submit results, IDs, and REQUEST/WRITABLE observations follow the
Core submit/completion verification requirements.
- ZLINK_POLLCOMPLETION does not consume a record in wait. Once a DONTWAIT drain empties the queue,
recv returns ZLINK_RECV_NO_DATA with EAGAIN.
- Closing an output after successful completion recv restores an empty aggregate that preserves
struct_size, and the same output can be reused.
Reply token and STREAM
- ROUTER DATA recv returns token
0; every multipart part of a REQUEST returns the same nonzero token. - Starting a reply with a token from another responder socket or source RID makes native submit fail.
- STREAM bind/connect fails while mode is
UNSPECIFIED; after settingRAWorPACKET, only the corresponding recv family succeeds. - Successful PACKET recv returns RID, header, and body.
NO_DATAand errors leave the caller's empty outputs unchanged.
Pull eventing
- Monitor and timer recv return ready events and fire counts through pull and distinguish DONTWAIT no-data in their respective recv results.