Native plugins › 04 Fields and constructors
Expose state to the language as ordinary fields, and initialize it from native constructors.
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.
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.
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;
}
___init
with a descriptor that matches the declaration.%native (kind 0), new Counter(10) allocates
the object and calls the constructor. The handle may be created in the
constructor, or lazily on first use as above.%nativesimple (kind 1), the singleton is constructed on
first use and its constructor takes no parameters.___init descriptors.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.