Rust QuickJS绑定库rquickjs-sys的使用:高性能JavaScript引擎集成与安全执行环境

Rust QuickJS绑定库rquickjs-sys的使用:高性能JavaScript引擎集成与安全执行环境

关于rquickjs-sys

rquickjs-sys是QuickJS JavaScript引擎的低级不安全原始绑定。QuickJS是一个轻量级的JavaScript引擎,由Fabrice Bellard开发。

github crates docs

注意:通常您不应该直接使用这个crate,而是使用rquickjs crate,它提供了高级安全绑定。

补丁

为了修复错误并支持一些未实现的功能,应用了一系列补丁。

热修复:

  • 修复堆栈溢出检查(对Rust很重要)
  • JS_NewClassID添加原子支持(对Rust很重要)
  • 无限处理(用INFINITY常量替换1.0 / 0.0

特殊补丁:

  • 读取模块导出(exports功能)
  • 重置堆栈函数(parallel功能)
  • MSVC支持

安装

在项目目录中运行以下Cargo命令:

cargo add rquickjs-sys

或者在Cargo.toml中添加以下行:

rquickjs-sys = "0.9.0"

完整示例代码

以下是一个使用rquickjs-sys的完整示例,展示如何创建QuickJS运行时并执行JavaScript代码:

use rquickjs_sys::{JS_NewRuntime, JS_NewContext, JS_Eval, JS_FreeContext, JS_FFreeRuntime};
use std::ffi::CString;
use std::ptr;

// 完整示例:创建JS运行时、执行代码并处理结果
fn main() {
    // 1. 创建运行时
    let rt = unsafe { JS_NewRuntime() };
    if rt.is_null() {
        panic!("无法创建JS运行时");
    }

    // 2. 创建执行上下文
    let ctx = unsafe { JS_NewContext(rt) };
    if ctx.is_null() {
        unsafe { JS_FreeRuntime(rt) };
        panic!("无法创建JS上下文");
    }

    // 3. 准备要执行的JS代码
    let js_code = CString::new("
        function factorial(n) {
            return n <= 1 ? 1 : n * factorial(n - 1);
        }
        factorial(5);
    ").unwrap();

    // 4. 执行JS代码
    let result = unsafe {
        JS_Eval(
            ctx,
            js_code.as_ptr(),
            js_code.as_bytes().len() as _,
            b"script.js\0".as_ptr() as _,
            0
        )
    };

    // 5. 处理执行结果
    if result.is_null() {
        println!("JS代码执行出错");
    } else {
        // 将结果转换为整数
        let mut result_value = 0;
        unsafe { JS_ToInt32(ctx, &mut result_value, result) };
        println!("JS执行结果: {}", result_value);
        
        // 释放结果值
        unsafe { JS_FreeValue(ctx, result) };
    }

    // 6. 清理资源
    unsafe {
        JS_FreeContext(ctx);
        JS_FreeRuntime(rt);
    }
}

更高级的示例

下面是一个更完整的示例,展示了模块加载、异常处理和异步执行:

use rquickjs_sys::{
    JS_NewRuntime, JS_NewContext, JS_Eval, JS_FreeContext, JS_FreeRuntime,
    JS_GetGlobalObject, JS_SetPropertyStr, JS_NewInt32, JS_Call, JS_ToInt32,
    JS_NewString, JS_GetPropertyStr, JS_FreeValue, JS_IsException, JS_GetException
};
use std::ffi::CString;
use std::ptr;

fn main() {
    // 1. 初始化运行时和上下文
    let rt = unsafe { JS_NewRuntime() };
    let ctx = unsafe { JS_NewContext(rt) };
    
    // 2. 设置全局变量
    let global_obj = unsafe { JS_GetGlobalObject(ctx) };
    let var_name = CString::new("appName").unwrap();
    let var_value = unsafe { JS_NewString(ctx, b"MyRustApp\0".as_ptr() as _) };
    unsafe { JS_SetPropertyStr(ctx, global_obj, var_name.as_ptr(), var_value) };
    
    // 3. 定义JS函数
    let func_code = CString::new(r#"
        function calculate(data) {
            if (!data) throw new Error("Invalid input");
            return {
                sum: data.a + data.b,
                product: data.a * data.b
            };
        }
    "#).unwrap();
    
    // 4. 执行函数定义
    unsafe { 
        JS_Eval(
            ctx, 
            func_code.as_ptr(), 
            func_code.as_bytes().len() as _,
            b"module.js\0".as_ptr() as _,
            0
        ) 
    };
    
    // 5. 准备调用参数
    let func_name = CString::new("calculate").unwrap();
    let func_val = unsafe { JS_GetPropertyStr(ctx, global_obj, func_name.as_ptr()) };
    
    // 6. 创建参数对象
    let arg_obj = unsafe { JS_NewObject(ctx) };
    let prop_a = CString::new("a").unwrap();
    let prop_b = CString::new("b").unwrap();
    unsafe { 
        JS_SetPropertyStr(ctx, arg_obj, prop_a.as_ptr(), JS_NewInt32(ctx, 3));
        JS_SetPropertyStr(ctx, arg_obj, prop_b.as_ptr(), JS_NewInt32(ctx, 4));
    }
    
    // 7. 调用函数
    let args = [arg_obj];
    let result = unsafe { 
        JS_Call(ctx, func_val, global_obj, args.len() as i32, args.as_ptr()) 
    };
    
    // 8. 处理结果或异常
    if unsafe { JS_IsException(result) } {
        let exception = unsafe { JS_GetException(ctx) };
        let exception_str = unsafe { JS_NewString(ctx, b"Error\0".as_ptr() as _) };
        println!("JS异常发生");
        unsafe { JS_FreeValue(ctx, exception) };
    } else {
        // 从结果对象中提取属性
        let sum_prop = CString::new("sum").unwrap();
        let sum_val = unsafe { JS_GetPropertyStr(ctx, result, sum_prop.as_ptr()) };
        let mut sum = 0;
        unsafe { JS_ToInt32(ctx, &mut sum, sum_val) };
        
        let product_prop = CString::new("product").unwrap();
        let product_val = unsafe { JS_GetPropertyStr(ctx, result, product_prop.as_ptr()) };
        let mut product = 0;
        unsafe { JS_ToInt32(ctx, &mut product, product_val) };
        
        println!("计算结果 - 和: {}, 积: {}", sum, product);
        
        unsafe {
            JS_FreeValue(ctx, sum_val);
            JS_FreeValue(ctx, product_val);
        }
    }
    
    // 9. 清理资源
    unsafe {
        JS_FreeValue(ctx, result);
        JS_FreeValue(ctx, arg_obj);
        JS_FreeValue(ctx, func_val);
        JS_FreeValue(ctx, global_obj);
        JS_FreeContext(ctx);
        JS_FreeRuntime(rt);
    }
}

最佳实践

  1. 使用unsafe块隔离所有QuickJS调用
  2. 确保所有创建的JS值都被正确释放
  3. 检查所有可能返回空指针的调用
  4. 考虑使用更高级的rquickjs包装库
  5. 实现错误处理机制处理JS异常

1 回复

Rust QuickJS绑定库rquickjs-sys使用指南

简介

rquickjs-sys是Rust语言对QuickJS JavaScript引擎的绑定库,提供了高性能的JavaScript执行环境集成能力。QuickJS是一个轻量级、可嵌入的JavaScript引擎,具有以下特点:

  • 极小的内存占用和启动时间
  • 几乎完整的ES2020支持
  • 可选的内存安全限制
  • 支持模块加载系统

基本使用方法

添加依赖

首先在Cargo.toml中添加依赖:

[dependencies]
rquickjs-sys = "0.4"

基本示例

use rquickjs_sys as q;

fn main() {
    // 创建运行时
    let rt = unsafe { q::JS_NewRuntime() };
    
    // 创建上下文
    let ctx = unsafe { q::JS_NewContext(rt) };
    
    // 执行简单JavaScript代码
    let code = "1 + 2";
    let val = unsafe {
        q::JS_Eval(
            ctx,
            code.as_ptr() as *const i8,
            code.len(),
            "script.js".as_ptr() as *const i8,
            q::JS_EVAL_TYPE_GLOBAL as i32,
        )
    };
    
    // 检查执行结果
    if unsafe { q::JS_IsException(val) } != 0 {
        // 处理错误
        let ex = unsafe { q::JS_GetException(ctx) };
        let mut len = 0;
        let cstr = unsafe { q::JS_ToCStringLen(ctx, &mut len, ex) };
        let err_str = unsafe { std::ffi::CStr::from_ptr(cstr) }.to_string_lossy();
        println!("Error: {}", err_str);
        unsafe { q::JS_F极CString(ctx, cstr) };
    } else {
        // 获取结果
        let mut num = 0;
        unsafe { q::JS_ToInt32(ctx, &mut num, val) };
        println!("Result: {}", num);
    }
    
    // 清理资源
    unsafe {
        q::JS_FreeValue(ctx, val);
        q::JS_FreeContext(ctx);
        q::JS_FreeRuntime(rt);
    }
}

完整示例代码

下面是一个结合了基本使用、内存限制和Rust-JS互操作的完整示例:

use rquickjs_sys as q;
use std::ffi::CString;

// 暴露给JS的Rust函数 - 计算斐波那契数列
extern "C" fn fibonacci(ctx: *mut q::JSContext, _this: q::JSValue, argc: i32, argv: *mut q::JSValue) -> q::JSValue {
    if argc < 1 {
        return unsafe { q::JS_ThrowTypeError(ctx, "Expected 1 argument".as_ptr() as *const i8) };
    }

    let mut n = 0;
    unsafe { q::JS_ToInt32(ctx, &mut n, *argv) };

    fn fib(n: i32) -> i32 {
        match n {
            0 => 0,
            1 => 1,
            _ => fib(n - 1) + fib(n - 2)
        }
    }

    unsafe { q::JS_NewInt32(ctx, fib(n)) }
}

fn main() {
    // 1. 初始化运行时和上下文
    let rt = unsafe { q::JS_NewRuntime() };
    let ctx = unsafe { q::JS_NewContext(rt) };

    // 2. 设置内存限制为8MB
    unsafe {
        q::JS_SetMemoryLimit(rt, 8 * 1024 * 1024);
        q::JS_SetGCThreshold(rt, 1 * 1024 * 1024);
    }

    // 3. 暴露Rust函数给JS
    let global = unsafe { q::JS_GetGlobalObject(ctx) };
    let func_name = CString::new("fib").unwrap();
    let func = unsafe { q::JS_NewCFunction(ctx, Some(fibonacci), func_name.as_ptr(), 1) };
    unsafe {
        q::JS_SetPropertyStr(ctx, global, func_name.as_ptr(), func);
        q::JS_FreeValue(ctx, global);
    }

    // 4. 执行JS代码
    let js_code = r#"
        function test() {
            try {
                console.log('Fib(10) =', fib(10));
                console.log('Fib(20) =', fib(20));
                
                // 测试错误处理
                console.log('Result:', notDefinedFunction());
            } catch (e) {
                console.error('Caught error:', e);
            }
        }
        test();
    "#;

    let val = unsafe {
        q::JS_Eval(
            ctx,
            js_code.as_ptr() as *const i8,
            js_code.len(),
            "script.js".as_ptr() as *const i8,
            q::JS_EVAL_TYPE_GLOBAL as i32,
        )
    };

    // 5. 处理执行结果
    if unsafe { q::JS_IsException(val) } != 0 {
        let ex = unsafe { q::JS_GetException(ctx) };
        let mut len = 0;
        let cstr = unsafe { q::JS_ToCStringLen(ctx, &mut len, ex) };
        let err_str = unsafe { std::ffi::CStr::from_ptr(cstr) }.to_string_lossy();
        println!("JS Error: {}", err_str);
        unsafe { q::JS_FreeCString(ctx, cstr) };
    } else {
        println!("JS executed successfully");
    }

    // 6. 清理资源
    unsafe {
        q::JS_FreeValue(ctx, val);
        q::JS_FreeContext(ctx);
        q::JS_FreeRuntime(rt);
    }
}

安全注意事项

  1. 大部分rquickjs-sys函数都是不安全的,需要手动管理内存和生命周期
  2. 确保在释放运行时前释放所有上下文
  3. 处理JavaScript异常以避免内存泄漏
  4. 考虑使用更高级的封装库(如rquickjs)以获得更安全的API

性能优化建议

  1. 重用运行时和上下文对象
  2. 预编译常用脚本
  3. 合理设置内存限制
  4. 避免频繁的Rust-JavaScript边界跨越

总结

rquickjs-sys提供了Rust与QuickJS引擎的低级绑定,适合需要精细控制JavaScript执行环境的高级场景。对于更简单的用例,可以考虑基于rquickjs-sys构建的高级封装库。

回到顶部