2017-10-20 90 views
0

我试图创建FFI绑定libmodbus,写在C. 这里我偶然发现此function如何创建预期OR-ed字节的C函数的FFI绑定?

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, 
} 

and

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 

回答

2

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

铁锈不。 Rust 依赖enum只有一个已定义变体的单个值,否则您将面临未定义行为的风险。

你有什么不是一个枚举(在Rust的意义上),你有位标志,你需要bitflags箱子。

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

3

由于DK。提到:

  • C的enum和Rust的enum有不同的限制。
  • 有一个Rust enum不是有效的,它不是enum变体之一。
  • 你叫什么? “bitflags”

幸运的是,BindGen对理解bitflags。如果你生成你的头在经过bitfield-enum标志或使用Builder::bitfield_enum

bindgen --bitfield-enum modbus_error_recovery_mode fake-modbus.h 

的BindGen将产生Bit*性状关于C枚举值常量,一个NEWTYPE包装,并实现:

// 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; 
} 

如何向公众公开bindgen生成的常量

当然,为非防锈代码创建一个惯用的Rust API是最困难的部分。我可能会尝试这样的事情:

#[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 
    } 
}