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

然后来看看 Python 如何调用:
from ctypes import *py_lib = CDLL("../py_lib/target/debug/libpy_lib.dylib")s = "hello 古明地觉".encode("utf-8")# Rust 返回的是原始指针,这里必须要拿到它保存的地址# 所以指定返回值为 c_void_p,如果指定为 c_char_p,# 那么会直接转成 bytes 对象,这样地址就拿不到了py_lib.to_uppercase.restype = c_void_p# 拿到地址 , 此时的 ptr 是一个普通的整数,但它和指针保存的地址是一样的ptr = py_lib.to_uppercase(c_char_p(s))# 将 ptr 转成 c_char_p,获取 value 属性 , 即可得到具体的 bytes 对象print(cast(ptr, c_char_p).value.decode("utf-8"))"""HELLO 古明地觉"""# 内容我们拿到了,但堆区的字符串还没有释放,所以调用 free_cstringpy_lib.free_cstring(c_void_p(ptr))通过 CString 的 into_raw,可以基于 CString 创建原始指针 *mut , 然后 Python 将指针指向的堆区数据拷贝一份 , 得到 bytes 对象 。
但这个 CString 依旧驻留在堆区,所以 Python 不能将返回值指定为 c_char_p,因为它会直接创建 bytes 对象,这样就拿不到指针了 。因此将返回值指定为 c_void_p,调用函数会得到一串整数,这个整数就是指针保存的地址 。
我们使用 cast 函数可以将地址转成 c_char_p,获取它的 value 属性拿到具体的字节串 。再通过 c_void_p 创建原始指针交给 Rust,调用 CString 的 from_raw,可以基于 *mut 创建 CString,从而将所有权夺回来,然后离开作用域时释放堆内存 。
 给函数传递指针如果扩展函数里面接收的是指针,那么 Python 要怎么传递呢?
#[no_mangle]pub extern "C" fn add(a: *mut i32, b: *mut i32) -> i32 {    // 定义为 *mut,那么可以修改指针指向的值,定义为 *const,则不能修改    if a.is_null() || b.is_null() {        0    } else {        let res = unsafe {            *a + *b        };        unsafe {            // 这里将 *a 和 *b 给改掉            *a = 666;            *b = 777;        }        res    }}定义了一个 add 函数 , 接收两个 i32 指针,返回解引用后相加的结果 。但是在返回之前,我们将 *a 和 *b 的值也修改了 。
from ctypes import *py_lib = CDLL("../py_lib/target/debug/libpy_lib.dylib")a = c_int(22)b = c_int(33)# 计算print(py_lib.add(pointer(a), pointer(b)))  # 55# 我们看到 a 和 b 也被修改了print(a, a.value)  # c_int(666) 666print(b, b.value)  # c_int(777) 777非常简单 , 那么问题来了,能不能返回一个指针呢?答案是当然可以,只不过存在一些注意事项 。
由于 Rust 本身的内存安全原则,直接从函数返回一个指向本地局部变量的指针是不安全的 。因为该变量的作用域仅限于函数本身,一旦函数返回,该变量的内存就会被回收,从而出现悬空指针 。
为了避免这种情况出现,我们应该在堆上分配内存 , 但这又出现了之前 CString 的问题 。Python 在拿到值之后 , 堆内存依旧驻留在堆区 。因此 Rust 如果想返回指针,那么同时还要定义一个释放函数 。
#[no_mangle]pub extern "C" fn add(a: *const i32, b: *const i32) -> *mut i32 {    // 返回值的类型是 *mut i32,所以 res 不能直接返回,因此它是 i32    let res = unsafe {*a + *b};    // 创建智能指针(将 res 装箱),然后返回原始指针    Box::into_raw(Box::new(res))}#[no_mangle]pub extern "C" fn free_i32(ptr: *mut i32) {    if !ptr.is_null() {        // 转成 Box<i32>,同时拿到所有权,在离开作用域时释放堆内存        unsafe { let _ = Box::from_raw(ptr); }    }}


推荐阅读