Разыменование указателя FFI в Python для получения базового массива
У меня есть C FFI, написанный на Rust, называется src/lib.rs
это выглядит следующим образом:
// compile with $ cargo build
extern crate libc;
use self::libc::{size_t, int32_t};
use std::cmp::min;
use std::slice;
#[no_mangle]
pub extern "C" fn rle_new(values_data: *const int32_t, values_length: size_t) -> *mut Rle {
let values = unsafe { slice::from_raw_parts(values_data, values_length as usize).to_vec() };
return Box::into_raw(Box::new(Rle::new(values)));
}
#[no_mangle]
pub extern "C" fn rle_free(ptr: *mut Rle) {
if ptr.is_null() {
return;
}
unsafe {
Box::from_raw(ptr);
}
}
#[no_mangle]
pub extern "C" fn rle_values_size(rle: *mut Rle) -> int32_t {
unsafe { (*rle).values.len() as i32 }
}
#[no_mangle]
pub extern "C" fn rle_values(rle: *mut Rle) -> *mut int32_t {
unsafe { &mut (*rle).values[0] }
}
#[derive(Debug, PartialEq)]
pub struct Rle {
pub values: Vec<i32>,
}
impl Rle {
pub fn new(values: Vec<i32>) -> Self {
return Rle { values: values };
}
}
Это мой Cargo.toml в базовой папке проекта:
[package]
name = "minimal_example"
version = "0.1.0"
authors = ["Dumbass"]
[dependencies]
libc = "0.2.16"
[lib]
crate-type = ["dylib"] # you might need a different type on linux/windows ?
Это код Python, вызывающий Rust, также помещенный в базовую папку:
import os
import sys, ctypes
from ctypes import c_char_p, c_uint32, Structure, POINTER, c_int32, c_size_t, pointer
class RleS(Structure):
pass
prefix = {'win32': ''}.get(sys.platform, 'lib')
extension = {'darwin': '.dylib', 'win32': '.dll'}.get(sys.platform, '.so')
libpath = os.environ.get("LD_LIBRARY_PATH", "target/debug") + "/"
libpath = libpath + prefix + "minimal_example" + extension
try:
lib = ctypes.cdll.LoadLibrary(libpath)
except OSError:
print("Library not found at " + libpath)
sys.exit()
lib.rle_new.restype = POINTER(RleS)
lib.rle_free.argtypes = (POINTER(RleS), )
lib.rle_values.argtypes = (POINTER(RleS), )
lib.rle_values.restypes = POINTER(c_int32)
lib.rle_values_size.argtypes = (POINTER(RleS), )
lib.rle_values_size.restypes = c_int32
class Rle:
def __init__(self, values):
values_length = len(values)
values_array = (c_int32 * len(values))(*values)
self.obj = lib.rle_new(values_array, c_size_t(values_length))
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
lib.rle_free(self.obj)
def __str__(self):
values_size = lib.rle_values_size(self.obj)
print(values_size, "values_size") # prints correct value
values_pointer = lib.rle_values(self.obj)
print("values_pointer:", values_pointer)
ar = ctypes.cast(values_pointer, ctypes.POINTER(ctypes.c_int32)).contents
print(ar) # segfaults!
rle = Rle([1, 1, 2] * 10)
print(rle)
У меня есть веские основания полагать, что код C верен, поскольку rle_values_size
а также rle_values
ссылаются на один и тот же объект, а именно вектор Rust внутри структуры, и rle_values_size
функция работает.
Однако, когда я пытаюсь разыменовать указатель, данный rle_values
и читать его как массив я получаю segfaults.
Я пробовал каждую перестановку фрагментов кода, которые я нашел в переполнении стека, но это происходит с ошибками.
Почему это сбой? Что я делаю неправильно?
Я добавил тэг Rust, так как я мог неправильно получить адрес вектора.
Ps. Если кто-то также знает, как читать это непосредственно в массиве, я бы тоже высказался.
Справочная информация: Как мне вернуть массив в пабе extern "C" fn?
1 ответ
cast
должен быть первым предупреждающим знаком. Почему вы должны приводить от типа к тому, что должно быть того же типа? Это потому, что есть простые опечатки:
lib.rle_values.restype = POINTER(c_int32)
lib.rle_values_size.restype = c_int32
Обратите внимание, что это должно быть restype
не restypes
,
def __str__(self):
values_size = lib.rle_values_size(self.obj)
print(values_size, "values_size")
values_pointer = lib.rle_values(self.obj)
print("values_pointer:", values_pointer)
thing = values_pointer[:values_size]
return str(thing)
Также лучше использовать as_mut_ptr
:
#[no_mangle]
pub extern "C" fn rle_values(rle: *mut Rle) -> *mut int32_t {
let mut rle = unsafe { &mut *rle };
rle.values.as_mut_ptr()
}
Запуск программы работает:
$ LD_LIBRARY_PATH=$PWD/target/debug/ python3 main.py
new
30 values_size
values_pointer: <__main__.LP_c_int object at 0x10f124048>
[1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2, 1, 1, 2]
Я также рекомендую:
- возвращаемое значение ctypes по умолчанию является
cint
, Не указав тип возврата дляfree
это, вероятно, не очень хорошая идея, как это должно бытьvoid
, - вернуть число без знака для длины данных; что означают -53 пункта?
- уменьшить объем
unsafe
блокирует только часть, которая является небезопасной, и код, который обеспечивает ее безопасность. говоря о том, что вы могли бы проверить
NULL
указатели в каждой функции.#[no_mangle] pub extern "C" fn rle_values_size(rle: *mut Rle) -> int32_t { match unsafe { rle.as_ref() } { Some(rle) => rle.values.len() as i32, None => 0, } } #[no_mangle] pub extern "C" fn rle_values(rle: *mut Rle) -> *mut int32_t { match unsafe { rle.as_mut() } { Some(mut rle) => rle.values.as_mut_ptr(), None => ptr::null_mut(), } }