Rust N-API绑定库napi_sym的使用:高效实现Rust与Node.js的跨语言交互

Rust N-API绑定库napi_sym的使用:高效实现Rust与Node.js的跨语言交互

napi_sym简介

napi_sym是一个用于Deno的Node-API实现的proc_macro宏。它主要完成以下功能:

  1. 将符号标记为#[no_mangle]并重写为unsafe extern "C" $name
  2. 确保函数符号存在于symbol_exports.json
  3. deno_napi::Result映射到原始的napi_result

示例代码

以下是内容中提供的示例代码:

use deno_napi::Env;
use deno_napi::Error;
use deno_napi::Result;
use deno_napi::napi_value;

#[napi_sym::napi_sym]
fn napi_get_boolean(
  env: *mut Env,
  value: bool,
  result: *mut napi_value,
) -> Result {
  let _env: &mut Env = env.as_mut().ok_or(Error::InvalidArg)?;
  // *result = ...
  Ok(())
}

完整示例

下面是一个更完整的Rust与Node.js交互的示例,展示了如何使用napi_sym创建Node.js原生扩展:

use deno_napi::{Env, Error, Result, napi_value};
use std::ffi::CString;

// 定义一个简单的加法函数
#[napi_sym::napi_sym]
fn napi_add_numbers(
    env: *mut Env,
    a: i32,
    b: i32,
    result: *mut napi_value,
) -> Result {
    let env = unsafe { &mut *env };
    let sum = a + b;
    env.create_int32(sum, result)
}

// 定义一个字符串处理函数
#[napi_sym::napi_sym]
fn napi_concatenate_strings(
    env: *mut Env,
    a_ptr: *const u8,
    a_len: usize,
    b_ptr: *const u8,
    b_len: usize,
    result: *mut napi_value,
) -> Result {
    let env = unsafe { &mut *env };
    
    // 将原始指针转换为Rust字符串
    let a = unsafe { std::slice::from_raw_parts(a_ptr, a_len) };
    let b = unsafe { std::slice::from_raw_parts(b_ptr, b_len) };
    
    let a_str = String::from_utf8(a.to_vec()).map_err(|_| Error::InvalidArg)?;
    let b_str = String::from_utf8(b.to_vec()).map_err(|_| Error::InvalidArg)?;
    
    let concatenated = format!("{}{}", a_str, b_str);
    env.create_string(&concatenated, result)
}

// 导出一个对象包含多个方法
#[napi_sym::napi_sym]
fn napi_init(env: *mut Env, exports: *mut napi_value) -> Result {
    let env = unsafe { &mut *env };
    
    // 创建一个对象
    let mut obj = env.create_object()?;
    
    // 添加方法
    env.set_named_property(
        &mut obj,
        "add",
        env.create_function("add", napi_add_numbers)?
    )?;
    
    env.set_named_property(
        &mut obj,
        "concat",
        env.create_function("concat", napi_concatenate_strings)?
    )?;
    
    // 将对象赋值给exports
    unsafe { *exports = obj };
    Ok(())
}

symbol_exports.json

symbol_exports.json是一个包含需要在链接时放入可执行文件动态符号表中的符号的文件。在不同平台上使用不同的方式实现:

  • Windows: 使用/DEF:选项
  • macOS: 使用-exported_symbol,_选项
  • Linux: 使用--export-dynamic-symbol=选项

在Windows上,需要运行tools/napi/generate_symbols_lists.js来生成.def文件。

安装和使用

要在项目中使用napi_sym,可以运行以下Cargo命令:

cargo add napi_sym

或者在Cargo.toml中添加:

napi_sym = "0.141.0"

这个库由Deno团队维护,主要贡献者包括Ryan Dahl、David Sherret和Bartek Iwańczuk等。


1 回复

Rust N-API绑定库napi_sym的使用:高效实现Rust与Node.js的跨语言交互

完整示例Demo

1. 创建简单的函数绑定

// 导入必要的库
use napi::{bindgen_prelude::*, JsObject};
use napi_sym::symbol;

// 定义一个简单的加法函数
#[symbol]
fn add(a: u32, b: u32) -> u32 {
    a + b
}

// 初始化模块
#[napi]
pub fn init_module(module: JsObject) -> Result<()> {
    // 将add函数暴露给Node.js
    module.create_named_method("add", add)?;
    Ok(())
}

2. 异步文件读取示例

use napi::{bindgen_prelude::*, JsObject};
use napi_sym::symbol;
use tokio::fs;

// 异步读取文件内容
#[symbol]
async fn read_file_async(path: String) -> Result<String> {
    fs::read_to_string(path).await.map_err(|e| {
        Error::new(Status::GenericFailure, format!("Failed to read file: {}", e))
    })
}

#[napi]
pub fn init_module(module: JsObject) -> Result<()> {
    module.create_named_method("readFileAsync", read_file_async)?;
    Ok(())
}

3. 处理JSON对象

use napi::{bindgen_prelude::*, JsObject};
use napi_sym::symbol;
use serde::{Deserialize, Serialize};

// 定义用户结构体
#[derive(Serialize, Deserialize)]
struct User {
    name: String,
    age: u32,
}

// 处理用户对象
#[symbol]
fn process_user(user: User) -> String {
    format!("Processed user: {}, age {}", user.name, user.age)
}

#[napi]
pub fn init_module(module: JsObject) -> Result<()> {
    module.create_named_method("processUser", process_user)?;
    Ok(())
}

4. 完整项目示例

Cargo.toml

[package]
name = "napi-sym-demo"
version = "0.1.0"
edition = "2021"

[lib]
crate-type = ["cdylib"]

[dependencies]
napi = "2.0"
napi-sym = "0.1"
tokio = { version = "1.0", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }

src/lib.rs

use napi::{bindgen_prelude::*, JsObject};
use napi_sym::symbol;
use serde::{Deserialize, Serialize};
use tokio::fs;

// 简单加法
#[symbol]
fn add(a: u32, b: u32) -> u32 {
    a + b
}

// 异步文件读取
#[symbol]
async fn read_file(path: String) -> Result<String> {
    fs::read_to_string(path).await.map_err(|e| {
        Error::new(Status::GenericFailure, format!("Failed to read file: {}", e))
    })
}

// 用户处理
#[derive(Serialize, Deserialize)]
struct User {
    name: String,
    age: u32,
}

#[symbol]
fn create_user(user: User) -> String {
    format!("Created user: {}, age {}", user.name, user.age)
}

// 错误处理示例
#[symbol]
fn divide(a: f64, b: f64) -> Result<f64> {
    if b == 0.0 {
        return Err(Error::new(Status::GenericFailure, "Division by zero"));
    }
    Ok(a / b)
}

// 模块初始化
#[napi]
pub fn init_module(module: JsObject) -> Result<()> {
    module.create_named_method("add", add)?;
    module.create_named_method("readFile", read_file)?;
    module.create_named_method("createUser", create_user)?;
    module.create_named_method("divide", divide)?;
    Ok(())
}

Node.js使用示例

const { add, readFile, createUser, divide } = require('./target/release/napi-sym-demo');

// 同步函数调用
console.log(add(5, 3)); // 输出: 8

// 异步函数调用
readFile('example.txt')
  .then(content => console.log('File content:', content))
  .catch(err => console.error('Error:', err));

// 对象处理
console.log(createUser({ name: 'Bob', age: 25 }));
// 输出: "Created user: Bob, age 25"

// 错误处理
try {
  console.log(divide(10, 0));
} catch (err) {
  console.error('Error:', err.message); // 输出: "Division by zero"
}

构建命令

# 构建发布版本
cargo build --release

# 在Node.js中测试
node test.js

这个完整示例展示了如何使用napi_sym库创建功能完善的Node.js原生扩展模块,包括同步函数、异步操作、JSON对象处理和错误处理等常见场景。

回到顶部