然后来看看 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); } }}
推荐阅读
- 你知道 Python 其实自带了小型数据库吗
- 解密Java连接MySQL的最佳实践:选择适合你的方式
- QQ如何用指纹解锁
- 怎么成为淘宝买菜的团长 如何申请成为淘宝买菜团长
- 如何制作三角形笔刷ps,ps该如何才可以画出三角形
- 在拼多多上如何修改评价,拼多多怎么修改评价等级
- cdr中应该如何画波浪线
- cdr2020如何合并打印,cdr该如何才可以进行打印
- 水泥路面起砂如何处理 水泥路面起砂如何处理视频
- 装修公司如何选 装修公司如何选好门店
