2017-10-20 124 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 
    } 
}