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交互的示例,包含结构体和回调函数的使用:
- 首先创建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);
}
- 编译为动态库:
cargo build --release
- 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);
- 运行Deno脚本:
deno run --allow-ffi --unstable main.js
这个扩展示例展示了如何使用deno_ffi处理更复杂的场景,包括结构体、字符串指针和回调函数。注意使用时需要正确处理内存管理和类型转换。
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"
这个完整示例展示了:
- 如何在Rust中处理复杂数据类型(图像)
- 内存管理的最佳实践
- 错误处理
- 二进制数据传输
- 资源清理
要运行这个示例:
- 将上述Rust代码保存为src/lib.rs
- 创建Cargo.toml文件
- 运行
cargo build --release
编译Rust库 - 创建Deno脚本(main.ts)
- 准备一个input.jpg图片
- 运行
deno run --allow-read --allow-write main.ts