使用Bindgen为ELK生成Rust绑定
介绍
bindgen 是一个能自动为 C(或 C++)库生成 Rust 绑定的辅助库和命令行工具。
elk 是一个迷你的JS引擎.
能够实现类似于这样的效果1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19##include <stdio.h>
##include "elk.h"
// C function that adds two numbers. Will be called from JS
jsval_t sum(struct js *js, jsval_t *args, int nargs) {
if (nargs != 2) return js_err(js, "2 args expected");
double a = js_getnum(args[0]); // Fetch 1st arg
double b = js_getnum(args[1]); // Fetch 2nd arg
return js_mknum(a + b);
}
int main(void) {
char mem[200];
struct js *js = js_create(mem, sizeof(mem)); // Create JS instance
js_set(js, js_glob(js), "sum", js_mkfun(sum))); // Import sum()
jsval_t result = js_eval(js, "sum(3, 4);", ~0); // Call sum
printf("result: %s\n", js_str(js, result)); // result: 7
return 0;
}
如果这个执行内容来自于服务器下发,那就可以很方便地动态下发程序然后执行特定的任务.
序
之前在玩ESP-IDF时候就尝试内嵌一个Lua引擎来动态执行Lua脚本的操作,然后用蓝牙更新Lua脚本以实现动态的绘制界面.
最近弄ESP-RS时候想试试类似的效果,因为感觉Lua用起来很啰嗦,现在已经完全忘记如何编写Lua脚本了.
当然Rust也有一些Lua引擎的现成crates,比如rLua.
但是数组索引从1开始真的是坏文明啊, 我们还是继续捣鼓js吧.
创建Rust项目
1 | cargo new "bindgen_elk" |
克隆elk源码
进入刚才创建的目录后,在src同级目录下克隆elk.
1 | cd bindgen_elk |
配置依赖
在当前项目的Cargo.toml中添加bindgen和cc依赖.1
2
3
4
5
6
7
8
9
10
11[package]
name = "bindgen_elk"
version = "0.1.0"
edition = "2024"
[dependencies]
libc = "0.2"
[build-dependencies]
cc = "1.0"
bindgen = "0.69"
创建wrapper.h
根据bindgen的规则,需要在项目目录下创建一个叫做wrapper.h的头文件,并在该文件内引入想要绑定的库头文件.
1 | # |
编写build.rs
在src同级别目录下创建一个名为build.rs的文件, 当存在build.rs文件时候, Cargo会优先编译执行该文件.
请注意指定include目录, 我用的是mingw64, 所以在bindgen::Builder::default()时候,手动指定了target和sysroot
1 | let bindings = bindgen::Builder::default() |
内容如下,具体步骤含义见注释.
1 | use std::{ |
生成并使用binding.rs
如果你的环境正常,这时候只需要执行cargo build即可1
2
3
4PS E:\GitHub\bindgen_elk> cargo build
# ...忽略大量无用信息
Compiling bindgen_elk v0.1.0 (E:\GitHub\bindgen_elk)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 11.25s
最终在target\debug\build\bindgen_elk-b6aa022ece64a1fa\out\bindings.rs下找到生成绑定文件.
测试JS脚本
参照文章开头的C调用JS代码, 我们写出Rust版本1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34use std::ffi::{CString, CStr};
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
#
pub unsafe extern "C" fn sum(js: *mut js, args: *mut jsval_t, nargs: i32) -> jsval_t {
if nargs != 2 {
let msg = CString::new("2 args expected").unwrap();
return js_mkerr(js, msg.as_ptr());
}
let a = js_getnum(*args.offset(0));
let b = js_getnum(*args.offset(1));
js_mknum(a + b)
}
fn main() {
unsafe {
let mut mem = [0u8; 8192];
let js = js_create(mem.as_mut_ptr() as *mut _, mem.len());
let name = CString::new("sum").unwrap();
js_set(js, js_glob(js), name.as_ptr(), js_mkfun(Some(sum)));
let code = CString::new("sum(3, 4);").unwrap();
let code_len = code.as_bytes().len();
let result = js_eval(js, code.as_ptr(), code_len);
let s = CStr::from_ptr(js_str(js, result)).to_str().unwrap();
println!("result: {}", s);
}
}
执行以后可以看到输出1
2
3
4PS E:\GitHub\bindgen_elk> cargo run
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.94s
Running `target\debug\bindgen_elk.exe`
result: 7
环境
- rustup 1.28.2 (e4f3ad6f8 2025-04-28)
- cargo 1.89.0-nightly (fc1518ef0 2025-06-06)
- elk @ a9bb856
参考资料
使用Bindgen为ELK生成Rust绑定
