所以,我只是好奇。 例如:我有一个板条箱
my_lib
,其中包含以下代码:Cargo.toml
然后我可以使用其他板条箱中的库:
[lib]
crate-type = ["dylib"]
这允许我在程序中使用
[dependencies]
my_lib = { path = "../my_lib" }
,就像任何静态链接的板条箱一样。并且
my_lib
被编译为单独的共享对象。但是 rust 实际上可以延迟加载 dylib 板条箱或者至少允许在所需的共享对象不存在时仍然启动可执行文件吗?通过运行我的意思是该程序将能够检查它是否缺少库并且将在没有某些功能的情况下运行。
您可能认为通过
my_lib
(或 dlopen 包装器)手动加载符号是解决方案。但存在的问题是,在这种情况下,我无法使用板条箱中的类型、结构和特征等。我只能使用加载的符号。
我认为可能存在哪些解决方案:
dlopen
,一些远程的东西,就像Windows中的
RUSTFLAGS
一样。
出于本示例的目的,我将像这样组织板条箱。
/DELAYLOAD
.
├── lazy_app
│ ├── Cargo.toml
│ └── src
│ └── main.rs
├── lazy_impl_a
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
├── lazy_impl_b
│ ├── Cargo.toml
│ └── src
│ └── lib.rs
└── lazy_interface
├── Cargo.toml
└── src
└── lib.rs
是需要在运行时选择性加载 crate 的应用程序。
lazy_app
定义了任何人都可以从这样一个动态加载的箱子中期望的元素:至少一个特征和一些实用程序来获取实现此特征的动态对象。lazy_interface
和 lazy_impl_a
是实现此类接口的动态库的两个示例。
lazy_impl_b
看起来像这样
lazy_interface/src/lib.rs
/*
[dependencies]
libc = "0.2"
*/
use std::ffi::{CStr, CString};
pub trait LazyTrait {
fn show_something(&self) -> String;
fn do_something(&mut self);
}
pub struct LazyInterface {
lib: *mut libc::c_void,
make:
fn(&[&str]) -> Result<Box<dyn LazyTrait>, Box<dyn std::error::Error>>,
}
impl LazyInterface {
pub fn load(path: &str) -> Result<Self, Box<dyn std::error::Error>> {
let lib_name = CString::new(path)?;
let lib = unsafe {
libc::dlopen(
lib_name.as_ptr() as *const libc::c_char,
libc::RTLD_LAZY,
)
};
if lib.is_null() {
let mut msg = format!("cannot load {:?}", path);
Self::error_detail(&mut msg);
Err(msg)?
}
let fnct_name = CString::new("make_lazy")?;
let symb = unsafe {
libc::dlsym(lib, fnct_name.as_ptr() as *const libc::c_char)
};
if symb.is_null() {
let mut msg =
format!("cannot find {:?} in {:?}", fnct_name, path);
Self::error_detail(&mut msg);
Self::close(lib);
Err(msg)?
}
Ok(Self {
lib,
make: unsafe { std::mem::transmute(symb) },
})
}
pub fn make(
&self,
params: &[&str],
) -> Result<Box<dyn LazyTrait>, Box<dyn std::error::Error>> {
(self.make)(params)
}
fn error_detail(msg: &mut String) {
let c_ptr = unsafe { libc::dlerror() };
if !c_ptr.is_null() {
msg.push_str(": ");
msg.push_str(&unsafe { CStr::from_ptr(c_ptr) }.to_string_lossy());
}
}
fn close(lib: *mut libc::c_void) {
unsafe { libc::dlclose(lib) };
}
}
impl Drop for LazyInterface {
fn drop(&mut self) {
Self::close(self.lib);
}
}
应该暴露动态加载的 crate 所期望的所有内容;这个例子只依赖于两个简单的函数。
LazyTrait
提供了样板来获取实现此特征的动态对象。LazyInterface
来保持简单,但其他一些解决方案可能有助于可移植性。libc::dlopen()
板条箱。"lib"
和 lazy_impl_a/src/lib.rs
可以这样定义
lazy_impl_b/src/lib.rs
/*
[lib]
crate-type = ["cdylib"]
[dependencies]
lazy_interface = { path = "../lazy_interface" }
*/
use lazy_interface::LazyTrait;
struct LazyA {
a: i32,
b: String,
}
impl LazyTrait for LazyA {
fn show_something(&self) -> String {
format!("LazyA with {:?} and {:?}", self.a, self.b)
}
fn do_something(&mut self) {
self.b.push_str(&format!("\nchanged {}", self.a));
self.a += 5;
self.b.push_str(&format!(" into {}", self.a));
}
}
#[allow(improper_ctypes_definitions)]
#[no_mangle]
pub extern "C" fn make_lazy(
params: &[&str]
) -> Result<Box<dyn LazyTrait>, Box<dyn std::error::Error>> {
let first = *params.get(0).ok_or("missing first parameter")?;
let second = *params.get(1).ok_or("missing second parameter")?;
Ok(Box::new(LazyA {
a: first.parse()?,
b: second.to_owned(),
}))
}
该特征的实现没有任何特定于动态代码加载的内容。我们只需要使用
/*
[lib]
crate-type = ["cdylib"]
[dependencies]
lazy_interface = { path = "../lazy_interface" }
*/
use lazy_interface::LazyTrait;
struct LazyB {
v: Vec<String>,
}
impl LazyTrait for LazyB {
fn show_something(&self) -> String {
format!("LazyB with {:?}", self.v)
}
fn do_something(&mut self) {
self.v.push(format!("element {}", self.v.len()));
}
}
#[allow(improper_ctypes_definitions)]
#[no_mangle]
pub extern "C" fn make_lazy(
params: &[&str]
) -> Result<Box<dyn LazyTrait>, Box<dyn std::error::Error>> {
Ok(Box::new(LazyB {
v: params.iter().map(|&s| s.to_owned()).collect(),
}))
}
公开一个提供实现此特征的动态对象的函数;该函数
必须与
no_mangle/extern "C"
中预期的原型完全匹配。
请注意,此类板条箱具有 LazyInterface
板条箱类型,并且必须与应用程序一样构建(应用程序不会构建这些板条箱,因为它们不是其依赖项的一部分)。
"cdylib"
可以是这样的
lazy_app/src/main.rs
/*
[dependencies]
lazy_interface = { path = "../lazy_interface" }
*/
use lazy_interface::LazyInterface;
fn main() -> Result<(), Box<dyn std::error::Error>> {
for lib in [
"../lazy_impl_a/target/debug/liblazy_impl_a.so",
"../lazy_impl_b/target/debug/liblazy_impl_b.so",
] {
match LazyInterface::load(lib) {
Ok(itf) => {
let mut lazy = itf.make(&["123", "hello"])?;
lazy.do_something();
println!("~~> {}", lazy.show_something());
}
Err(e) => {
println!("{}", e);
}
}
}
Ok(())
}
/*
~~> LazyA with 128 and "hello\nchanged 123 into 128"
~~> LazyB with ["123", "hello", "element 2"]
*/
的资源,并且仅在可用时才加载实现。
请注意,所有这些都依赖于一堆
lazy_interface
调用。
很容易引入错误。例如,如果实现中公开的
unsafe
函数的原型与 make_lazy
如果
LazyInterface::make
在应用程序中被删除,而它创建的一些资源(直接或间接)仍在使用中,则可能会发生另一件可怕的事情。这个例子只是一个想法;应该对其进行双重检查,以发现任何不健全或可能不健全的情况。