Appearance
C
There is no C SDK and none is needed: the runtime exposes five functions, and messages are JSON text. Rust, Go, Zig and Nim use this same interface; their pages reference the message formats documented here.
Install
bash
tar xzf guido-0.1.0-linux-x86_64.tar.gz
./guido-0.1.0-linux-x86_64/install.shThis installs ~/.local/lib/libguido.so (the runtime), ~/.local/include/guido.h (the header), and guido on PATH.
The API
c
int guido_start(int argc, const char **argv); /* boot the engine */
int guido_iteration(void); /* render one frame */
int guido_send(const char *json_line); /* send a message */
const char *guido_recv(void); /* next reply or NULL */
void guido_stop(void); /* shut down */Messages look like {"jsonrpc":"2.0","id":1,"method":"instantiate","params":{"className":"Button"}}. The full operation list is in the protocol reference; detailed function semantics are in the C API reference.
Example
A button that responds to clicks:
c
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static int (*guido_start)(int, const char **);
static int (*guido_iteration)(void);
static int (*guido_send)(const char *);
static const char *(*guido_recv)(void);
static void (*guido_stop)(void);
int main(void) {
char lib[512];
snprintf(lib, sizeof lib, "%s/.local/lib/libguido.so", getenv("HOME"));
void *h = dlopen(lib, RTLD_NOW | RTLD_LOCAL);
if (!h) { fprintf(stderr, "%s\n", dlerror()); return 1; }
*(void **)&guido_start = dlsym(h, "guido_start");
*(void **)&guido_iteration = dlsym(h, "guido_iteration");
*(void **)&guido_send = dlsym(h, "guido_send");
*(void **)&guido_recv = dlsym(h, "guido_recv");
*(void **)&guido_stop = dlsym(h, "guido_stop");
const char *args[] = { "--no-project" };
if (guido_start(1, args) != 0) return 1;
guido_send("{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"handshake\","
"\"params\":{\"clientInfo\":{\"name\":\"c\",\"version\":\"0\"}}}");
guido_send("{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"instantiate\","
"\"params\":{\"className\":\"Button\"}}");
char root[32] = "", button[32] = "";
const char *line;
while (guido_iteration() == 0) {
while ((line = guido_recv()) != NULL) {
/* strstr matching keeps the example dependency-free; use a JSON
* parser (cJSON, jansson) in a real application. */
if (strstr(line, "\"id\":1")) {
sscanf(strstr(line, "\"rootId\":\""), "\"rootId\":\"%31[0-9]", root);
} else if (strstr(line, "\"id\":2")) {
sscanf(strstr(line, "\"oid\":\""), "\"oid\":\"%31[0-9]", button);
char buf[512];
snprintf(buf, sizeof buf,
"{\"jsonrpc\":\"2.0\",\"method\":\"batch\",\"params\":{\"ops\":["
"{\"method\":\"set\",\"params\":{\"oid\":\"%s\",\"property\":\"text\",\"value\":\"Click me\"}},"
"{\"method\":\"addChild\",\"params\":{\"parent\":\"%s\",\"child\":\"%s\"}}]}}",
button, root, button);
guido_send(buf);
snprintf(buf, sizeof buf,
"{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"connect\","
"\"params\":{\"oid\":\"%s\",\"signal\":\"pressed\"}}", button);
guido_send(buf);
} else if (strstr(line, "\"method\":\"signal\"")) {
printf("clicked\n");
}
}
}
guido_stop(); /* the loop ends when the window is closed */
return 0;
}bash
cc -O2 -o app main.c -ldl && ./appA version with proper JSON handling and both linking modes is in the repo at examples/embedding/c/.
Scenes from the Godot editor
Load screens designed in the editor and address their nodes by name (background: Custom scenes & assets). These message shapes are the same in every compiled language:
json
{"jsonrpc":"2.0","id":10,"method":"loadPack","params":{"path":"/abs/app.pck"}}
{"jsonrpc":"2.0","id":11,"method":"instantiateScene","params":{"path":"res://main.tscn"}}
{"jsonrpc":"2.0","method":"addChild","params":{"parent":"<rootId>","child":"<scene oid>"}}
{"jsonrpc":"2.0","id":12,"method":"call","params":{"oid":"<scene>","method":"get_node","args":["%SaveButton"]}}
{"jsonrpc":"2.0","id":13,"method":"connect","params":{"oid":"<node from id 12>","signal":"pressed"}}call returns its value under "result". Objects arrive as {"$oid":"..."}; send the same wrapper to pass an object as an argument:
json
{"jsonrpc":"2.0","id":14,"method":"load","params":{"path":"/abs/themes/adwaita_dark.tres"}}
{"jsonrpc":"2.0","method":"set","params":{"oid":"<rootId>","property":"theme","value":{"$oid":"<theme oid>"}}}During development, instantiateScene also accepts an absolute .tscn path, without a pack.
Static linking
Linking libguido.a produces one self-contained ~22 MB executable:
make
cc -O2 -DGUIDO_STATIC -I$HOME/.local/include -o app main.c libguido.a \
-static-libgcc -static-libstdc++ -lstdc++ -lm -lpthread -ldl \
-Wl,--gc-sections -sWith -DGUIDO_STATIC, guido.h declares the functions directly; remove the dlopen block and the rest of the code is unchanged. The archive is distributed separately from the tarball; see Building from source.
Notes
guido_start,guido_iterationandguido_stopmust be called from one thread.guido_sendis thread-safe.- The pointer returned by
guido_recvis valid until the next call; copy the string before receiving again. - Include an
"id"in messages that need a reply; omit it for fire-and-forget operations. Batch fire-and-forget operations into a singlebatchmessage per frame when making many updates. - Route incoming
signalnotifications bysubId(from theconnectreply), not by signal name. - Shutdown: after the last sends, run two more
guido_iterationcalls so queued operations are applied, thenguido_stop(), then drainguido_recv()once for the finalquitnotification. - Objects that were instantiated but neither freed nor added to the tree are reported on stderr at exit.