Rust 可以延迟加载动态链接 (.dll/.so/.dylib) 包吗?

问题描述 投票:0回答:1

所以,我只是好奇。 例如:我有一个板条箱

my_lib
,其中包含以下代码:
Cargo.toml

然后我可以使用其他板条箱中的库:

[lib] crate-type = ["dylib"]

这允许我在程序中使用 
[dependencies] my_lib = { path = "../my_lib" }

,就像任何静态链接的板条箱一样。并且

my_lib
被编译为单独的共享对象。
但是 rust 实际上可以延迟加载 dylib 板条箱或者至少允许在所需的共享对象不存在时仍然启动可执行文件吗?通过运行我的意思是该程序将能够检查它是否缺少库并且将在没有某些功能的情况下运行。

您可能认为通过

my_lib

(或 dlopen 包装器)手动加载符号是解决方案。但存在的问题是,在这种情况下,我无法使用板条箱中的类型、结构和特征等。我只能使用加载的符号。

我认为可能存在哪些解决方案:

    能够传递一些
  • dlopen

    ,一些远程的东西,就像Windows中的

    RUSTFLAGS
    一样。
    
    

  • 能够使用其他板条箱中的类型,而无需链接这些板条箱。只需手动为每个函数创建包装器即可。
  • 能够手动创建某种 rust 编译器插件,它将在编译结束时收集所有符号和类型的列表,并基于此生成包装器箱。
  • 一些代码生成板条箱,可生成其他板条箱的包装器。这些包装器将手动从其他板条箱加载符号,同时仍然提供函数和类型。
  • 不要
关心是否能够使用 C-FFI 类型和 ABI 内容。对我来说,只使用使用完全相同版本的编译器编译的 .dll/.so/.dylib-s 是可以接受的,因此 ABI 是相同的。

出于本示例的目的,我将像这样组织板条箱。
rust shared-libraries dlopen
1个回答
0
投票
/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
 在应用程序中被删除,而它创建的一些资源(直接或间接)仍在使用中,则可能会发生另一件可怕的事情。
这个例子只是一个想法;应该对其进行双重检查,以发现任何不健全或可能不健全的情况。

© www.soinside.com 2019 - 2024. All rights reserved.