强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

QuickJS 嵌入式 JavaScript 引擎完全教程 / 06 - 原生扩展

原生扩展

本章介绍如何用 C 编写原生扩展模块,为 JavaScript 环境添加系统级能力。

6.1 C 模块基础

ES Module 形式的 C 模块

QuickJS 的 C 模块与 ES Module 语法对应,可以被 import 语句加载。

// native_math.c — 最简单的 C 模块
#include "quickjs.h"
#include <math.h>

#define countof(x) (sizeof(x) / sizeof((x)[0]))

// 导出函数:square(x)
static JSValue js_square(JSContext *ctx, JSValue this_val,
                          int argc, JSValue *argv) {
    double x;
    if (JS_ToFloat64(ctx, &x, argv[0]))
        return JS_EXCEPTION;
    return JS_NewFloat64(ctx, x * x);
}

// 导出函数:sqrt(x)
static JSValue js_sqrt(JSContext *ctx, JSValue this_val,
                        int argc, JSValue *argv) {
    double x;
    if (JS_ToFloat64(ctx, &x, argv[0]))
        return JS_EXCEPTION;
    if (x < 0)
        return JS_ThrowRangeError(ctx, "sqrt of negative number");
    return JS_NewFloat64(ctx, sqrt(x));
}

// 导出函数:hypot(x, y)
static JSValue js_hypot(JSContext *ctx, JSValue this_val,
                         int argc, JSValue *argv) {
    double x, y;
    if (JS_ToFloat64(ctx, &x, argv[0]) ||
        JS_ToFloat64(ctx, &y, argv[1]))
        return JS_EXCEPTION;
    return JS_NewFloat64(ctx, hypot(x, y));
}

// 模块函数表
static const JSCFunctionListEntry module_funcs[] = {
    JS_CFUNC_DEF("square", 1, js_square),
    JS_CFUNC_DEF("sqrt", 1, js_sqrt),
    JS_CFUNC_DEF("hypot", 2, js_hypot),
    // 常量
    JS_PROP_DOUBLE_DEF("E", M_E, 0),
    JS_PROP_DOUBLE_DEF("PI", M_PI, 0),
    JS_PROP_INT32_DEF("MAX_SAFE", 9007199254740991, 0),
};

// 模块初始化函数
static int native_math_init(JSContext *ctx, JSModuleDef *m) {
    return JS_SetModuleExportList(ctx, m, module_funcs,
                                   countof(module_funcs));
}

// 模块定义入口(QuickJS 通过函数名识别)
JSModuleDef *js_init_module_native_math(JSContext *ctx,
                                         const char *module_name) {
    JSModuleDef *m = JS_NewCModule(ctx, module_name, native_math_init);
    if (!m) return NULL;

    // 声明导出列表
    JS_AddModuleExportList(ctx, m, module_funcs, countof(module_funcs));
    return m;
}

使用 C 模块

// main.c — 加载并使用 C 模块
#include "quickjs-libc.h"
#include <stdio.h>

// 声明模块初始化函数
extern JSModuleDef *js_init_module_native_math(JSContext *ctx,
                                                const char *module_name);

int main() {
    JSRuntime *rt = JS_NewRuntime();
    JSContext *ctx = JS_NewContext(rt);

    // 注册 C 模块(必须在 eval 之前)
    js_init_module_native_math(ctx, "native_math");

    const char *code = R"(
        import { square, sqrt, hypot, PI } from "native_math";

        console.log("square(7):", square(7));       // 49
        console.log("sqrt(144):", sqrt(144));        // 12
        console.log("hypot(3,4):", hypot(3, 4));    // 5
        console.log("PI:", PI);                      // 3.14159...
    )";

    JSValue result = JS_Eval(ctx, code, strlen(code),
                              "<main>", JS_EVAL_TYPE_MODULE);
    if (JS_IsException(result)) {
        JSValue ex = JS_GetException(ctx);
        const char *msg = JS_ToCString(ctx, ex);
        fprintf(stderr, "Error: %s\n", msg);
        JS_FreeCString(ctx, msg);
        JS_FreeValue(ctx, ex);
    }
    JS_FreeValue(ctx, result);

    JS_FreeContext(ctx);
    JS_FreeRuntime(rt);
    return 0;
}

命名规则

QuickJS 通过函数名来匹配模块名。模块名中的特殊字符会被替换:

模块名C 初始化函数名
native_mathjs_init_module_native_math
my-libjs_init_module_my_lib
osjs_init_module_os
stdjs_init_module_std

6.2 JSCFunctionListEntry 详解

JSCFunctionListEntry 是 QuickJS 提供的声明式 API,用于批量注册函数、常量、属性。

函数定义宏

// 基本函数
JS_CFUNC_DEF(name, length, func)

// 带 this 的函数(构造函数等)
JS_CFUNC_DEF2(name, length, func, prop_flags)

// getter / setter
JS_CFUNC_GETSET_DEF(name, getter, setter)

// 构造函数
JS_CFUNC_SPECIAL_DEF(name, length, constructor, func)

// 原始值 getter (get/set)
JS_CGETSET_DEF(name, getter, setter)

属性定义宏

// 整数常量
JS_PROP_INT32_DEF(name, value, flags)

// 浮点常量
JS_PROP_DOUBLE_DEF(name, value, flags)

// 字符串常量
JS_PROP_STRING_DEF(name, value, flags)

// undefined
JS_PROP_UNDEFINED_DEF(name, flags)

完整示例

// complete_export.c — 完整的导出列表示例
static const JSCFunctionListEntry module_exports[] = {
    // 函数
    JS_CFUNC_DEF("add", 2, js_add),
    JS_CFUNC_DEF("multiply", 2, js_multiply),
    JS_CFUNC_DEF("format", 2, js_format),

    // getter/setter
    JS_CGETSET_DEF("version", js_get_version, NULL),

    // 常量
    JS_PROP_INT32_DEF("MAX_SIZE", 1048576, JS_PROP_CONFIGURABLE),
    JS_PROP_DOUBLE_DEF("EPSILON", 2.220446049250313e-16, 0),
    JS_PROP_STRING_DEF("PLATFORM", "linux", 0),

    // 嵌套对象(需要另外定义)
    // JS_OBJECT_DEF("config", config_props, countof(config_props), JS_PROP_C_W_E),
};

6.3 原生函数模式

接受可变参数

// varargs_func.c — 接受可变数量参数
static JSValue js_sum(JSContext *ctx, JSValue this_val,
                      int argc, JSValue *argv) {
    double total = 0;

    for (int i = 0; i < argc; i++) {
        double val;
        if (JS_ToFloat64(ctx, &val, argv[i]))
            return JS_EXCEPTION;
        total += val;
    }

    return JS_NewFloat64(ctx, total);
}

// JavaScript: sum(1, 2, 3, 4, 5) → 15

接受选项对象

// options_pattern.c — 接受选项对象参数
static JSValue js_create_server(JSContext *ctx, JSValue this_val,
                                 int argc, JSValue *argv) {
    JSValue options = argc > 0 ? argv[0] : JS_UNDEFINED;
    int port = 8080;
    const char *host = "0.0.0.0";
    int backlog = 128;

    if (JS_IsObject(options)) {
        JSValue port_val = JS_GetPropertyStr(ctx, options, "port");
        if (!JS_IsUndefined(port_val)) {
            JS_ToInt32(ctx, &port, port_val);
        }
        JS_FreeValue(ctx, port_val);

        JSValue host_val = JS_GetPropertyStr(ctx, options, "host");
        if (!JS_IsUndefined(host_val)) {
            host = JS_ToCString(ctx, host_val);
        }
        // 注意:需要稍后释放 host

        JSValue backlog_val = JS_GetPropertyStr(ctx, options, "backlog");
        if (!JS_IsUndefined(backlog_val)) {
            JS_ToInt32(ctx, &backlog, backlog_val);
        }
        JS_FreeValue(ctx, backlog_val);
    }

    // 创建服务器逻辑...
    JSValue result = JS_NewObject(ctx);
    JS_SetPropertyStr(ctx, result, "port", JS_NewInt32(ctx, port));
    JS_SetPropertyStr(ctx, result, "host", JS_NewString(ctx, host));
    JS_SetPropertyStr(ctx, result, "backlog", JS_NewInt32(ctx, backlog));

    return result;
}

// JavaScript:
// createServer({ port: 3000, host: "127.0.0.1" })

返回 Promise

// promise_func.c — 返回 Promise 的 C 函数
#include "quickjs-libc.h"
#include <string.h>

// 异步任务的数据
typedef struct {
    JSContext *ctx;
    JSValue resolve;
    JSValue reject;
} AsyncData;

// 在实际应用中,这个函数会在另一个线程或事件循环中调用
static void resolve_async(AsyncData *data) {
    JSValue result = JS_NewString(data->ctx, "async result");
    JS_Call(data->ctx, data->resolve, JS_UNDEFINED, 1, &result);
    JS_FreeValue(data->ctx, result);
    JS_FreeValue(data->ctx, data->resolve);
    JS_FreeValue(data->ctx, data->reject);
    free(data);
}

static JSValue js_async_fetch(JSContext *ctx, JSValue this_val,
                               int argc, JSValue *argv) {
    const char *url = JS_ToCString(ctx, argv[0]);
    if (!url) return JS_EXCEPTION;

    // 创建 Promise
    JSValue resolve_fn, reject_fn;
    JSValue promise = JS_NewPromiseCapability(ctx, &resolve_fn, &reject_fn);

    // 保存回调数据
    AsyncData *data = malloc(sizeof(AsyncData));
    data->ctx = ctx;
    data->resolve = JS_DupValue(ctx, resolve_fn);
    data->reject = JS_DupValue(ctx, reject_fn);

    // 在实际应用中,这里会启动异步操作
    // 为简单示例,直接同步完成
    printf("Fetching: %s\n", url);
    JS_FreeCString(ctx, url);

    // 模拟异步完成
    resolve_async(data);

    JS_FreeValue(ctx, resolve_fn);
    JS_FreeValue(ctx, reject_fn);

    return promise;
}

// JavaScript:
// const data = await asyncFetch("https://api.example.com/data");

6.4 自定义类定义

完整的自定义类

// buffer_class.c — 实现一个 ArrayBuffer 类
#include "quickjs-libc.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static JSClassID js_buffer_class_id;

typedef struct {
    uint8_t *data;
    size_t size;
    size_t capacity;
} BufferData;

// 析构函数
static void js_buffer_finalizer(JSRuntime *rt, JSValue val) {
    BufferData *buf = JS_GetOpaque(val, js_buffer_class_id);
    if (buf) {
        free(buf->data);
        free(buf);
    }
}

// 类定义
static JSClassDef js_buffer_class = {
    "Buffer",
    .finalizer = js_buffer_finalizer,
};

// 构造函数 new Buffer(size | data)
static JSValue js_buffer_constructor(JSContext *ctx, JSValue this_val,
                                      int argc, JSValue *argv) {
    BufferData *buf = calloc(1, sizeof(BufferData));
    if (!buf) return JS_ThrowOutOfMemory(ctx);

    if (argc >= 1) {
        if (JS_IsNumber(argv[0])) {
            // new Buffer(size)
            int32_t size;
            JS_ToInt32(ctx, &size, argv[0]);
            buf->data = calloc(1, size);
            buf->size = size;
            buf->capacity = size;
        } else if (JS_IsString(argv[0])) {
            // new Buffer(string)
            const char *str = JS_ToCString(ctx, argv[0]);
            size_t len = strlen(str);
            buf->data = malloc(len);
            memcpy(buf->data, str, len);
            buf->size = len;
            buf->capacity = len;
            JS_FreeCString(ctx, str);
        }
    }

    JSValue obj = JS_NewObjectClass(ctx, js_buffer_class_id);
    if (JS_IsException(obj)) {
        free(buf->data);
        free(buf);
        return obj;
    }
    JS_SetOpaque(obj, buf);
    return obj;
}

// getter: buffer.length
static JSValue js_buffer_get_length(JSContext *ctx, JSValue this_val) {
    BufferData *buf = JS_GetOpaque(this_val, js_buffer_class_id);
    return buf ? JS_NewInt32(ctx, (int32_t)buf->size) : JS_UNDEFINED;
}

// 方法: buffer.write(string, offset)
static JSValue js_buffer_write(JSContext *ctx, JSValue this_val,
                                int argc, JSValue *argv) {
    BufferData *buf = JS_GetOpaque(this_val, js_buffer_class_id);
    if (!buf) return JS_EXCEPTION;

    const char *str = JS_ToCString(ctx, argv[0]);
    size_t len = strlen(str);
    int32_t offset = 0;
    if (argc > 1) JS_ToInt32(ctx, &offset, argv[1]);

    if (offset + len > buf->capacity) {
        // 扩容
        size_t new_cap = (offset + len) * 2;
        buf->data = realloc(buf->data, new_cap);
        buf->capacity = new_cap;
    }

    memcpy(buf->data + offset, str, len);
    if (offset + len > buf->size) buf->size = offset + len;

    JS_FreeCString(ctx, str);
    return JS_NewInt32(ctx, (int32_t)len);
}

// 方法: buffer.read(offset, length)
static JSValue js_buffer_read(JSContext *ctx, JSValue this_val,
                               int argc, JSValue *argv) {
    BufferData *buf = JS_GetOpaque(this_val, js_buffer_class_id);
    if (!buf) return JS_EXCEPTION;

    int32_t offset = 0, length = -1;
    if (argc > 0) JS_ToInt32(ctx, &offset, argv[0]);
    if (argc > 1) JS_ToInt32(ctx, &length, argv[1]);

    if (length < 0) length = buf->size - offset;
    if (offset < 0 || offset + length > (int32_t)buf->size) {
        return JS_ThrowRangeError(ctx, "Buffer read out of bounds");
    }

    return JS_NewStringLen(ctx, (const char *)buf->data + offset, length);
}

// 方法: buffer.toString()
static JSValue js_buffer_toString(JSContext *ctx, JSValue this_val,
                                   int argc, JSValue *argv) {
    BufferData *buf = JS_GetOpaque(this_val, js_buffer_class_id);
    if (!buf) return JS_EXCEPTION;
    return JS_NewStringLen(ctx, (const char *)buf->data, buf->size);
}

// 方法: buffer.fill(value)
static JSValue js_buffer_fill(JSContext *ctx, JSValue this_val,
                               int argc, JSValue *argv) {
    BufferData *buf = JS_GetOpaque(this_val, js_buffer_class_id);
    if (!buf) return JS_EXCEPTION;

    int32_t value = 0;
    JS_ToInt32(ctx, &value, argv[0]);
    memset(buf->data, value, buf->size);

    return JS_DupValue(ctx, this_val); // 支持链式调用
}

// 注册类
void js_buffer_init(JSContext *ctx) {
    JS_NewClassID(&js_buffer_class_id);
    JS_NewClass(JS_GetRuntime(ctx), js_buffer_class_id, &js_buffer_class);

    JSValue proto = JS_NewObject(ctx);

    JS_SetPropertyStr(ctx, proto, "write",
        JS_NewCFunction(ctx, js_buffer_write, "write", 2));
    JS_SetPropertyStr(ctx, proto, "read",
        JS_NewCFunction(ctx, js_buffer_read, "read", 2));
    JS_SetPropertyStr(ctx, proto, "toString",
        JS_NewCFunction(ctx, js_buffer_toString, "toString", 0));
    JS_SetPropertyStr(ctx, proto, "fill",
        JS_NewCFunction(ctx, js_buffer_fill, "fill", 1));

    JS_SetPropertyGetSet(ctx, proto,
        JS_NewAtom(ctx, "length"),
        JS_NewCFunction2(ctx, js_buffer_get_length, "length",
                          0, JS_CFUNC_getter),
        JS_UNDEFINED);

    JSValue ctor = JS_NewCFunction2(ctx, js_buffer_constructor,
                                     "Buffer", 1, JS_CFUNC_constructor, 0);
    JS_SetConstructor(ctx, ctor, proto);
    JS_SetClassProto(ctx, js_buffer_class_id, proto);

    JSValue global = JS_GetGlobalObject(ctx);
    JS_SetPropertyStr(ctx, global, "Buffer", ctor);
    JS_FreeValue(ctx, global);
}

6.5 属性与方法

只读属性

// readonly_properties.c
static JSValue js_get_config_version(JSContext *ctx, JSValue this_val) {
    return JS_NewString(ctx, "1.0.0");
}

// 注册为只读 getter(setter 为 undefined)
static const JSCFunctionListEntry config_entries[] = {
    JS_CGETSET_DEF("version", js_get_config_version, NULL),
};

计算属性

// computed_property.c
typedef struct {
    int width;
    int height;
} RectData;

static JSClassID js_rect_class_id;

// 计算属性:area(每次访问都实时计算)
static JSValue js_rect_get_area(JSContext *ctx, JSValue this_val) {
    RectData *r = JS_GetOpaque(this_val, js_rect_class_id);
    return r ? JS_NewFloat64(ctx, r->width * r->height) : JS_UNDEFINED;
}

static JSValue js_rect_get_perimeter(JSContext *ctx, JSValue this_val) {
    RectData *r = JS_GetOpaque(this_val, js_rect_class_id);
    return r ? JS_NewFloat64(ctx, 2.0 * (r->width + r->height)) : JS_UNDEFINED;
}

使用 Symbol

// symbol_property.c
static JSValue js_object_toPrimitive(JSContext *ctx, JSValue this_val,
                                      int argc, JSValue *argv) {
    // Symbol.toPrimitive 实现
    const char *hint = "default";
    if (argc > 0) {
        hint = JS_ToCString(ctx, argv[0]);
    }

    if (strcmp(hint, "number") == 0) {
        return JS_NewFloat64(ctx, 42);
    } else if (strcmp(hint, "string") == 0) {
        return JS_NewString(ctx, "[MyObject]");
    }
    return JS_NewInt32(ctx, 42);
}

6.6 继承体系

类继承实现

// inheritance.c — 在 C 中实现 JS 类继承
#include "quickjs-libc.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// === 基类:Animal ===
static JSClassID js_animal_class_id;

typedef struct {
    char *name;
    int age;
} AnimalData;

static void js_animal_finalizer(JSRuntime *rt, JSValue val) {
    AnimalData *a = JS_GetOpaque(val, js_animal_class_id);
    if (a) {
        free(a->name);
        free(a);
    }
}

static JSClassDef js_animal_class = {
    "Animal",
    .finalizer = js_animal_finalizer,
};

static JSValue js_animal_constructor(JSContext *ctx, JSValue this_val,
                                      int argc, JSValue *argv) {
    AnimalData *a = calloc(1, sizeof(AnimalData));
    const char *name = JS_ToCString(ctx, argv[0]);
    a->name = strdup(name);
    JS_FreeCString(ctx, name);
    if (argc > 1) JS_ToInt32(ctx, &a->age, argv[1]);

    JSValue obj = JS_NewObjectClass(ctx, js_animal_class_id);
    JS_SetOpaque(obj, a);
    return obj;
}

static JSValue js_animal_speak(JSContext *ctx, JSValue this_val,
                                int argc, JSValue *argv) {
    AnimalData *a = JS_GetOpaque(this_val, js_animal_class_id);
    if (!a) return JS_EXCEPTION;
    char buf[256];
    snprintf(buf, sizeof(buf), "%s says: ...", a->name);
    return JS_NewString(ctx, buf);
}

static JSValue js_animal_get_name(JSContext *ctx, JSValue this_val) {
    AnimalData *a = JS_GetOpaque(this_val, js_animal_class_id);
    return a ? JS_NewString(ctx, a->name) : JS_UNDEFINED;
}

// === 子类:Dog extends Animal ===
static JSClassID js_dog_class_id;

typedef struct {
    AnimalData base; // 继承基类数据
    char *breed;
} DogData;

static void js_dog_finalizer(JSRuntime *rt, JSValue val) {
    DogData *d = JS_GetOpaque(val, js_dog_class_id);
    if (d) {
        free(d->base.name);
        free(d->breed);
        free(d);
    }
}

static JSClassDef js_dog_class = {
    "Dog",
    .finalizer = js_dog_finalizer,
};

static JSValue js_dog_constructor(JSContext *ctx, JSValue this_val,
                                   int argc, JSValue *argv) {
    DogData *d = calloc(1, sizeof(DogData));
    const char *name = JS_ToCString(ctx, argv[0]);
    d->base.name = strdup(name);
    JS_FreeCString(ctx, name);
    if (argc > 1) {
        const char *breed = JS_ToCString(ctx, argv[1]);
        d->breed = strdup(breed);
        JS_FreeCString(ctx, breed);
    }

    JSValue obj = JS_NewObjectClass(ctx, js_dog_class_id);
    JS_SetOpaque(obj, d);
    return obj;
}

// 重写 speak 方法
static JSValue js_dog_speak(JSContext *ctx, JSValue this_val,
                             int argc, JSValue *argv) {
    DogData *d = JS_GetOpaque(this_val, js_dog_class_id);
    if (!d) return JS_EXCEPTION;
    char buf[256];
    snprintf(buf, sizeof(buf), "%s says: Woof! (breed: %s)",
             d->base.name, d->breed ? d->breed : "unknown");
    return JS_NewString(ctx, buf);
}

// 注册继承体系
void js_inheritance_init(JSContext *ctx) {
    JSRuntime *rt = JS_GetRuntime(ctx);

    // --- 基类 Animal ---
    JS_NewClassID(&js_animal_class_id);
    JS_NewClass(rt, js_animal_class_id, &js_animal_class);

    JSValue animal_proto = JS_NewObject(ctx);
    JS_SetPropertyStr(ctx, animal_proto, "speak",
        JS_NewCFunction(ctx, js_animal_speak, "speak", 0));
    JS_SetPropertyGetSet(ctx, animal_proto,
        JS_NewAtom(ctx, "name"),
        JS_NewCFunction2(ctx, js_animal_get_name, "get name", 0, JS_CFUNC_getter),
        JS_UNDEFINED);

    JSValue animal_ctor = JS_NewCFunction2(ctx, js_animal_constructor,
                                            "Animal", 1, JS_CFUNC_constructor, 0);
    JS_SetConstructor(ctx, animal_ctor, animal_proto);
    JS_SetClassProto(ctx, js_animal_class_id, animal_proto);

    // --- 子类 Dog ---
    JS_NewClassID(&js_dog_class_id);
    JS_NewClass(rt, js_dog_class_id, &js_dog_class);

    JSValue dog_proto = JS_NewObject(ctx);
    // 继承 Animal.prototype
    JS_SetPrototype(ctx, dog_proto, animal_proto);

    // 添加子类特有方法和重写方法
    JS_SetPropertyStr(ctx, dog_proto, "speak",
        JS_NewCFunction(ctx, js_dog_speak, "speak", 0));

    JSValue dog_ctor = JS_NewCFunction2(ctx, js_dog_constructor,
                                         "Dog", 2, JS_CFUNC_constructor, 0);
    JS_SetConstructor(ctx, dog_ctor, dog_proto);
    // Dog.prototype.__proto__ = Animal.prototype
    JS_SetPrototype(ctx, JS_GetPropertyStr(ctx, dog_ctor, "prototype"),
                    animal_proto);
    JS_SetClassProto(ctx, js_dog_class_id, dog_proto);

    // 挂载到全局
    JSValue global = JS_GetGlobalObject(ctx);
    JS_SetPropertyStr(ctx, global, "Animal", animal_ctor);
    JS_SetPropertyStr(ctx, global, "Dog", dog_ctor);
    JS_FreeValue(ctx, global);
}

int main() {
    JSRuntime *rt = JS_NewRuntime();
    JSContext *ctx = JS_NewContext(rt);
    js_init_module_std(ctx, "std");
    js_init_module_os(ctx, "os");
    js_inheritance_init(ctx);

    const char *code = R"(
        const animal = new Animal("Generic", 5);
        print(animal.speak());       // "Generic says: ..."
        print(animal.name);          // "Generic"

        const dog = new Dog("Rex", "German Shepherd");
        print(dog.speak());          // "Rex says: Woof! (breed: German Shepherd)"
        print(dog.name);             // "Rex"
        print(dog instanceof Dog);   // true
        print(dog instanceof Animal);// true
    )";

    JSValue result = JS_Eval(ctx, code, strlen(code), "<inheritance>", 0);
    if (JS_IsException(result)) {
        JSValue ex = JS_GetException(ctx);
        const char *msg = JS_ToCString(ctx, ex);
        fprintf(stderr, "Error: %s\n", msg);
        JS_FreeCString(ctx, msg);
        JS_FreeValue(ctx, ex);
    }
    JS_FreeValue(ctx, result);

    JS_FreeContext(ctx);
    JS_FreeRuntime(rt);
    return 0;
}

6.7 异常处理

// exception_handling.c — 在 C 扩展中正确处理异常
static JSValue js_risky_function(JSContext *ctx, JSValue this_val,
                                  int argc, JSValue *argv) {
    // 1. 抛出 TypeError
    if (!JS_IsNumber(argv[0])) {
        return JS_ThrowTypeError(ctx, "Expected a number, got %s",
                                  JS_ToCString(ctx,
                                      JS_GetPropertyStr(ctx,
                                          JS_GetGlobalObject(ctx),
                                          "typeof")));
    }

    // 2. 抛出 RangeError
    double val;
    JS_ToFloat64(ctx, &val, argv[0]);
    if (val < 0 || val > 100) {
        return JS_ThrowRangeError(ctx, "Value must be between 0 and 100");
    }

    // 3. 抛出自定义 Error
    if (val == 42) {
        JSValue err = JS_NewError(ctx);
        JS_SetPropertyStr(ctx, err, "message",
            JS_NewString(ctx, "Special value 42 is not allowed"));
        JS_SetPropertyStr(ctx, err, "code",
            JS_NewInt32(ctx, 42));
        return JS_Throw(ctx, err);
    }

    // 4. 传播下层异常
    JSValue result = JS_Eval(ctx, "undefined_function()", 22, "<internal>", 0);
    if (JS_IsException(result)) {
        return JS_EXCEPTION; // 异常自动传播
    }
    JS_FreeValue(ctx, result);

    return JS_NewFloat64(ctx, val * 2);
}

6.8 编译与链接

Makefile 示例

# Makefile — 编译 QuickJS 扩展
CC = gcc
CFLAGS = -Wall -O2 -I.
QUICKJS_OBJS = quickjs.o quickjs-libc.o cutils.o libregexp.o libunicode.o

# 编译为共享库(可选,需要 dlopen 支持)
# shared: native_math.so

# native_math.so: native_math.c $(QUICKJS_OBJS)
# 	$(CC) -shared -fPIC -o $@ $^

# 编译为静态链接程序
app: main.c native_math.c $(QUICKJS_OBJS)
	$(CC) $(CFLAGS) -o $@ $^ -lm -lpthread -ldl

quickjs.o: quickjs.c quickjs.h
	$(CC) $(CFLAGS) -c $<

quickjs-libc.o: quickjs-libc.c quickjs-libc.h
	$(CC) $(CFLAGS) -c $<

cutils.o: cutils.c cutils.h
	$(CC) $(CFLAGS) -c $<

libregexp.o: libregexp.c
	$(CC) $(CFLAGS) -c $<

libunicode.o: libunicode.c
	$(CC) $(CFLAGS) -c $<

clean:
	rm -f *.o app

6.9 本章小结

要点说明
C 模块使用 JS_NewCModule + JSCFunctionListEntry 定义模块
命名规则js_init_module_<name> 格式命名初始化函数
函数表使用 JS_CFUNC_DEF 等宏声明式注册
自定义类JSClassID + JSClassDef + JS_SetOpaque
继承通过 JS_SetPrototype 建立原型链
异常使用 JS_Throw* 函数抛出,JS_EXCEPTION 传播

扩展阅读