You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为Rust编写的可执行程序提供支持Mod的友好API?

实现Rust程序的Mod API方案

核心思路:动态加载共享库

你的可执行程序需要通过动态加载共享库(对应平台的.so/.dll/.dylib文件)实现Mod加载,这是跨语言Mod支持的标准方案。Rust中可以借助libloading crate完成动态库加载,同时通过规范的C绑定接口,让Rust和C编写的Mod都能兼容调用开放功能。


步骤1:定义稳定的C兼容API接口

先将需要开放的功能封装为C兼容函数——C是跨语言交互的通用中间层,Rust Mod也能通过extern "C"语法调用这些接口。

示例:提供给C/C++开发者的API头文件

创建my_app_api.h:

#ifndef MY_APP_API_H
#define MY_APP_API_H

// 通用错误码定义
typedef enum {
    MY_APP_OK = 0,
    MY_APP_ERR_INVALID_ARG,
    MY_APP_ERR_NOT_FOUND
} MyAppResult;

// 程序核心上下文指针(内部由主程序管理,Mod仅做传递)
typedef struct MyAppContext MyAppContext;

// 开放API:获取应用版本号
const char* my_app_get_version(MyAppContext* ctx);

// 开放API:注册Mod初始化回调(Mod通过该回调接收程序启动事件)
MyAppResult my_app_register_init_callback(MyAppContext* ctx, void (*init_fn)(MyAppContext*));

#endif

Rust端实现API导出

在主程序中用#[no_mangle]和extern "C"标记导出函数,确保名称不被编译混淆:

use std::ffi::CString;
use std::sync::Mutex;

// 主程序上下文结构体,存储状态和Mod回调列表
pub struct MyAppContext {
    version: &'static str,
    init_callbacks: Mutex<Vec<extern "C" fn(*mut MyAppContext)>>,
}

#[no_mangle]
pub extern "C" fn my_app_get_version(ctx: *mut MyAppContext) -> *const u8 {
    let ctx = unsafe { &*ctx };
    CString::new(ctx.version).unwrap().into_raw()
}

#[no_mangle]
pub extern "C" fn my_app_register_init_callback(
    ctx: *mut MyAppContext,
    init_fn: extern "C" fn(*mut MyAppContext),
) -> i32 {
    let ctx = unsafe { &mut *ctx };
    ctx.init_callbacks.lock().unwrap().push(init_fn);
    0 // 返回MY_APP_OK
}

// 程序启动时触发所有Mod的初始化回调
pub fn trigger_mod_inits(ctx: *mut MyAppContext) {
    let ctx = unsafe { &mut *ctx };
    for callback in ctx.init_callbacks.lock().unwrap().iter() {
        callback(ctx);
    }
}

注意:所有导出函数必须遵循C调用约定,且#[no_mangle]是必须的,否则编译后函数名会被混淆,动态加载时无法找到。


步骤2:实现Mod自动加载逻辑

使用libloading和glob crate扫描mods文件夹,自动加载其中的共享库。

1. 添加依赖到Cargo.toml

[dependencies]
libloading = "0.8"
glob = "0.3" # 用于递归扫描文件夹中的共享库文件

2. 编写加载逻辑代码

use glob::glob;
use libloading::{Library, Symbol};
use std::path::Path;

// 定义Mod入口函数签名:所有Mod必须导出`my_app_mod_init`函数
type ModInitFn = extern "C" fn(*mut MyAppContext) -> i32;

pub fn load_mods(ctx: *mut MyAppContext, mods_dir: &str) -> Result<Vec<Library>, Box<dyn std::error::Error>> {
    let mut loaded_libs = Vec::new();

    // 根据平台匹配共享库后缀
    let pattern = match std::env::consts::OS {
        "windows" => format!("{}/**/*.dll", mods_dir),
        "macos" => format!("{}/**/*.dylib", mods_dir),
        _ => format!("{}/**/*.so", mods_dir),
    };

    for entry in glob(&pattern)? {
        let path = entry?;
        if !path.is_file() {
            continue;
        }

        // 加载动态库
        let lib = Library::new(&path)?;
        println!("Loaded mod: {}", path.display());

        // 获取Mod的初始化入口函数
        let init_fn: Symbol<ModInitFn> = unsafe { lib.get(b"my_app_mod_init")? };
        // 调用Mod初始化函数,传入主程序上下文
        let result = init_fn(ctx);
        if result != 0 {
            eprintln!("Mod {} initialization failed (error code: {})", path.display(), result);
            continue;
        }

        loaded_libs.push(lib);
    }

    Ok(loaded_libs)
}

步骤3:为Rust Mod开发者提供封装工具

针对Rust开发者,你可以发布一个my_app_mod crate,封装C绑定的细节,降低Mod开发门槛。

示例:my_app_mod crate的核心代码

use std::ffi::CStr;

// 导入主程序的C绑定
#[link(name = "my_app")]
extern "C" {
    pub fn my_app_get_version(ctx: *mut MyAppContext) -> *const u8;
    pub fn my_app_register_init_callback(
        ctx: *mut MyAppContext,
        init_fn: extern "C" fn(*mut MyAppContext),
    ) -> i32;
}

#[repr(C)]
pub struct MyAppContext;

// 封装为Rust友好的函数
pub fn get_version(ctx: &mut MyAppContext) -> &str {
    unsafe {
        let c_str = CStr::from_ptr(my_app_get_version(ctx as *mut _));
        c_str.to_str().unwrap()
    }
}

// 提供宏,简化Mod初始化函数的注册
#[macro_export]
macro_rules! my_app_mod_init {
    ($init_fn:expr) => {
        #[no_mangle]
        pub extern "C" fn my_app_mod_init(ctx: *mut $crate::MyAppContext) -> i32 {
            let ctx = unsafe { &mut *ctx };
            $init_fn(ctx);
            0
        }
    };
}

Rust Mod开发示例

Rust开发者只需依赖my_app_mod,即可快速编写Mod:

use my_app_mod::{get_version, my_app_mod_init, MyAppContext};

fn init_mod(ctx: &mut MyAppContext) {
    println!("My Rust Mod initialized! App version: {}", get_version(ctx));
    // 此处可调用更多API实现交互逻辑
}

my_app_mod_init!(init_mod);

编译Mod时需指定为动态库:

cargo build --release --lib --crate-type cdylib

步骤4:处理生命周期与安全问题

  • 动态库生命周期:loaded_libs需在程序运行全程保持存活,否则动态库会被卸载,引发悬垂指针问题。
  • 线程安全:若主程序为多线程架构,API函数必须保证线程安全,例如使用Mutex或RwLock保护共享状态。
  • 内存安全:避免在API中传递Rust特有类型(如String、Vec),改用C兼容类型(如*const u8、size_t),并明确内存所有权规则(例如谁负责释放内存)。
  • 版本兼容性:API接口发布后尽量不要修改函数签名,否则旧Mod会失效。可在上下文结构体中添加版本字段,让Mod能判断当前API版本并做兼容处理。

总结

  1. 用#[no_mangle]和extern "C"导出C兼容的API函数,确保跨语言可调用。
  2. 借助libloading动态扫描并加载mods文件夹中的共享库。
  3. 为Rust开发者提供封装好的crate,降低开发复杂度。
  4. 严格遵守C交互的内存、线程安全规则,保证API的稳定性与兼容性。

内容的提问来源于stack exchange,提问作者Alexis

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 09:07:46