FFI
链接C ABI
动态链接库(实操分享)不需要依赖任何第三方crate
就可达成·运行时·链接的功能要求。至于使用第三方crate
所带来的好处,我将在文章末尾给出解释与列举。
首先,在rs
代码里,使用extern { ... }
块导入外部函数。代码模板如下:
#[link(name = "actual_lib_name_without_extname")] extern {
#[link_name = "actual_external_function_name"] // 支持对`FFI`函数重命名
fn external_function_local_alias(_: *const c_char) -> *const c_char; // 原始指针`*const c_char`是对`FFI`安全的。
...
}
上述【代码模板】解释:
actual_lib_name_without_extname
需要被替换为【链接库文件名(不含扩展名与lib
前缀)】actual_external_function_name
需要被替换为【外部函数真实名字】external_function_local_alias
需要被替换为【外部函数的本地别名】。即,根据本地命名规范,对外部函数·重命名。然后,设置环境变量$RUSTFLAGS
export RUSTFLAGS=-L native=<链接库搜索目录>
更多解释:
C ABI
动态链接库(文件)】必须被预置于此<链接库搜索目录>下。= note: ld.exe: cannot find -l<链接库文件名>
。$RUSTFLAGS
会将【编译器配置指令-L
】传递给rustc
核心和向Library Search Path
清单临时添加一个新检索目录。Cargo Package
根目录为起点的【相对路径】。native=
前缀表示:在该<链接库搜索目录>下预存都是C ABI
链接库,而不是Rust ABI
链接库。.cargo\config.toml [build] rustflags = "***"
配置项内,设置此-L
编译器参数不管用 — 原因不详且和Cargo Book
文档描述不符。接着,若你的目标仅只是cargo build
编译出一个.exe
可执行文件,那么到这就可以打住了。
再续,若你的目标是cargo run
既编译源码又运行可执行文件,那么还有一步需要被完成。即,使【C ABI
动态链接库】对编译输出的.exe
文件可见。否则,在应用程序启动过程中,会遇到(exit code: 0xc0000135, STATUS_DLL_NOT_FOUND)
的错误和程序崩溃退出。其支持两种作法:
C ABI
动态链接库(文件)】至【编译输出.exe
文件】所在文件夹内。Cargo Package
根目录下,编写一个简单的build.rs
构建脚本cargo
,在编译过程中,在$OUT_DIR
文件夹内(即,target\debug
或target\release
),创建一个指向【C ABI
动态链接库(文件)】的【符号链接】。build.rs
程序,可参考:
use ::std::{env, fs, os, path::{Path, PathBuf}};fn main() { let out_dir = env::var("OUT_DIR").unwrap(); let work_dir = vec!["../../..", "../../../deps"]; work_dir.iter().for_each(|dir_path| symbolic_link_dll(&Path::new(&out_dir[..]).join(dir_path).canonicalize().unwrap())); }#[cfg(windows)]fn symbolic_link_dll(exe_dir: &PathBuf) { const DLL_FILE: &str = "auxiliaries_native.dll"; let mut dll_origin = env::current_dir().unwrap(); dll_origin.push("assets"); dll_origin.push(DLL_FILE); if dll_origin.exists() { let dll_symbol = exe_dir.join(DLL_FILE); if dll_symbol.exists() { fs::remove_file(dll_symbol.clone()).unwrap(); } os::windows::fs::symlink_file(dll_origin.clone(), dll_symbol.clone()).unwrap(); } }#[cfg(not(windows))]fn symbolic_link_dll(exe_dir: &PathBuf) { unreachable!("算是家庭作业,自己实现看看。其实,和`win32`的差不多!"); }最后,执行cargo run
命令,完成:
.exe
可执行文件。C ABI
动态链接库】文件和链接之。dll
丢了、找不到了,程序直接崩溃退出 —— 连写日志的机会都没有。GUI
错误提示框。啥都没有,难死我了!GUI
主界面。在我的业务场景下,该应用程序是一个Win32 GUI App
— 体积绝对碾压electron
(比性能,算我欺负你)。
crate
可带来的好处相比于直接写extern {...}
块的简单粗暴,使用第三方crate
(比如,dlopen
)可带来的优势有两点:
C ABI
动态链接库】。这样,应用程序的启动与初始化延时会更短些。.dll
后缀文件?”,而不是没头没脑地直接崩溃退出 — 特别是,禁用了console
的【产品模式】真会导致什么崩溃线索都找不到。甲方还一口咬定一个文件都没有误删!太恶心了!
弹个对话框至少还留了一丝与产品经理狡辩的机会:“瞧!是不是,甲方一定是把某个关键的dll
给误删了。不是代码的错!”。Nice! 就是这个范儿!运行时【动态链接】是将【依赖项】置于.exe
文件之外的。若遇到链接库文件丢失的情况,应用程序就不能正常运行了。
所以,我的下一个目标就是:在编译时,将【静态链接库.a
文件】直接编译入.exe
可执行文件内,来避免dll
文件意外丢失的问题(当然,.exe
文件的体积也会更大些)。但是,我正遇到了一个mingw64
的编译错误undefined reference to 'BCryptGenRandom'
还未搞定。若你对此也有兴趣,请待我的后续更新...