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

如何在Rust动态链接.so库中访问自定义类型与方法?

Rust动态共享库中自定义类型与方法的访问实现

问题背景

  • 目标:实现可被多个二进制文件共享的Rust库,同时尽可能减小二进制体积
  • 当前进展:已通过crate-type = "dylib"编译动态库,并用libloading调用简单函数,现需扩展自定义结构体User及其方法的跨库调用

核心思路

Rust的结构体与方法默认不具备跨动态库的可访问性,需通过以下方式处理:

  • 用#[repr(C)]标记结构体,固定内存布局,保证跨库的内存结构一致性
  • 将结构体的构造、属性访问、内存销毁逻辑封装为#[no_mangle]标记的外部函数,暴露给动态链接
  • 在二进制端定义与库端完全匹配的镜像结构体,通过libloading加载并调用这些外部函数

1. 动态库端实现(lib.rs)

use std::ffi::CString;
use std::ptr;

// 用#[repr(C)]固定结构体内存布局,确保与二进制端镜像结构一致
#[repr(C)]
pub struct User {
    username: String,
    email: String,
}

impl User {
    fn new(username: String, email: String) -> Self {
        User { username, email }
    }

    fn get_username(&self) -> &str {
        &self.username
    }

    fn get_email(&self) -> &str {
        &self.email
    }
}

// 构造User实例的外部函数,返回指针
#[no_mangle]
pub extern "Rust" fn user_new(username: *const u8, email: *const u8) -> *mut User {
    // 将C风格字符串转为Rust String
    let username = unsafe { CString::from_raw(username as *mut u8) }.into_string().unwrap();
    let email = unsafe { CString::from_raw(email as *mut u8) }.into_string().unwrap();
    
    Box::into_raw(Box::new(User::new(username, email)))
}

// 获取用户名的外部函数,返回C风格字符串
#[no_mangle]
pub extern "Rust" fn user_get_username(user: *const User) -> *const u8 {
    let user = unsafe { &*user };
    CString::new(user.get_username()).unwrap().into_raw()
}

// 获取邮箱的外部函数,返回C风格字符串
#[no_mangle]
pub extern "Rust" fn user_get_email(user: *const User) -> *const u8 {
    let user = unsafe { &*user };
    CString::new(user.get_email()).unwrap().into_raw()
}

// 销毁User实例的外部函数,释放内存
#[no_mangle]
pub extern "Rust" fn user_destroy(user: *mut User) {
    if !user.is_null() {
        unsafe { Box::from_raw(user); }
    }
}

#[no_mangle]
pub fn minicall() -> u8 {
    3u8
}

2. 二进制端实现(main.rs)

use libloading::{Library, Symbol};
use std::ffi::CString;

// 定义与库端完全一致的镜像结构体,保证内存布局匹配
#[repr(C)]
struct User {
    _username: String, // 不直接访问字段,通过库提供的方法操作
    _email: String,
}

// 定义函数类型别名,简化符号加载代码
type UserNewFn = unsafe extern "Rust" fn(*const u8, *const u8) -> *mut User;
type UserGetUsernameFn = unsafe extern "Rust" fn(*const User) -> *const u8;
type UserGetEmailFn = unsafe extern "Rust" fn(*const User) -> *const u8;
type UserDestroyFn = unsafe extern "Rust" fn(*mut User);

fn main() {
    unsafe {
        let lib = Library::new("liblibtest.so").unwrap();
        
        // 调用原有minicall函数
        let minicall: Symbol<unsafe extern fn() -> u8> = lib.get(b"minicall").unwrap();
        let r = minicall();
        println!("r = {r}");

        // 加载User相关函数
        let user_new: Symbol<UserNewFn> = lib.get(b"user_new").unwrap();
        let user_get_username: Symbol<UserGetUsernameFn> = lib.get(b"user_get_username").unwrap();
        let user_get_email: Symbol<UserGetEmailFn> = lib.get(b"user_get_email").unwrap();
        let user_destroy: Symbol<UserDestroyFn> = lib.get(b"user_destroy").unwrap();

        // 准备参数:转为C风格字符串
        let username = CString::new("alice").unwrap();
        let email = CString::new("alice@example.com").unwrap();

        // 创建User实例
        let user_ptr = user_new(username.as_ptr(), email.as_ptr());
        assert!(!user_ptr.is_null());

        // 获取并打印用户名、邮箱
        let username_ptr = user_get_username(user_ptr);
        let username = CString::from_raw(username_ptr as *mut u8).into_string().unwrap();
        println!("Username: {}", username);

        let email_ptr = user_get_email(user_ptr);
        let email = CString::from_raw(email_ptr as *mut u8).into_string().unwrap();
        println!("Email: {}", email);

        // 销毁User实例,避免内存泄漏
        user_destroy(user_ptr);
    }
}

关键注意事项

  • #[repr(C)]是必须的:Rust默认结构体布局未定义,跨库传递必须固定内存结构,否则会触发未定义行为
  • 内存管理一致性:跨动态库传递String等带内存的类型时,通过C风格字符串中转,或统一使用全局分配器(如设置RUSTFLAGS="-C target-feature=+crt-static"静态链接分配器)
  • #[no_mangle]不可省略:防止Rust编译器混淆函数名,确保动态链接能正确找到函数
  • 使用extern "Rust" ABI:全程基于Rust自身的调用约定,无需依赖C语言

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 17:07:15