Skip to content

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.sh

This 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 && ./app

A 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 -s

With -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_iteration and guido_stop must be called from one thread. guido_send is thread-safe.
  • The pointer returned by guido_recv is 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 single batch message per frame when making many updates.
  • Route incoming signal notifications by subId (from the connect reply), not by signal name.
  • Shutdown: after the last sends, run two more guido_iteration calls so queued operations are applied, then guido_stop(), then drain guido_recv() once for the final quit notification.
  • Objects that were instantiated but neither freed nor added to the tree are reported on stderr at exit.