Native plugins › 01 How native classes work
Understand the split between the declaration in .loth
and the implementation in C, and how the two are matched.
.loth declaration ──┐
├── matched by name + descriptor ──▶ binding
foo.so implementation ─┘
A native class is declared in Lopolith like any other type, but with no bodies:
%native com.example.counter.Counter
pub int scale
void ___init(int start)
void add(int d)
int value()
The C side provides one function per method, all with the same signature
void fn(NativeCtx* ctx), and registers them with descriptors that
must match the declaration exactly.
Every method is identified by a name plus a descriptor string:
| Type | Descriptor |
|---|---|
bool byte short int long float double void | Z B S I J F D V |
String | L_LANG.String; |
class com.example.Foo | Lcom.example.Foo; |
| arrays | [I, [L_LANG.String;, [[I |
| method | (II)Lcom.example.Foo;, ()V |
| generic slot | L?0;, L?1; |
class token &T | &Lcom.example.T; |
Class names inside descriptors are dotted fully-qualified names. There are no generics or overloading beyond what the declaration says: a method is matched by its exact name and descriptor.
A library exports one function. The modern (V2) form registers several classes, each with kind, fields, interfaces and slots:
typedef struct NativeClassMeta {
const char* class_name;
int kind; /* 0 = newable (%native); 1 = singleton (%nativesimple) */
const char* const* implements; /* dotted FQNs, NULL-terminated */
const char* const* slots; /* slot names, NULL-terminated */
const NativeFieldReg* fields; int nfields;
const NativeMethodReg* methods; int nmethods;
void (*finalize)(VM* vm, ObjNative* obj);
void (*gc_scan)(VM* vm, ObjNative* obj);
} NativeClassMeta;
const NativeClassMeta** lopolith_get_native_classes(uint32_t* out_n);
The older V1 form registers a single class with
lopolith_get_native; the loader prefers V2 and falls back to V1 for
existing libraries. New plugins should use V2.
Binding happens once, when the host calls
loth_engine_bind_native (or dynamically via
vm_bind_native_file). The loader:
.loth declaration:
class kind, every field (name and descriptor), every method (name and
descriptor), interfaces and slot names. Both directions are checked — the
declaration may not promise a method the library does not provide.| Declaration | Kind | Instance |
|---|---|---|
%native | 0 | created with new |
%nativesimple | 1 | one singleton, initialized on first use |
Both can declare fields, methods, interfaces and slots. A singleton's constructor takes no parameters.
Every method receives a NativeCtx: the receiver, the arguments
with their static types, the VM, and the output slot. Chapter 3 covers it in
detail; the rest of this track builds the example plugin piece by piece.