如何创建 FFI 绑定到需要 OR 字节的 C 函数?

How do I create FFI bindings to C functions expecting OR-ed bytes?

我尝试创建 FFI 绑定到 libmodbus,用 C 编写。 在这里我偶然发现了这个 function

modbus_set_error_recovery(ctx,
                          MODBUS_ERROR_RECOVERY_LINK |
                          MODBUS_ERROR_RECOVERY_PROTOCOL);

第二个参数定义为

typedef enum
{
    MODBUS_ERROR_RECOVERY_NONE          = 0,
    MODBUS_ERROR_RECOVERY_LINK          = (1<<1),
    MODBUS_ERROR_RECOVERY_PROTOCOL      = (1<<2)
} modbus_error_recovery_mode;

我的 bindgen 生成的绑定是这些:

#[repr(u32)]
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum modbus_error_recovery_mode {
    MODBUS_ERROR_RECOVERY_NONE = 0,
    MODBUS_ERROR_RECOVERY_LINK = 2,
    MODBUS_ERROR_RECOVERY_PROTOCOL = 4,
}

extern "C" {
    pub fn modbus_set_error_recovery(ctx: *mut modbus_t,
                                     error_recovery:
                                         modbus_error_recovery_mode)
     -> ::std::os::raw::c_int;
}

到目前为止,我的安全界面如下所示:

pub fn set_error_recovery(&mut self, error_recovery_mode: ErrorRecoveryMode) -> Result<()> {

    unsafe {
        match ffi::modbus_set_error_recovery(self.ctx, error_recovery_mode.to_c()) {
            -1 => bail!(Error::last_os_error()),
            0 => Ok(()),
            _ => panic!("libmodbus API incompatible response"),
        }
    }
}

use std::ops::BitOr;

#[derive(Clone, Copy, PartialEq, Eq)]
pub enum ErrorRecoveryMode {
    NONE = 0,
    Link = 2,
    Protocol = 4,
}

impl ErrorRecoveryMode {
    pub fn to_c(self) -> ffi::modbus_error_recovery_mode {
        match self {
            NONE => ffi::modbus_error_recovery_mode::MODBUS_ERROR_RECOVERY_NONE,
            Link => ffi::modbus_error_recovery_mode::MODBUS_ERROR_RECOVERY_LINK,
            Protocol => ffi::modbus_error_recovery_mode::MODBUS_ERROR_RECOVERY_PROTOCOL,
        }
    }
}

impl BitOr for ErrorRecoveryMode {
    type Output = Self;
    fn bitor(self, rhs: ErrorRecoveryMode) -> ErrorRecoveryMode {
        self | rhs
    }
}

如果我这样调用 set_error_recovery,就会触发堆栈溢出

assert!(modbus.set_error_recovery(ErrorRecoveryMode::Link | ErrorRecoveryMode::Protocol).is_ok())

错误是

thread 'set_error_recovery' has overflowed its stack
fatal runtime error: stack overflow

问题是 C 的 enum 和 Rust 的 enum 非常 不同。特别是,C 允许 enum 绝对具有任何值,无论该值是否对应于变体。

Rust 没有。 Rust 依赖 enums 仅具有定义变体的单个值,或者你 运行 未定义行为的风险。

您拥有的不是枚举(在 Rust 意义上),您拥有的是位标志,您需要 bitflags 板条箱。

至于栈溢出,那是因为你自己定义了BitOr实现;该代码是无条件递归的。

作为DK。提及:

  • C 的 enum 和 Rust 的 enum 有不同的限制。
  • 拥有不是枚举变体之一的 Rust 枚举是无效的。
  • 你所拥有的叫做"bitflags"

幸运的是,Bindgen 理解位标志。如果您在传递 bitfield-enum 标志或使用 Builder::bitfield_enum:

时生成 headers
bindgen --bitfield-enum modbus_error_recovery_mode fake-modbus.h

Bindgen 将为每个 C 枚举值、新类型包装器和 Bit* 特征的实现生成常量:

// Many implementation details removed 

pub struct modbus_error_recovery_mode(pub ::std::os::raw::c_uint);

pub const modbus_error_recovery_mode_MODBUS_ERROR_RECOVERY_NONE: modbus_error_recovery_mode =
    modbus_error_recovery_mode(0);    
pub const modbus_error_recovery_mode_MODBUS_ERROR_RECOVERY_LINK: modbus_error_recovery_mode =
    modbus_error_recovery_mode(2);
pub const modbus_error_recovery_mode_MODBUS_ERROR_RECOVERY_PROTOCOL: modbus_error_recovery_mode =
    modbus_error_recovery_mode(4);

impl ::std::ops::BitOr<modbus_error_recovery_mode> for modbus_error_recovery_mode {}
impl ::std::ops::BitOrAssign for modbus_error_recovery_mode {}
impl ::std::ops::BitAnd<modbus_error_recovery_mode> for modbus_error_recovery_mode {} 
impl ::std::ops::BitAndAssign for modbus_error_recovery_mode {}

extern "C" {
    pub fn modbus_set_error_recovery(
        ctx: *mut modbus_t,
        error_recovery: modbus_error_recovery_mode,
    ) -> ::std::os::raw::c_int;
}

How do I expose the bindgen generated constants to the public

当然,创建惯用的 Rust API 到 non-Rust 代码是困难的部分。我可能会尝试这样的事情:

#[derive(Debug)]
struct Modbus(*mut raw::modbus_t);

#[derive(Debug)]
struct Error;

#[derive(Debug, Copy, Clone)]
enum ErrorRecovery {
    Link,
    Protocol,
}

impl ErrorRecovery {
    fn as_raw(&self) -> raw::modbus_error_recovery_mode {
        use ErrorRecovery::*;

        match *self {
            Link => raw::modbus_error_recovery_mode_MODBUS_ERROR_RECOVERY_LINK,
            Protocol => raw::modbus_error_recovery_mode_MODBUS_ERROR_RECOVERY_PROTOCOL,
        }
    }
}

impl Modbus {
    fn set_error_recovery(&mut self, flags: Option<&[ErrorRecovery]>) -> Result<(), Error> {
        let flag = flags.unwrap_or(&[]).iter().fold(
            raw::modbus_error_recovery_mode_MODBUS_ERROR_RECOVERY_NONE,
            |acc, v| acc | v.as_raw(),
        );

        let res = unsafe { raw::modbus_set_error_recovery(self.0, flag) };
        Ok(()) // real error checking
    }
}