The reverse direction is the harder credibility test. Most languages that
call C cannot be called from C without ceremony — a runtime to
initialize, a foreign-function veneer, reference-counting rules to obey at
every crossing. The example
CCallsMica
is a plain C program, compiled by cc, whose link line names three archives
— your Mica library, Mica’s standard library, and the Dragon runtime floor
beneath it — and that is the entire integration:
cc -std=c23 -o ccallsmica main.c ../build/Utilities/Utilities.a /usr/lib/mica-stdlib-arm64.a /usr/lib/libmicadragon-rt-arm64.a -lm
No Mica source in the build. No init function to call. The C program’s
main is in charge from the first instruction.
The naming rule
A Mica export’s linkage name is the build target’s name, two
underscores, and the exported name. The
Utilities library
builds a target called Utilities, so its exp function Fibonacci links
as:
extern int64_t Utilities__Fibonacci(int32_t n);
extern double Utilities__VectorMagnitude(double x, double y);
extern float Utilities__Sigmoid(float x);The header utilities_ffi.h in the example is exactly such declarations
plus a few typedefs — written by hand once, deliberately, so the example
shows the raw truth of the boundary with nothing generated in between. The
types map as the C ABI already says they must: int64 is int64_t,
float32 is float, bool is bool, a packed Mica record is a
__attribute__((packed)) struct, and a Mica string arrives as a
two-field descriptor — pointer and rune count:
typedef struct MicaStringView
{
const mica_char_t *Data;
uint64_t Length;
} MicaStringView;The run
Ordinal contract surface
pair const check = 1
wrapper pair state/level = busy / 4
Packed aggregate contract surface
payload = 5, 16, 4.25
lane = (21,30) (22,40)
lane total = 70
Text contract surface
row = zero | | two
wrapper = north | west / center / east | south
Summary
- Called one Mica library directly from C without an adapter runtime
- Demonstrated value, pointer, and const-pointer calls across the shared ABI
- Covered classic scalars, ordinals, packed data, arrays, records, and descriptor-based stringsNote what the sections mirror: they are the same value shapes the Mica-calls-C example sends the other way, printing the same numbers. One ABI story, told from both ends, agreeing — that symmetry is the boundary’s real test, and both examples are built by CI on every push so it cannot silently rot.
Deeper than a demo
This direction is not a courtesy feature; it is load-bearing. The Dragon SDK’s C consumers drive the compiler’s whole backend through exactly this boundary, and the PL/0 compiler that certifies the SDK exists twice — once in C, once in Mica — precisely because a C program linking Mica archives is a first-class citizen. When your build system, your test harness, or your twenty-year-old application is C, Mica joins it, not the other way around.
And since 6.12.6, the sandwich composes: a Mica program can hand a C library a callback road back into its own exports — C calling Mica inside Mica calling C — which the debugging tutorial walks with a debugger attached.
What this does not do
- Non-exported names do not exist. The archive publishes
exp-marked declarations and nothing else — the same rule the Mica side lives by, enforced by the symbol table itself. - The header is on your honor. Like every
externdeclaration in C, a wrong prototype is your foot and your trigger. Keep the declarations in one header beside the archive, and change them only when the library’s contract changes. - The stdlib archive joins the link. Mica code needs its runtime and
standard library; they arrive as one more
.aon the line — no shared runtime daemon, no environment, just an archive.
Try it
git clone https://gitlab.com/mica-lang/mica-container.git
make -C mica-container/examples/CCallsMica runAdd extern int64_t Utilities__Fibonacci(int32_t); to any C project you
own, link the two archives, and call it. That — genuinely — is the whole
procedure.
Next
Mica calls Linux — the boundary at its most
worn: system calls, and the errno convention the compiler lifts so your
program never sees a -1.