Embedding › 04 Running code

Running code

Start entry classes, call individual methods, pass arguments, and read results and exit codes.

1. Entry classes with vm_run

ClassMeta* entry = loth_engine_entry(e, "com.example.App");
if (!entry) { /* not found, or does not implement Main */ }
int rc = vm_run(vm, entry, argc, argv);

2. Calling methods with vm_call

Not every component has a Main. To call a method on a class, obtain the singleton (or a new instance) and call it:

Value svc = vm_get_singleton(vm, "com.example.Service");
Value out = NIL_VAL;
int r = vm_call(vm, svc, "handle", args, argc, NULL, NULL, &out);
if (r == 1) {
    /* the method threw; the exception object is in out */
} else if (r == -1) {
    /* no such method, or the receiver is not an object */
}

3. Command-line arguments and exit codes

The host decides what to pass. The example passes none. A host that forwards its own arguments can do this:

int rc = vm_run(vm, entry, argc - 1, argv + 1);
return rc;                       // the language program controls the exit code

By convention run returns 0 for success. The CLI treats a usage error as exit code 2 and an uncaught exception as 1, and hosts are encouraged to follow the same convention.

4. Output

Std.print writes through a process-wide channel. By default it goes to stdout under a lock, so a single print call never interleaves with another. A host can take over per VM:

static void collect(void* user, const char* data, size_t len) {
    // append data[0..len) to your own buffer
}

vm->out_fn = collect;
vm->out_user = my_buffer;

The callback may be invoked from any thread, so it must be thread-safe. This is how the multi-VM example keeps each VM's output separate.

5. Getting data back

Values cross the boundary as Value, an untagged union. The host must know the type: a method that returns int puts it in out.i; a reference in out.o. For strings and objects, the same allocation, string and root APIs the native track describes are available to hosts.