Почему я получаю несоответствие соглашения о вызовах при передаче указателя на фрагмент с помощью функции Rust cdecl и Ctypes Python? - PullRequest
0 голосов
/ 06 июля 2019

Существует очевидное несоответствие соглашения о вызовах, когда позиция и содержимое аргументов неверны при загрузке небольшой функции с помощью модуля Python Ctypes.

В примере, который я создал при попытке заставить что-то работать, один позиционный аргумент получает значение другого, а другой - мусор.

Документы Ctypes утверждают, что cdll.LoadLibrary ожидает соглашения cdecl. Результирующий стандартный шаблон:

# Tell Rustc to output a dynamically linked library
crate-type = ["cdylib"]
// Specify clean symbol and cdecl calling convention
#[no_mangle]
pub extern "cdecl" fn boring_function(
    n: *mut size_t,
    in_data: *mut [c_ulong],
    out_data: *mut [c_double],
    garbage: *mut [c_double],
) -> c_int {
    //...

Загрузка нашей библиотеки после сборки ...

lib = ctypes.CDLL("nothing/lib/playtoys.so")
lib.boring_function.restype = ctypes.c_int

Загрузить результат в Python и вызвать его с некоторыми инициализированными данными

data_len = 8
in_array_t = ctypes.c_ulong * data_len
out_array_t = ctypes.c_double * data_len
in_array = in_array_t(7, 7, 7, 7, 7, 8, 7, 7)
out_array = out_array_t(10000.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9)
val = ctypes.c_size_t(data_len)
in_array_p = ctypes.byref(in_array)
out_array_p = ctypes.byref(out_array)
n_p = ctypes.byref(val)
garbage = n_p
res = boring_function(n_p,
                      in_array_p,
                      # garbage cannot be observed in any callee arg
                      ctypes.cast(garbage, ctypes.POINTER(out_array_t)),
                      out_array_p)

Обратите внимание на параметр garbage. Это так называется, потому что он содержит адрес мусора. Обратите внимание, что его позиция поменялась местами с out_array_p в вызове Python и объявлении Rust.

[src/hello.rs:29] n = 0x00007f56dbce5bc0
[src/hello.rs:30] in_data = 0x00007f56f81e3270
[src/hello.rs:31] out_data = 0x00007f56f81e3230
[src/hello.rs:32] garbage = 0x000000000000000a

in_data, out_data и n выводят правильные значения в этой конфигурации. Позиционный обмен между garbage и out_data делает это возможным.

Другие примеры, использующие более или менее аргументы, показывают схожие шаблоны промежуточных упорядоченных переменных, содержащих нечетные значения, которые напоминают адреса ранее в программе, или несвязанный мусор.

Либо я что-то упускаю в настройке соглашения о вызовах, либо какая-то особая магия в argtypes должна отсутствовать. До сих пор мне не повезло с изменением объявленных соглашений о вызовах или явных аргументов. Есть ли другие ручки, которые я должен попробовать повернуть?

1 Ответ

2 голосов
/ 06 июля 2019
in_data: *mut [c_ulong],

Срез не FFI-безопасный тип данных. А именно, фрагменты Rust используют жирные указатели , которые принимают два значения размера указателя.

Вам нужно передать указатель данных и длину в виде двух отдельных аргументов.

Смотри также:

Полный пример из Омнибуса :

extern crate libc;

use libc::{uint32_t, size_t};
use std::slice;

#[no_mangle]
pub extern fn sum_of_even(n: *const uint32_t, len: size_t) -> uint32_t {
    let numbers = unsafe {
        assert!(!n.is_null());

        slice::from_raw_parts(n, len as usize)
    };

    let sum =
        numbers.iter()
        .filter(|&v| v % 2 == 0)
        .fold(0, |acc, v| acc + v);
    sum as uint32_t
}
#!/usr/bin/env python3

import sys, ctypes
from ctypes import POINTER, c_uint32, c_size_t

prefix = {'win32': ''}.get(sys.platform, 'lib')
extension = {'darwin': '.dylib', 'win32': '.dll'}.get(sys.platform, '.so')
lib = ctypes.cdll.LoadLibrary(prefix + "slice_arguments" + extension)

lib.sum_of_even.argtypes = (POINTER(c_uint32), c_size_t)
lib.sum_of_even.restype = ctypes.c_uint32

def sum_of_even(numbers):
    buf_type = c_uint32 * len(numbers)
    buf = buf_type(*numbers)
    return lib.sum_of_even(buf, len(numbers))

print(sum_of_even([1,2,3,4,5,6]))

Отказ от ответственности: я являюсь основным автором Omnibus

...