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

在Rust中向动态库暴露主程序公共API的技术方案问询

可行方案与实现思路

1. 抽离公共API Crate(最优解)

这是Rust生态中处理动态库交互最成熟的方案,完全解决结构体共享、函数调用的问题,且类型安全:

  • 新建独立Rust crate(比如game_api),仅存放结构体定义、Trait、函数签名、常量,不包含具体实现:
    // game_api/src/lib.rs
    #[derive(Debug, Clone)]
    pub struct WorldPosition {
        pub x: f32,
        pub y: f32,
    }
    
    // 主程序需实现的核心API trait
    pub trait GameApi {
        fn spawn_entity(&self, pos: WorldPosition, entity_type: &str);
        fn show_gui_message(&self, msg: &str);
        // 其他需要暴露的功能
    }
    
    // 模组初始化函数签名,主程序会调用该函数传递API实例
    pub type ModInitFn = extern "C" fn(api: &dyn GameApi) -> Result<(), String>;
    
  • 主程序依赖game_api crate,实现GameApi trait,启动时加载模组并传递API实例:
    // 主程序中的GameApi实现
    struct GameCore;
    impl GameApi for GameCore {
        fn spawn_entity(&self, pos: WorldPosition, entity_type: &str) {
            // 主程序实体生成逻辑
        }
        fn show_gui_message(&self, msg: &str) {
            // 主程序GUI显示逻辑
        }
    }
    
    // 加载模组的代码
    fn load_mod(path: &str) {
        let lib = libloading::Library::new(path).unwrap();
        let init_fn: libloading::Symbol<ModInitFn> = lib.get(b"mod_init").unwrap();
        // 传递主程序API实例给模组
        init_fn(&GameCore).unwrap();
    }
    
  • 模组同样依赖game_api crate,实现初始化函数并导出:
    // 模组代码
    #[no_mangle]
    pub extern "C" fn mod_init(api: &dyn GameApi) -> Result<(), String> {
        // 调用主程序API
        api.show_gui_message("模组加载成功!");
        api.spawn_entity(WorldPosition { x: 100.0, y: 200.0 }, "custom_monster");
        Ok(())
    }
    

该方案完全类型安全,结构体与API定义仅需编写一次,主程序与模组各自编译,无代码重复。

2. 主程序导出全局符号(适合简单场景)

若不想抽离单独crate,可让主程序直接导出全局函数(借助#[no_mangle]和extern "C"),模组通过libloading加载这些符号。但结构体定义需在主程序与模组中保持一致,建议通过公共头文件或共享crate维护:

  • 主程序导出函数:
    use std::os::raw::c_char;
    use std::ffi::{CStr, CString};
    
    #[no_mangle]
    pub extern "C" fn game_spawn_entity(x: f32, y: f32, entity_type: *const c_char) {
        let entity_type = unsafe { CStr::from_ptr(entity_type).to_str().unwrap() };
        // 主程序逻辑
    }
    
    #[no_mangle]
    pub extern "C" fn game_show_gui_message(msg: *const c_char) {
        let msg = unsafe { CStr::from_ptr(msg).to_str().unwrap() };
        // 主程序GUI逻辑
    }
    
  • 模组加载并调用:
    use std::os::raw::c_char;
    use std::ffi::CString;
    
    fn call_main_api() {
        let lib = libloading::Library::new("../target/release/game.exe").unwrap();
        let spawn_entity: libloading::Symbol<extern "C" fn(f32, f32, *const c_char)> = lib.get(b"game_spawn_entity").unwrap();
        let msg = CString::new("模组调用主程序API").unwrap();
        spawn_entity(100.0, 200.0, msg.as_ptr());
    }
    

这种方式需手动处理C字符串转换,结构体传递时必须保证内存布局一致,容易出错,适合功能较少的场景。

3. 回调上下文封装(优化回调思路)

若不想用公共crate,可将主程序所有API函数指针封装成上下文结构体,主程序将该结构体传递给模组初始化函数,模组通过结构体指针调用主程序功能:

  • 主程序定义上下文结构体(需用#[repr(C)]保证内存布局稳定):
    use std::os::raw::c_char;
    use std::ffi::{CStr, CString};
    
    #[repr(C)]
    pub struct GameApiContext {
        pub spawn_entity: extern "C" fn(x: f32, y: f32, entity_type: *const c_char),
        pub show_gui_message: extern "C" fn(msg: *const c_char),
    }
    
    // 主程序实现API函数
    #[no_mangle]
    extern "C" fn spawn_entity_impl(x: f32, y: f32, entity_type: *const c_char) {
        let entity_type = unsafe { CStr::from_ptr(entity_type).to_str().unwrap() };
        // 逻辑实现
    }
    
    #[no_mangle]
    extern "C" fn show_gui_message_impl(msg: *const c_char) {
        let msg = unsafe { CStr::from_ptr(msg).to_str().unwrap() };
        // 逻辑实现
    }
    
    // 加载模组时传递上下文
    fn load_mod(path: &str) {
        let api_ctx = GameApiContext {
            spawn_entity: spawn_entity_impl,
            show_gui_message: show_gui_message_impl,
        };
        let lib = libloading::Library::new(path).unwrap();
        let init_fn: libloading::Symbol<extern "C" fn(api_ctx: *const GameApiContext)> = lib.get(b"mod_init").unwrap();
        unsafe { init_fn(&api_ctx) };
    }
    
  • 模组中定义完全一致的GameApiContext结构体(必须包含#[repr(C)]),然后调用:
    use std::os::raw::c_char;
    use std::ffi::CString;
    
    #[repr(C)]
    pub struct GameApiContext {
        pub spawn_entity: extern "C" fn(x: f32, y: f32, entity_type: *const c_char),
        pub show_gui_message: extern "C" fn(msg: *const c_char),
    }
    
    #[no_mangle]
    pub extern "C" fn mod_init(api_ctx: *const GameApiContext) {
        let api_ctx = unsafe { &*api_ctx };
        let msg = CString::new("通过上下文调用API").unwrap();
        (api_ctx.show_gui_message)(msg.as_ptr());
    }
    

该方案无需公共crate,但需手动维护结构体一致性,易因定义不一致引发内存错误,适合快速原型开发。

方案对比

方案类型安全维护成本复杂度适用场景
公共API Crate✅低中中大型游戏模组系统
主程序导出全局符号❌中低小型游戏/简单模组功能
回调上下文封装❌高中快速原型/特殊场景

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 15:58:12