Native plugins › 02 Your first plugin
Build a working native class from scratch, in four steps.
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.
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);
}
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.
# 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.
%native → 0,
%nativesimple → 1.