如何在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
相关产品推荐
相关产品推荐

