解密 Python 如何调用 Rust 编译生成的动态链接库( 七 )

这时就尴尬了 , 此时的字符串是 Rust 里面创建的,转成原始指针之后,Rust 将不再管理相应的堆内存(因为 into_raw 将所有权转移走了),此时就需要手动堆内存了 。
from ctypes import *py_lib = CDLL("../py_lib/target/debug/libpy_lib.dylib")class Girl(Structure):    _fields_ = [        ("name", c_char_p),        ("age", c_uint8),    ]# 指定 create_struct 的返回值类型为 Girlpy_lib.create_struct.restype = Girlgirl = py_lib.create_struct()print(girl.name.decode("utf-8"))  # S 老师print(girl.age)  # 18# 直接传递 girl 即可,会释放 girl 里面的字段在堆区的内存py_lib.free(girl)此时就不会出现内存泄露了,在 free 的时候,将变量 girl 传进去,释放掉内部字段占用的堆内存 。
当然,Rust 也可以返回结构体指针,通过 Box<T> 实现 。
#[no_mangle]pub extern "C" fn create_struct() -> *mut Girl {    let name = CString::new("S 老师").unwrap().into_raw();    let age = 18;    Box::into_raw(Box::new(Girl { name, age }))}注意:之前是 name 字段在堆上 , 但结构体实例在栈上 , 现在 name 字段和结构体实例都在堆上 。
然后 Python 调用也很简单 , 关键是释放的问题 。
from ctypes import *py_lib = CDLL("../py_lib/target/debug/libpy_lib.dylib")class Girl(Structure):    _fields_ = [        ("name", c_char_p),        ("age", c_uint8),    ]# 此时返回值类型就变成了 c_void_p# 当返回指针时 , 建议将返回值设置为 c_void_ppy_lib.create_struct.restype = c_void_p# 拿到指针(一串整数)ptr = py_lib.create_struct()# 将指针转成指定的类型,而类型显然是 POINTER(Girl)# 调用 POINTER(T) 的 contents 方法,拿到相应的结构体实例girl = cast(ptr, POINTER(Girl)).contents# 访问具体内容print(girl.name.decode("utf-8"))  # S 老师print(girl.age)  # 18# 释放堆内存,这里的释放分为两步,并且顺序不能错# 先 free(girl),释放掉内部字段(name)占用的堆内存# 然后 free(c_void_p(ptr)),释放掉结构体实例 girl 占用的堆内存py_lib.free(girl)py_lib.free(c_void_p(ptr))不难理解,只是在释放结构体实例的时候需要多留意,如果内部有字段占用堆内存 , 那么需要先将这些字段释放掉 。而释放的方式是将结构体实例作为参数传给 free 函数,然后再传入 c_void_p 释放结构体实例 。
 回调函数最后看一下 Python 如何传递函数给 Rust,因为 Python 和 Rust 之间使用的是 C ABI,所以函数必须遵循 C 的标准 。
// calc 接收三个参数,前两个参数是 *const i32// 最后一个参数是函数,它接收两个 *const i32,返回一个 i32#[no_mangle]pub extern "C" fn calc(    a: *const i32, b: *const i32,    op: extern "C" fn(*const i32, *const i32) -> i32) -> i32{    op(a, b)}然后看看 Python 如何传递回调函数 。
from ctypes import *py_lib = CDLL("../py_lib/target/debug/libpy_lib.dylib")# 基于 Python 函数创建 C 函数,通过 @CFUNCTYPE() 进行装饰# CFUNCTYPE 第一个参数是返回值类型,剩余的参数是参数类型@CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))def add(a, b):  # a、b 为 int *,通过 .contents.value 拿到具体的值    return a.contents.value + b.contents.value@CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))def sub(a, b):    return a.contents.value - b.contents.value@CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))def mul(a, b):    return a.contents.value * b.contents.value@CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))def div(a, b):    return a.contents.value // b.contents.valuea = pointer(c_int(10))b = pointer(c_int(2))print(py_lib.calc(a, b, add))  # 12print(py_lib.calc(a, b, sub))  # 8print(py_lib.calc(a, b, mul))  # 20print(py_lib.calc(a, b, div))  # 5


推荐阅读