前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[Rust笔记] 【运行时】FFI 链接 C ABI 动态链接库(实操分享)

[Rust笔记] 【运行时】FFI 链接 C ABI 动态链接库(实操分享)

作者头像
MikeLoveRust
发布2022-11-28 15:14:52
9860
发布2022-11-28 15:14:52
举报

【运行时】FFI链接C ABI动态链接库(实操分享)

不需要依赖任何第三方crate就可达成·运行时·链接的功能要求。至于使用第三方crate所带来的好处,我将在文章末尾给出解释与列举。

"干货"步骤

首先,在rs代码里,使用extern { ... }块导入外部函数。代码模板如下:

代码语言:javascript
复制
#[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

代码语言:javascript
复制
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\debugtarget\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'还未搞定。若你对此也有兴趣,请待我的后续更新...

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-06-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Rust语言学习交流 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 【运行时】FFI链接C ABI动态链接库(实操分享)
    • "干货"步骤
      • 第三方crate可带来的好处
        • 遗憾·待续
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档