gpt4 book ai didi

python - 将元组的 Rust 向量转换为 C 兼容结构

转载 作者:IT老高 更新时间:2023-10-28 21:12:44 24 4
gpt4 key购买 nike

关注 these answers ,我目前定义了一个 Rust 1.0 函数如下,以便可以使用 ctypes 从 Python 调用:

use std::vec;

extern crate libc;
use libc::{c_int, c_float, size_t};
use std::slice;

#[no_mangle]
pub extern fn convert_vec(input_lon: *const c_float,
lon_size: size_t,
input_lat: *const c_float,
lat_size: size_t) -> Vec<(i32, i32)> {
let input_lon = unsafe {
slice::from_raw_parts(input_lon, lon_size as usize)
};
let input_lat = unsafe {
slice::from_raw_parts(input_lat, lat_size as usize)
};

let combined: Vec<(i32, i32)> = input_lon
.iter()
.zip(input_lat.iter())
.map(|each| convert(*each.0, *each.1))
.collect();
return combined
}

我正在像这样设置 Python 部分:

from ctypes import *

class Int32_2(Structure):
_fields_ = [("array", c_int32 * 2)]

rust_bng_vec = lib.convert_vec_py
rust_bng_vec.argtypes = [POINTER(c_float), c_size_t,
POINTER(c_float), c_size_t]
rust_bng_vec.restype = POINTER(Int32_2)

这似乎没问题,但我是:

  • 不知道如何转换 combined (a Vec<(i32, i32)>) 转换为与 C 兼容的结构,因此可以将其返回到我的 Python 脚本中。
  • 不确定我是否应该返回引用 (return &combined ?),如果我这样做了,我将如何使用适当的生命周期说明符来注释函数

最佳答案

最需要注意的是,在 C 中没有元组这样的东西。C 是库互操作性的通用语言,您将被要求限制自己使用这种语言的能力。如果你在 Rust 和另一种高级语言之间交谈,这并不重要;你必须说C。

C 中可能没有元组,但有 struct。一个二元素元组只是一个有两个成员的结构!

让我们从我们要编写的 C 代码开始:

#include <stdio.h>
#include <stdint.h>

typedef struct {
uint32_t a;
uint32_t b;
} tuple_t;

typedef struct {
void *data;
size_t len;
} array_t;

extern array_t convert_vec(array_t lat, array_t lon);

int main() {
uint32_t lats[3] = {0, 1, 2};
uint32_t lons[3] = {9, 8, 7};

array_t lat = { .data = lats, .len = 3 };
array_t lon = { .data = lons, .len = 3 };

array_t fixed = convert_vec(lat, lon);
tuple_t *real = fixed.data;

for (int i = 0; i < fixed.len; i++) {
printf("%d, %d\n", real[i].a, real[i].b);
}

return 0;
}

我们已经定义了两个 struct——一个代表我们的元组,另一个代表一个数组,因为我们将来回传递它们。

我们将通过在 Rust 中定义 exact same 结构来跟进这一点,并将它们定义为具有 exact same 成员(类型、排序、名称)。重要的是,我们使用 #[repr(C)] 让 Rust 编译器知道不要对数据进行重新排序。

extern crate libc;

use std::slice;
use std::mem;

#[repr(C)]
pub struct Tuple {
a: libc::uint32_t,
b: libc::uint32_t,
}

#[repr(C)]
pub struct Array {
data: *const libc::c_void,
len: libc::size_t,
}

impl Array {
unsafe fn as_u32_slice(&self) -> &[u32] {
assert!(!self.data.is_null());
slice::from_raw_parts(self.data as *const u32, self.len as usize)
}

fn from_vec<T>(mut vec: Vec<T>) -> Array {
// Important to make length and capacity match
// A better solution is to track both length and capacity
vec.shrink_to_fit();

let array = Array { data: vec.as_ptr() as *const libc::c_void, len: vec.len() as libc::size_t };

// Whee! Leak the memory, and now the raw pointer (and
// eventually C) is the owner.
mem::forget(vec);

array
}
}

#[no_mangle]
pub extern fn convert_vec(lon: Array, lat: Array) -> Array {
let lon = unsafe { lon.as_u32_slice() };
let lat = unsafe { lat.as_u32_slice() };

let vec =
lat.iter().zip(lon.iter())
.map(|(&lat, &lon)| Tuple { a: lat, b: lon })
.collect();

Array::from_vec(vec)
}

我们必须从不接受或返回非repr(C) 类型跨越FFI 边界,所以我们要跨越我们的Array。请注意,有大量 unsafe 代码,因为我们必须将指向数据的未知指针 (c_void) 转换为特定类型。这就是 C 世界中泛型的代价。

现在让我们把目光转向 Python。基本上,我们只需要模仿 C 代码所做的:

import ctypes

class FFITuple(ctypes.Structure):
_fields_ = [("a", ctypes.c_uint32),
("b", ctypes.c_uint32)]

class FFIArray(ctypes.Structure):
_fields_ = [("data", ctypes.c_void_p),
("len", ctypes.c_size_t)]

# Allow implicit conversions from a sequence of 32-bit unsigned
# integers.
@classmethod
def from_param(cls, seq):
return cls(seq)

# Wrap sequence of values. You can specify another type besides a
# 32-bit unsigned integer.
def __init__(self, seq, data_type = ctypes.c_uint32):
array_type = data_type * len(seq)
raw_seq = array_type(*seq)
self.data = ctypes.cast(raw_seq, ctypes.c_void_p)
self.len = len(seq)

# A conversion function that cleans up the result value to make it
# nicer to consume.
def void_array_to_tuple_list(array, _func, _args):
tuple_array = ctypes.cast(array.data, ctypes.POINTER(FFITuple))
return [tuple_array[i] for i in range(0, array.len)]

lib = ctypes.cdll.LoadLibrary("./target/debug/libtupleffi.dylib")

lib.convert_vec.argtypes = (FFIArray, FFIArray)
lib.convert_vec.restype = FFIArray
lib.convert_vec.errcheck = void_array_to_tuple_list

for tupl in lib.convert_vec([1,2,3], [9,8,7]):
print tupl.a, tupl.b

原谅我的初级 Python。 我相信有经验的 Pythonista 可以让这看起来更漂亮! 感谢@eryksun 提供some nice advice关于如何使调用方法的消费者方面更加更好。

关于所有权和内存泄漏的一句话

在这个示例代码中,我们泄露了由 Vec 分配的内存。理论上,FFI 代码现在拥有内存,但实际上,它不能用它做任何有用的事情。要获得一个完全正确的示例,您需要添加另一个方法来接受从被调用者返回的指针,将其转换回 Vec,然后允许 Rust 删除该值。这是唯一安全的方法,因为 Rust 几乎可以保证使用与您的 FFI 语言使用的内存分配器不同的内存分配器。

Not sure whether I should be returning a reference and how I would have to annotate the function with the appropriate lifetime specifier if I did

不,您不想(阅读:不能)返回引用。如果可以,那么项目的所有权将以函数调用结束,并且引用将指向任何内容。这就是为什么我们需要使用 mem::forget 进行两步舞并返回一个原始指针。

关于python - 将元组的 Rust 向量转换为 C 兼容结构,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30984688/

24 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com