介绍 bindgen 是一个能自动为 C(或 C++)库生成 Rust 绑定的辅助库和命令行工具。
elk 是一个迷你的JS引擎. 能够实现类似于这样的效果
main.rs 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" 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 ]); double b = js_getnum (args[1 ]); return js_mknum (a + b); } int main (void) { char mem[200 ]; struct js *js = js_create (mem, sizeof (mem)); js_set (js, js_glob (js), "sum" , js_mkfun (sum))); jsval_t result = js_eval (js, "sum(3, 4);" , ~0 ); printf ("result: %s\n" , js_str (js, result)); return 0 ; }
如果这个执行内容来自于服务器下发,那就可以很方便地动态下发程序然后执行特定的任务.
序 之前在玩ESP-IDF时候就尝试内嵌一个Lua引擎来动态执行Lua脚本的操作,然后用蓝牙更新Lua脚本以实现动态的绘制界面. 最近弄ESP-RS时候想试试类似的效果,因为感觉Lua用起来很啰嗦,现在已经完全忘记如何编写Lua脚本了 . 当然Rust也有一些Lua引擎的现成crates,比如rLua . 但是数组索引从1开始真的是坏文明啊, 我们还是继续捣鼓js吧.
创建Rust项目
克隆elk源码 进入刚才创建的目录后,在src同级目录下克隆elk.
1 2 3 4 5 6 7 8 9 cd bindgen_elkgit clone https://github.com/cesanta/elk.git Cloning into 'elk' ... remote: Enumerating objects: 932, done . remote: Counting objects: 100% (204/204), done . remote: Compressing objects: 100% (101/101), done . remote: Total 932 (delta 95), reused 152 (delta 87), pack-reused 728 (from 1) Receiving objects: 100% (932/932), 4.66 MiB | 12.09 MiB/s, done . Resolving deltas: 100% (442/442), done .
配置依赖 在当前项目的Cargo.toml中添加bindgen 和cc 依赖.
Cargo.toml 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 的头文件,并在该文件内引入想要绑定的库头文件.
wrapper.h
编写build.rs 在src同级别目录下创建一个名为build.rs 的文件, 当存在build.rs 文件时候, Cargo会优先编译执行该文件.
请注意指定include目录, 我用的是mingw64, 所以在bindgen::Builder::default()时候,手动指定了target和sysroot
1 2 3 4 5 6 7 8 9 10 11 let bindings = bindgen::Builder::default () .header ("wrapper.h" ) .parse_callbacks (Box ::new (bindgen::CargoCallbacks)) .clang_arg ("--target=x86_64-w64-mingw32" ) .clang_arg (format! ("--sysroot={}" , "C:/Program Files/mingw64" )) .clang_arg ("-IC:/Program Files/mingw64/lib/gcc/x86_64-w64-mingw32/8.1.0/include" ) .generate () .expect ("Unable to generate bindings" );
内容如下,具体步骤含义见注释.
build.rs 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 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 use std::{ env, path::{Path, PathBuf}, }; extern crate cc;fn compile_libelk () { let src = ["elk/elk.c" ]; let mut builder = cc::Build::new (); let build = builder.files (src.iter ()).include ("elk" ).flag ("-DJS_DUMP" ); build.compile ("elk" ); } fn bindgen_generate () { let dir = env::var ("CARGO_MANIFEST_DIR" ).unwrap (); println! ( "cargo:rustc-link-search=native={}" , Path::new (&dir).join ("elk" ).display () ); println! ("cargo:rerun-if-changed=wrapper.h" ); let bindings = bindgen::Builder::default () .header ("wrapper.h" ) .parse_callbacks (Box ::new (bindgen::CargoCallbacks)) .clang_arg ("--target=x86_64-w64-mingw32" ) .clang_arg (format! ("--sysroot={}" , "C:/Program Files/mingw64" )) .clang_arg ("-IC:/Program Files/mingw64/lib/gcc/x86_64-w64-mingw32/8.1.0/include" ) .generate () .expect ("Unable to generate bindings" ); let out_path = PathBuf::from (env::var ("OUT_DIR" ).unwrap ()); bindings .write_to_file (out_path.join ("bindings.rs" )) .expect ("Couldn't write bindings!" ); } fn main () { compile_libelk (); bindgen_generate (); }
生成并使用binding.rs 如果你的环境正常,这时候只需要执行cargo build即可
1 2 3 4 PS 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 34 use std::ffi::{CString, CStr};include!(concat! (env! ("OUT_DIR" ), "/bindings.rs" )); #[unsafe(no_mangle)] 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 4 PS 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
参考资料