Native plugins › 02 Your first plugin

Your first plugin

Build a working native class from scratch, in four steps.

Step 1: declare the class

com/example/counter/Counter.loth:

%native com.example.counter.Counter
    void ___init(int start)
    void add(int d)
    int value()

The path must mirror the name. There are no bodies — the library provides them.

Step 2: implement the methods

counter.c — the handle holds the C state; the methods read arguments through typed accessors and write the result to ctx->out:

#include "vm.h"
#include "native.h"
#include <stdlib.h>

typedef struct { int64_t value; } Counter;

static Counter* self(NativeCtx* ctx) {
    ObjNative* o = (ObjNative*)ctx->recv.o;
    if (!o->handle) {
        Counter* c = calloc(1, sizeof(Counter));
        if (!c) return NULL;
        o->handle = c;
    }
    return (Counter*)o->handle;
}

static void nd_init(NativeCtx* ctx) {
    Counter* c = self(ctx);
    if (c) c->value = (int64_t)n_as_int(n_arg(ctx, 0), n_arg_type(ctx, 0));
}

static void nd_add(NativeCtx* ctx) {
    Counter* c = self(ctx);
    if (c) c->value += (int64_t)n_as_int(n_arg(ctx, 0), n_arg_type(ctx, 0));
}

static void nd_value(NativeCtx* ctx) {
    Counter* c = self(ctx);
    ctx->out.i = (int32_t)(c ? c->value : 0);
}

Step 3: register

static const NativeMethodReg counter_methods[] = {
    { "___init", "(I)V", nd_init },
    { "add",     "(I)V", nd_add },
    { "value",   "()I",  nd_value },
};

static const NativeClassMeta counter_meta = {
    "com.example.counter.Counter", 0, NULL, NULL,
    NULL, 0, counter_methods, 3,
    NULL, NULL,                     /* finalize, gc_scan */
};

static const NativeClassMeta* classes[] = { &counter_meta };
const NativeClassMeta** lopolith_get_native_classes(uint32_t* n) { *n = 1; return classes; }

The constructor is registered under ___init, exactly as declared. The two trailing NULLs are the finalizer and the GC callback; chapter 5 fills them in.

Step 4: build and use it

# library, placed where the loader looks for com.example.counter.Counter
cc -shared -fPIC -O2 -I src counter.c -o build/com/example/counter/Counter.so

# a program that uses it
cat > Demo.loth <<'EOF'
%simple com.example.counter.Demo implements Main
    #import Std
    #import com.example.counter.Counter
    int run(int argc, String[] argv) Main@run {
        Counter c = new Counter(10)
        c.add(5)
        Std.print("value=", c.value(), "\n")
        return 0
    }
EOF

loth compile Demo.loth -o build
loth run -e com.example.counter.Demo -cp build build/com/example/counter/Demo.lothc
# value=15

The library is found because its path under -cp matches the class name. Inspect it without running anything:

$ loth native-info build/com/example/counter/Counter.so
class: com.example.counter.Counter (native)
  method: ___init(I)V
  method: add(I)V
  method: value()I
  finalize=no gc_scan=no

The complete example adds fields, a string held in the handle, an exception and a second class.

Checklist