为什么在使用 cdecl Rust 函数和 Python 的 Ctypes 传递指向切片的指针时会出现调用约定不匹配?

Why do I get a calling convention mismatch when passing a pointer to a slice with a cdecl Rust function and Python's Ctypes?

使用 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 参数。它之所以如此命名,是因为它最终包含一个垃圾地址。请注意,它的位置在 Python 调用和 Rust 声明中与 out_array_p 交换。

[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_dataout_datan 在此配置中打印正确的值。 garbageout_data 之间的位置交换使这成为可能。

使用更多或更少参数的其他示例揭示了中间有序变量的类似模式,其中包含奇数值,类似于程序中较早的地址或不相关的垃圾。

要么是我在设置调用约定的过程中遗漏了一些东西,要么是 argtypes 中的某些特殊魔法遗漏了。到目前为止,我还没有成功更改声明的调用约定或显式参数类型。还有其他我应该尝试转动的旋钮吗?

in_data: *mut [c_ulong],

切片是不是一种FFI-safe数据类型。即,Rust 的切片使用 胖指针 ,它占用两个 pointer-sized 值。

您需要将数据指针和长度作为两个单独的参数传递。

另请参阅:

来自 Omnibus 的完整示例:

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

的主要作者