如何创建 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 依赖 enum
s 仅具有定义变体的单个值,或者你 运行 未定义行为的风险。
您拥有的不是枚举(在 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
}
}
我尝试创建 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 依赖 enum
s 仅具有定义变体的单个值,或者你 运行 未定义行为的风险。
您拥有的不是枚举(在 Rust 意义上),您拥有的是位标志,您需要 bitflags
板条箱。
至于栈溢出,那是因为你自己定义了BitOr
实现;该代码是无条件递归的。
作为DK。提及:
- C 的
enum
和 Rust 的enum
有不同的限制。 - 拥有不是枚举变体之一的 Rust 枚举是无效的。
- 你所拥有的叫做"bitflags"
幸运的是,Bindgen 理解位标志。如果您在传递 bitfield-enum
标志或使用 Builder::bitfield_enum
:
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
}
}