簡體   English   中英

將元組的Rust向量轉換為C兼容結構

[英]Convert Rust vector of tuples to a C compatible structure

按照這些 答案 ,我目前已如下定義了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)

這似乎還可以,但我是:

  • 不知道如何將combinedVec<(i32, i32)> )轉換為C兼容結構,因此可以將其返回給我的Python腳本。
  • 不知道我是否應該返回引用( return &combined ?),如果執行了該操作,則如何用適當的生命周期說明符注釋函數?

需要注意的最重要的事情是,C 語言沒有元組之類的東西 。C 語言是圖書館互操作性的通用語言 ,您將不得不限制自己使用這種語言的能力。 是否在Rust和另一種高級語言之間對話並不重要。 你必須說C

C中可能沒有元組,但是有struct s。 兩個元素的元組只是具有兩個成員的結構!

讓我們從要編寫的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中定義完全相同的結構,並將它們定義為具有完全相同的成員(類型,順序,名稱)。 重要的是,我們使用#[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)
}

我們絕不能跨FFI邊界接受或返回non- repr(C)類型,因此我們要遍歷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對於一些不錯的建議 ,就如何使調用該方法好得多的消費方。

關於所有權和內存泄漏的一句話

在此示例代碼中,我們泄漏了Vec分配的內存。 從理論上講,FFI代碼現在擁有該內存,但是實際上,它不能對其做任何有用的事情。 要獲得一個完全正確的示例,您需要添加另一個方法,該方法將接受被調用者的指針,將其轉換回Vec ,然后允許Rust刪除該值。 這是唯一安全的方法,因為Rust幾乎可以保證使用與FFI語言所使用的內存分配器不同的內存分配器。

不知道我是否應該返回引用,如果執行了該操作,則如何用適當的生命周期說明符注釋該函數

不,您不想(閱讀: 不能 )返回引用。 如果可以,則該項目的所有權將以函數調用結束,並且引用將指向任何內容。 這就是為什么我們需要使用mem::forget進行兩步跳舞並返回原始指針的原因。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM