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_math | js_init_module_native_math |
my-lib | js_init_module_my_lib |
os | js_init_module_os |
std | js_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 传播 |
扩展阅读
- QuickJS C API 参考
- JSCFunctionListEntry 宏定义 — 查看 quickjs.h
- quickjspp C++ 封装