首页 > 解决方案 > Impl Send 用于 Bindgen 生成的指针类型

问题描述

我正在尝试将 FFI 指针类型发送到另一个线程。它指向的结构是由opensles-sys 中的 bindgen 生成的

这是我的包装器结构:

pub struct AndroidAudioIO {
    sl_output_buffer_queue: NonNull<SLObjectItf>,
}
unsafe impl Send for AndroidAudioIO{}

SLObjectItf类型是一个别名,*const *const SLObjectItf_其定义由 bindgen 生成。它是 FFI 函数指针的集合。

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct SLObjectItf_ {
    pub Realize: ::std::option::Option<
        unsafe extern "C" fn(self_: SLObjectItf, async: SLboolean) -> SLresult,
    >,
    // More of the same pattern, only extern "C" function pointers, no data
}

我尝试添加unsafe impl Send for SLObjectItf_{}和其他变体无济于事。

error[E0277]: `std::ptr::NonNull<*const *const opensles::bindings::SLObjectItf_>` cannot be shared between threads safely
  --> src/lib.rs:12:1
   |
12 | / lazy_static! {
13 | | static ref engine:Option<mynoise::Engine<Box<audio::AndroidAudioIO>>> = None;
14 | | }
   | |_^ `std::ptr::NonNull<*const *const opensles::bindings::SLObjectItf_>` cannot be shared between threads safely
   |
   = help: within `audio::AndroidAudioIO`, the trait `std::marker::Sync` is not implemented for `std::ptr::NonNull<*const *const opensles::bindings::SLObjectItf_>`
   = note: required because it appears within the type `audio::AndroidAudioIO`

我只关心Send而不关心的原因Sync是单个线程(RT音频线程)与这个结构交互,但它是在另一个线程上创建的,因此需要Send指向正确线程的指针。

标签: rustffi

解决方案


下面的代码重现了同样的问题(假设Engine只保留AndroidAudioIO在类型级别,以便它可以在以后生成这样的处理程序;它也可以通过直接组合工作)。

#[macro_use]
extern crate lazy_static;

use std::marker::PhantomData;
use std::ptr::NonNull;

#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct SLObjectItf;

pub struct AndroidAudioIO {
    sl_output_buffer_queue: NonNull<SLObjectItf>,
}
unsafe impl Send for AndroidAudioIO {}

#[derive(Debug)]
pub struct Engine<T>(PhantomData<T>);

lazy_static! {
    static ref engine: Option<Engine<AndroidAudioIO>> = None;
}

游乐场

这里的问题是该Engine实体位于全局静态变量中,这立即使其在所有线程之间共享。这需要Sync,但Engine没有给出一个实现,Sync因为AudioAndroidIO没有实现Sync。实际上,无论引擎是否包含作为属性的音频 I/O 处理程序,或者该信息仅存在于类型级别,甚至PhantomData直接从其参数类型继承这些 trait 实现。引用文档:

impl<T: ?Sized> Send for PhantomData<T>
where
    T: Send,
impl<T: ?Sized> Sync for PhantomData<T>
where
    T: Sync

这很可能Engine是可以拥有的情况Sync(尽管PhantomData选择了这种避免对内部类型假设的安全行为)。要解决这个问题,首先要绝对确保它Engine是线程安全的。然后,为此手动实施Sync

unsafe impl<T> Sync for Engine<T> {}

我尝试添加unsafe impl Send for SLObjectItf_{}和其他变体无济于事。

好吧,无论如何,这通常是一个坏主意™。实现Send和/或Sync应该在绑定的安全、高级抽象之上完成。


推荐阅读