Native plugins › 06 Exceptions and callbacks
Throw language exceptions from C, and call language methods from C, with the exception rules in both directions.
vm_throw(VM* vm, Value exc); /* any object implementing _LANG.Exception */
vm_throw_iobe(VM* vm, int64_t idx, int64_t len);
vm_throw_dzbe(VM* vm); /* DivideByZeroException */
static void nd_divide(NativeCtx* ctx) {
int32_t a = n_as_int(n_arg(ctx, 0), n_arg_type(ctx, 0));
int32_t b = n_as_int(n_arg(ctx, 1), n_arg_type(ctx, 1));
if (b == 0) {
vm_throw_dzbe(ctx->vm); /* language code catches DivideByZeroException */
return;
}
ctx->out.i = a / b;
}
vm_throw, stop working and return; the exception
propagates at the call site.try/catch receives it normally.int vm_call(VM* vm, Value recv, const char* method,
Value* args, uint32_t argc, const TypeDesc* const* arg_types,
const TypeDesc* ret_type, Value* out);
Value r = NIL_VAL;
int rc = vm_call(ctx->vm, ctx->args[0], "___equals", &other, 1, NULL, NULL, &r);
if (rc == 0 && r.i) {
/* the language method returned true */
} else if (rc == 1) {
/* the method threw; r holds the exception object */
}
| Return | Meaning |
|---|---|
| 0 | normal completion, result in *out |
| 1 | the callee threw; the exception object is in *out |
| -1 | no such method, or a null / non-object receiver |
vm_call or throw again). That means
you may allocate and then rethrow it safely:
vm_throw(vm, exc)./* 1. Propagate it to your caller's language code: */
vm_throw(ctx->vm, exc);
/* 2. Handle it: inspect the object (its class, its fields) and continue. */
/* 3. Swallow it: ignore the return code. */
Nothing is automatic: the VM never unwinds through your C frames, so a native is always in control of what happens next.
Generic containers need to compare elements whose type is only known through
a slot. The pattern is to call ___equals for references and compare
bits for primitives:
static int elem_equal(NativeCtx* ctx, Value a, Value b) {
const TypeDesc* t = ctx->nslots > 0 ? ctx->slots[0] : NULL;
if (t && n_is_ref(t)) {
if (a.o == b.o) return 1;
Value r = NIL_VAL;
int rc = vm_call(ctx->vm, a, "___equals", &b, 1, NULL, NULL, &r);
return (rc == 0 && r.i) ? 1 : 0;
}
return a.i == b.i;
}
If a native constructor throws, the object is not cached (for singletons) and the exception propagates like any other. Keep constructors short and avoid allocation-heavy work in them.