为什么在使用 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_data
、out_data
和 n
在此配置中打印正确的值。 garbage
和 out_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
的主要作者
使用 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_data
、out_data
和 n
在此配置中打印正确的值。 garbage
和 out_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
的主要作者