MESH ONLINE│CODENAME: Breaking the Law
v0.45

C — Headers and Linking

The C surface is not one header. It is eleven — and they all resolve out of a single shared library, libnet. One of the header pairings is mutually exclusive. This page is the map.

The one decision that bites

net.h and net.go.h share the NET_SDK_H include guard, so only one of them can be active in a translation unit. net.go.h is the broader surface — but it is not a strict superset: net_ingest_raw_ex, net_poll_ex and net_stats_ex exist only in net.h.

Pick one per translation unit based on the symbols you need. If you need both surfaces in one program, split them across separate translation units.

Every other header has its own guard and composes freely.

The headers

HeaderGuardSurfaceLibrary
net.hNET_SDK_HNarrow event bus — init / ingest / poll / stats / shutdownlibnet
net.go.hNET_SDK_HMesh + compute — sessions, channels, capabilities, NAT, daemon dispatch, placement filters, predicate helperslibnet
net_cortex.hNET_CORTEX_HRedEX logs, CortEX Tasks/Memories adapters, NetDb bundle + snapshotlibnet
net_transport.hNET_TRANSPORT_HBlob and directory transfer over the stream transportlibnet
net_rpc.hNET_RPC_HnRPC request/responselibnet
net_meshdb.hNET_MESHDB_HFederated query layer over capability queries + CortEX foldslibnet
net_meshos.hNET_MESHOS_HDaemon-author SDK — operator handle + control-event channellibnet
net_deck.hNET_DECK_HDeck operator-side SDKlibnet
net_org.hNET_ORG_HOrganization capability auth — own NET_ORG_ABI_VERSION; pair with net_rpc.h for the streaming verbslibnet
net_subnet.hNET_SUBNET_HSubnet authority — exported serve + gateway provisioninglibnet
net_mcp.hNET_MCP_HMCP bridge helpers, graduated consent / pin surfacelibnet

net_cortex.h is deliberately self-contained — it depends only on <stdint.h> and <stddef.h>, so a consumer who wants just the storage slice can include it without dragging in the mesh and compute surface from net.go.h. Its symbols resolve when the cdylib is built with --features "netdb redex-disk".

net.go.h is the in-crate mirror of go/net.h at the repo root, and pulls in net_cortex.h for its storage half.

Every header resolves out of the same libnet. That is not a simplification of the table — it is the whole shipping model, and the reason is in One library, on purpose below.

net_subnet.h shares net_org.h's error namespace (NET_ORG_ERR_SUBNET) and handle model, and #includes it. The subnet-exported caller verb, net_org_call_exported, is declared in net_org.h because it takes the org client handle.

net_org.h carries its own ABI stamp, versioned independently of net_rpc.h: NET_ORG_ABI_VERSION is 0x0002. Call net_org_check_abi_version(NET_ORG_ABI_VERSION) at init and hard-fail on a mismatch — the check is exact equality, so a consumer built against an older expectation is refused rather than waved on and must be rebuilt against the current headers. Include net_rpc.h beside net_org.h when you use the org streaming verbs (net_org_call_streaming / net_org_call_client_stream / net_org_call_duplex): they hand out and drive the shared nRPC handle types, so code that drains a public stream drains an org stream unchanged.

Building the library

▸ shell
cargo build --release -p net-ffi

One command, one library, every header served.

Output lands in target/release/:

PlatformFilenameAlso produced
Linuxlibnet.so—
macOSlibnet.dylib—
Windowsnet.dllnet.dll.lib (import library), net.pdb

On Windows the DLL is not the thing you link against. net.dll.lib is the import library, and it is what the linker needs; net.dll is what the loader needs, later, at run time. Missing either produces a different failure, and they are covered separately below.

Compiling against it

▸ shell
gcc -o app app.c -L target/release -lnet -lpthread -ldl -lm

-lnet regardless of which headers you included. There is no second -l to add.

On Windows, pick the form that matches your toolchain:

▸ bat
:: MSVC — link the import library by name
cl /I include app.c target\release\net.dll.lib
▸ shell
# MinGW / GCC — `-lnet` resolves against net.dll directly.
gcc -o app.exe app.c -Iinclude -Ltarget/release -lnet

-lpthread -ldl -lm are not Windows libraries; leave them off. If your ld is old enough that -lnet does not find net.dll, generate a GNU import library once and link that instead:

▸ shell
gendef net.dll                              # writes net.def
dlltool -d net.def -D net.dll -l libnet.dll.a

One library, on purpose

Net used to ship a cdylib per surface — libnet_rpc, libnet_org, libnet_meshdb and the rest — and this page used to tell you to link the ones you needed alongside -lnet. Do not do that, on any version. If you have a build script carrying those flags, drop them.

Each of those libraries statically embedded its own copy of the core, and re-exported the core's symbols. Linking two therefore put two copies of Net's internal state in one process. The exported functions deduplicate — the dynamic linker resolves each name once — which is why the arrangement worked for a long time and why it was documented here in good faith.

statics do not deduplicate. Each copy keeps its own. One of them is the registry parking_lot uses to track threads blocked on a lock: a thread that blocks through one copy is recorded in that copy's registry, and an unlock that runs through the other consults a registry the waiter was never in. The lock is released; the waiter is never woken.

It surfaces as a hang with no visible cause. The case that found it sat ten minutes inside a capability announcement while every worker thread in the process was idle — a held lock owned by nobody. It is timing-dependent, so it passes far more often than it fails.

One library makes that unreachable. The per-surface libraries are no longer built.

Finding it at run time

Linking is not the last step. The executable records the library by name, and the loader has to find it when the process starts:

▸ shell
LD_LIBRARY_PATH=target/release ./app       # Linux
DYLD_LIBRARY_PATH=target/release ./app     # macOS

Windows has no equivalent variable — it searches the executable's own directory first, then PATH. So do one of these:

▸ bat
:: Either put the DLL beside the executable — what you ship
copy target\release\net.dll .
app.exe
 
:: Or point PATH at the build output — what you do while developing
set PATH=%CD%\target\release;%PATH%
app.exe

Skip it and the program dies before main. The failure is quiet and easy to misread — under MSYS/MinGW it looks like this, and a plain cmd.exe shows a dialog or nothing at all:

▸ text
$ ./app.exe
app.exe: error while loading shared libraries: net.dll: cannot open shared
object file: No such file or directory
[exit=127]

Nothing there says the link succeeded and only the loader failed, so it is worth recognizing: exit 127 naming net.dll means the DLL is not on the search path, not that anything is wrong with your build.

Windows toolchains CI runs

The C consumer programs are built and run on Windows with each of these, so any of them is a supported way to build against net.dll:

ToolchainC runtimeLink
MSVC, /MDUCRTnet.dll.lib
MSVC, /MDd (debug)debug UCRTnet.dll.lib
MinGW-w64 (MSYS2 ucrt64)UCRTnet.dll directly
MinGW-w64 (MSYS2 mingw64)MSVCRTnet.dll directly

Mixing C runtimes with net.dll is safe because no buffer crosses from one allocator to another: the library's buffers go back through its own free functions, and your callbacks' buffers through the deallocator you register. See Memory and Threading.

Examples in the repo

Every program here is built and run by CI against the C SDK bundle, using only what this page describes: headers by name, and -lnet.

FileWhat it shows
.claude/skills/net-event-bus/examples/hello.cThe event-bus quickstart loop
net/crates/net/examples/c/lifecycle.cA mesh node's lifecycle: new, handshake, start, shutdown, free
net/crates/net/examples/c/transfer.cBlob and directory transfer between two nodes
net/crates/net/examples/c/tree_range.cTree blobs, Reed-Solomon encoding and range reads
net/crates/net/examples/c/blob_callbacks.cA blob adapter written in C
net/crates/net/examples/c/rpc_callbacks.cnRPC handlers written in C

The other skill examples (.claude/skills/net-event-bus/examples/*.c) cover capabilities, channels, RedEX and organization streaming.

Next