Native plugins › 04 Fields and constructors

Fields and constructors

Expose state to the language as ordinary fields, and initialize it from native constructors.

1. Language-visible fields

A native class can declare fields that Lopolith code reads and writes directly:

%native com.example.counter.Counter
    pub int scale
    void ___init(int start)
    int value()

The VM allocates one Value per declared field in ObjNative.fields, in declaration order, and scans reference fields during GC. The library describes the same fields in its metadata:

static const NativeFieldReg counter_fields[] = { { "scale", "I", 1 } };
static const NativeClassMeta counter_meta = {
    "com.example.counter.Counter", 0, NULL, NULL,
    counter_fields, 1, counter_methods, 7, counter_finalize, counter_scan,
};

Field name and descriptor must match the declaration. The third member marks the field public.

2. Reading and writing fields from C

static void nd_value(NativeCtx* ctx) {
    Counter* c = self(ctx);
    ObjNative* o = (ObjNative*)ctx->recv.o;
    int32_t scale = 1;
    if (o->fields) scale = (int32_t)o->fields[0].i;   /* pub int scale */
    ctx->out.i = (int32_t)((c ? c->value : 0) * (int64_t)scale);
}

Index 0 is the first declared field. For reference-typed fields, remember the GC scans fields[] automatically; you do not need a gc_scan for them.

3. Private state belongs in the handle

Fields that the language should not see go into the C handle instead. The handle is opaque to the VM: it is created lazily, owned by your plugin, and released in finalize. This is where you put buffers, file descriptors, and any C state.

typedef struct {
    int64_t value;
    Value label;      /* a Value kept in the handle must be rooted (chapter 5) */
} 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;
        c->label = NIL_VAL;
        o->handle = c;
    }
    return (Counter*)o->handle;
}

4. Constructors

5. Two classes, one library

The example registers both Counter (newable) and CounterUtil (singleton) from the same .so:

%nativesimple com.example.counter.CounterUtil
    int twice(int x)
static const NativeMethodReg util_methods[] = { { "twice", "(I)I", nd_twice } };
static const NativeClassMeta util_meta = {
    "com.example.counter.CounterUtil", 1, NULL, NULL, NULL, 0,
    util_methods, 1, NULL, NULL,
};

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

Kind 1 makes CounterUtil.twice(21) a direct singleton call, no new required.