ctypes 已被包含在 Python 标准库中,成为 Python 内建的用于调用动态链接库函数的功能模块。ctypes的主要问题是,我们必须使用其特定的 API 完全重复 C ABI 的声明。cffi 则是则通过解析实际的 C ABI 声明,自动推断所需的数据类型和函数签名,以避免重写声明。ctypes和cffi都使用了libffi,通过它实现 Python 动态调用其他语言的库。在本文中的示例,我们采用 cffi 库。pip :pip install cffi
cffi 的方式有 ABI 模式 和 API 模式 ,前者以二进制级别访问库,而后者使用 C 编译器访问库,所以在运行时,API 模式比 ABI 模式更快。我们的示例中使用 ABI 模式,因为它不需要 C 编译器。cffi 中,我们可以使用 ffi.cdef(source) 解析给定的 C ABI。在其中注册所有函数,类型,常量和全局变量,这些类型可以在其它函数中立即使用。然后通过 ffi.dlopen(libpath) 使用 ABI 模式加载外部库并返回一个该库的对象,这样我们就可以使用库对象来调用先前由 ffi.cdef() 声明的函数,读取常量以及读取或写入全局变量。这种方式的大致代码框架如下:# 导入 FFI 类
from cffi import FFI
ffi = FFI()
# 声明数据类型和函数原型
ffi.cdef("""
""")
# 以 ABI 模式加载外部库并返回库对象
lib = ffi.dlopen("")
example_04
├── Cargo.toml
├── ffi
│ ├── Cargo.toml
│ ├── cbindgen.toml
│ ├── example_04_header.h
│ ├── src
│ │ └── lib.rs
├── .gitignore
├── python
│ └── main.py
├── README.md
├── src
│ └── lib.rs
ffi 目录存放 Rust 代码库暴露给外部的 C ABI 代码;example_04_header.h:cbindgen --config cbindgen.toml --output example_04_header.h
python 目录存放在 Python 调用 Rust 代码库的 Python 代码;src 目录存放 Rust 库的代码,lib.rs 中包含了我们设计并实现的几个示例函数:count_char,计算给定字符串的长度;sum_of_even,计算给定整数数组中所有偶数之和;handle_tuple,处理元组包含整数和布尔类型两个元素,将整数加1和布尔取反后返回;u8 组成的 UTF-8 编码的字节序列,字符串内部允许 NUL 字节;但在 C 中,字符串只是指向一个 char 的指针,用一个 NUL 字节作为终止。std::ffi::CStr,它表示一个 NUL 字节作为终止的字节数组,可以通过 UTF-8 验证转换成 Rust 中的 &str。#[no_mangle]
pub extern "C" fn count_char(s: *const c_char) -> c_uint {
let c_str = unsafe {
assert!(!s.is_null());
CStr::from_ptr(s)
};
let r_str = c_str.to_str().unwrap();
r_str.chars().count() as u32
}
char 类型对应于 Python 中的单字符字符串,在 Python 中字符串必须编码为 UTF-8,才能通过 FFI 边界。# coding: utf-8
print 'count_char("hello") from Rust: ', lib.count_char("hello")
print 'count_char("你好") from Rust: ', lib.count_char(u"你好".encode('utf-8'))
count_char("hello") from Rust: 5
count_char("你好") from Rust: 2
from_raw_parts 将指针和长度,转换为一个 Rust 中的切片。#[no_mangle]
pub extern "C" fn sum_of_even(ptr: *const c_int, len: size_t) -> c_int {
let slice = unsafe {
assert!(!ptr.is_null());
slice::from_raw_parts(ptr, len as usize)
};
let sum = slice.iter()
.filter(|&&num| num % 2 == 0)
.fold(0, |sum, &num| sum + num);
sum as c_int
}
cdata 类型。可以通过 ffi.new(cdecl,init=None) ,根据指定的 C 类型分配实例,并返回指向它的指针。array = ffi.new("int[]", [1, 4, 9, 16, 25])
print 'sum_of_even from Rust: ', lib.sum_of_even(array, len(array))
sum_of_even from Rust: 20
#[repr(C)]
pub struct c_tuple {
integer: c_uint,
boolean: bool,
}
#[no_mangle]
pub extern "C" fn handle_tuple(tup: c_tuple) -> c_tuple {
let (integer, boolean) = tup.into();
(integer + 1, !boolean).into()
}
cdata 类型。py_cdata = ffi.new('c_tuple *')
py_cdata.integer = 100
py_cdata.boolean = True
print('cdata = {0}, {1}'.format(py_cdata.integer, py_cdata.boolean))
new_py_cdata = lib.handle_tuple(py_cdata[0])
print('change cdata = {0}, {1}'.format(new_py_cdata.integer, new_py_cdata.boolean))
cdata = 100, True
change cdata = 101, False
cbindgen 工具生成 C API 的头文件或手动添加 C API 函数定义;原文作者:Rust语言中文社区;如有侵权,请联系管理员删除。