Native plugins › 07 Advanced metadata
Declare interfaces and slots, inspect libraries, and load plugins at run time.
A native class may implement language interfaces. The metadata lists their dotted fully-qualified names, and the declaration binds each member:
%native com.example.counter.Task
String name()
void run()
static const char* const task_ifaces[] = { "com.example.Runnable", NULL };
static const NativeClassMeta task_meta = {
"com.example.counter.Task", 0, task_ifaces, NULL,
NULL, 0, task_methods, 2, NULL, NULL,
};
Binding checks that every interface member declared in .loth is
provided, and that the descriptors agree. A native object can then be passed
anywhere the interface is expected, including new Thread(task).
%native com.example.EqSet<?a>
void put(?a v)
bool contains(?a v)
static const char* const eqset_slots[] = { "a", NULL };
static const NativeClassMeta eqset_meta = {
"com.example.EqSet", 0, NULL, eqset_slots,
NULL, 0, eqset_methods, 2, eqset_finalize, eqset_scan,
};
L?0;, L?1; for slots.ctx->slots[i] holds the bound type, which may
itself be a nested generic.A &T value is a class token: the class itself, not an
instance. Native code can recover the class from a token:
ClassMeta* vm_token_class(Value token); /* NULL if not a token */
Tokens are ordinary objects; store them in fields and instantiate later with
vm_new_instance(vm, cls) plus a constructor call.
$ loth native-info lib/Thread.so
class: Thread (native)
method: ___init(LRunnable;)V
method: start()V
method: join()V
method: id()I
finalize=yes gc_scan=yes
class: ThreadUtil (singleton)
...
class: Lock (native)
...
class: Cond (native)
...
native-info opens the library and prints every registered class
with kind, fields, methods and slots — useful when a binding is rejected and you
want to see what the library actually declares.
One library can register many classes, and binding a single one opens the
library and binds every class it declares that is already loaded and unbound.
This is how lib/Thread.so provides Thread,
ThreadUtil, Lock and Cond, and how the
example provides Counter and CounterUtil.
When the file name does not match the class name, the loader scans the library directory for a registration. Naming the file after the class is still the clearest choice.
int vm_load_module(VM* vm, const char* lothc_path); /* 0 on success */
int vm_bind_native_file(VM* vm, const char* so_path); /* 0 on success */
The order is: load the .lothc module first, then bind its
library. Both are atomic — any duplicate class, missing dependency or metadata
mismatch rejects the whole operation and leaves the VM unchanged.
Load before you start threads.
The module table is not synchronized against concurrent readers, so treat dynamic loading as a startup activity. Everything else — running code, creating objects, calling methods — is thread-safe in the ways the other tracks describe.