Rust FFI插件库deno_ffi的使用:实现Rust与Deno的高效跨语言交互与系统集成

Rust FFI插件库deno_ffi的使用:实现Rust与Deno的高效跨语言交互与系统集成

deno_ffi是一个实现了动态库FFI(外部函数接口)的Rust crate。

性能表现

Deno FFI调用具有极低的开销(在M1 16GB RAM机器上约1纳秒),性能与原生代码相当。Deno利用V8快速API调用和JIT编译绑定来实现这种高速性能。

Deno.dlopen会生成一个优化路径和一个后备路径。当V8决定优化该函数时,就会触发优化路径,从而通过Fast API进行调用。后备路径则处理像函数回调这样的类型,并为Fast调用不支持的意外类型实现适当的错误处理。

优化调用会进入一个JIT编译的函数"trampoline",该函数直接为符号调用转换Fast API值。由于tinycc,JIT编译本身非常快。目前,优化路径仅在Linux和MacOS上受支持。

运行基准测试的命令:

target/release/deno bench --allow-ffi --allow-read --unstable-ffi ./tests/ffi/tests/bench.js

安装

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

cargo add deno_ffi

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

deno_ffi = "0.199.0"

完整示例

下面是一个更完整的Rust与Deno通过deno_ffi交互的示例,包含结构体和回调函数的使用:

  1. 首先创建Rust库:
// lib.rs
use std::os::raw::c_char;
use std::ffi::CStr;

// 定义结构体
#[repr(C)]
pub struct User {
    pub id: i32,
    pub name: *const c_char,
}

// 回调函数类型
type Callback = extern "C" fn(i32);

#[no_mangle]
pub extern "C" fn create_user(id: i32, name: *const c_char) -> User {
    User { id, name }
}

#[no_mangle]
pub extern "C" fn print_user(user: User) {
    unsafe {
        let name = CStr::from_ptr(user.name).to_str().unwrap();
        println!("User {}: {}", user.id, name);
    }
}

#[no_mangle]
pub extern "C" fn process_with_callback(value: i32, callback: Callback) {
    println!("Processing value: {}", value);
    callback(value * 2);
}
  1. 编译为动态库:
cargo build --release
  1. Deno代码调用Rust函数:
// main.js
// 加载动态库
const dylib = Deno.dlopen(
  "./target/release/libdeno_example.dylib", // 或 .so/.dll
  {
    create_user: {
      parameters: ["i32", "pointer"],
      result: { struct: ["i32", "pointer"] },
    },
    print_user: {
      parameters: [{ struct: ["i32", "pointer"] }],
      result: "void",
    },
    process_with_callback: {
      parameters: ["i32", { function: { parameters: ["i32"], result: "void" } }],
      result: "void",
    },
  },
);

// 创建字符串指针
const name = new TextEncoder().encode("John Doe\0");
const namePtr = Deno.UnsafePointer.of(name);

// 创建用户
const user = dylib.symbols.create_user(1, namePtr);
console.log("Created user with id:", user.id);

// 打印用户
dylib.symbols.print_user(user);

// 使用回调函数
dylib.symbols.process_with_callback(10, (result) => {
  console.log("Callback received:", result); // 输出: Callback received: 20
});

// 释放内存
Deno.UnsafePointerView.getCString(user.name);
  1. 运行Deno脚本:
deno run --allow-ffi --unstable main.js

这个扩展示例展示了如何使用deno_ffi处理更复杂的场景,包括结构体、字符串指针和回调函数。注意使用时需要正确处理内存管理和类型转换。


1 回复

Rust FFI插件库deno_ffi的使用:实现Rust与Deno的高效跨语言交互与系统集成

介绍

deno_ffi是一个用于在Deno运行时中调用Rust代码的FFI(外部函数接口)插件库。它允许开发者将高性能的Rust代码与JavaScript/TypeScript生态无缝集成,特别适合需要系统级访问或高性能计算的场景。

主要特点:

  • 在Deno中直接调用Rust编译的动态库
  • 类型安全的跨语言交互
  • 高性能的跨语言调用
  • 支持异步操作
  • 简单的API设计

基本使用示例

Rust端代码

use deno_ffi::*;
use std::ffi::{CStr, CString};
use std::os::raw::c_char;

// 简单的加法函数
#[no_mangle]
pub extern "C" fn add_numbers(a: i32, b: i32) -> i32 {
    a + b
}

// 字符串反转函数
#[no_mangle]
pub extern "C" fn reverse_string(input: *const c_char) -> *mut c_char {
    // 将C字符串转换为Rust字符串
    let input_str = unsafe { CStr::from_ptr(input) }.to_str().unwrap();
    // 反转字符串
    let reversed = input_str.chars().rev().collect::<String>();
    // 转换回C字符串并返回指针
    CString::new(reversed).unwrap().into_raw()
}

// 异步操作示例
#[ffi_export]
pub fn async_operation(callback: extern "C" fn(i32)) {
    std::thread::spawn(move || {
        // 模拟耗时操作
        std::thread::sleep(std::time::Duration::from_secs(1));
        // 回调JavaScript
        callback(42);
    });
}

Deno/TypeScript端代码

import { load } from "deno_ffi";

async function main() {
  // 加载编译好的Rust动态库
  const lib = await load({
    name: "my_ffi_lib",
    path: "./target/release/libmy_ffi_lib.so", // Linux
    // path: "./target/release/my_ffi_lib.dll", // Windows
    // path: "./target/release/libmy_ffi_lib.dylib", // macOS
  });

  // 调用同步加法函数
  const sum = lib.symbols.add_numbers(10, 20);
  console.log(`10 + 20 = ${sum}`);

  // 调用字符串处理函数
  const reversed = lib.symbols.reverse_string("Hello World");
  console.log(`Reversed: ${reversed}`);

  // 调用异步函数
  await new Promise((resolve) => {
    lib.symbols.async_operation((result) => {
      console.log(`Async result: ${result}`);
      resolve();
    });
  });
}

main().catch(console.error);

完整示例Demo

下面是一个完整的图像处理示例,展示如何使用deno_ffi在Rust中处理图像,然后在Deno中显示结果。

Rust端代码 (src/lib.rs)

use deno_ffi::*;
use image::{DynamicImage, ImageBuffer};
use std::ffi::{CStr, CString};
use std::os::raw::c_char;

// 图像处理函数 - 转换为灰度图
#[ffi_export]
pub fn convert_to_grayscale(
    input_ptr: *const u8,
    input_len: usize,
    output_ptr: *mut *mut u8,
    output_len: *mut usize,
) -> Result<(), String> {
    // 将输入的字节数组转换为图像
    let input_slice = unsafe { std::slice::from_raw_parts(input_ptr, input_len) };
    let img = image::load_from_memory(input_slice)
        .map_err(|e| format!("Failed to load image: {}", e))?;

    // 转换为灰度图
    let gray_img = img.grayscale();

    // 将结果图像编码为PNG
    let mut buffer = Vec::new();
    gray_img
        .write_to(&mut buffer, image::ImageOutputFormat::Png)
        .map_err(|e| format!("Failed to encode image: {}", e))?;

    // 返回结果
    let boxed_slice = buffer.into_boxed_slice();
    let raw_slice = Box::into_raw(boxed_slice);
    
    unsafe {
        *output_ptr = raw_slice as *mut u8;
        *output_len = (*raw_slice).len();
    }

    Ok(())
}

// 释放内存的函数
#[ffi_export]
pub fn free_image_buffer(ptr: *mut u8, len: usize) {
    unsafe {
        let _ = Vec::from_raw_parts(ptr, len, len);
    }
}

Deno端代码 (main.ts)

import { load } from "deno_ffi";

async function processImage(imagePath: string) {
  // 加载FFI库
  const lib = await load({
    name: "image_processor",
    path: "./target/release/libimage_processor.so",
  });

  // 读取图像文件
  const imageData = await Deno.readFile(imagePath);
  
  // 准备输出变量
  let outputPtr: Deno.PointerValue = new Uint8Array();
  let outputLen = 0;

  // 调用Rust函数处理图像
  try {
    await lib.symbols.convert_to_grayscale(
      imageData,
      imageData.length,
      { value: outputPtr },
      { value: outputLen }
    );

    // 获取处理后的图像数据
    const resultPtr = outputPtr.value;
    const resultLen = outputLen.value;
    const resultView = new Deno.UnsafePointerView(resultPtr);
    const result = new Uint8Array(resultLen);
    resultView.copyInto(result);

    // 保存结果
    await Deno.writeFile("grayscale.png", result);
    console.log("Grayscale image saved as grayscale.png");

    // 释放内存
    lib.symbols.free_image_buffer(resultPtr, resultLen);
  } catch (e) {
    console.error("Image processing failed:", e);
  }
}

// 使用示例
processImage("input.jpg").catch(console.error);

Cargo.toml配置

[package]
name = "image_processor"
version = "0.1.0"
edition = "2021"

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

[dependencies]
deno_ffi = "0.1"
image = "0.24"

这个完整示例展示了:

  1. 如何在Rust中处理复杂数据类型(图像)
  2. 内存管理的最佳实践
  3. 错误处理
  4. 二进制数据传输
  5. 资源清理

要运行这个示例:

  1. 将上述Rust代码保存为src/lib.rs
  2. 创建Cargo.toml文件
  3. 运行cargo build --release编译Rust库
  4. 创建Deno脚本(main.ts)
  5. 准备一个input.jpg图片
  6. 运行deno run --allow-read --allow-write main.ts
回到顶部