我有一个C ++库,该库对音频数据进行分析,并提供一个C API。 C API函数之一采用const int16_t*
指向数据的指针并返回分析结果。
我正在尝试为此API建立一个Python接口,并且大多数接口都在工作,但是我在获取ctypes指针用作该函数的参数时遇到了麻烦。由于C端的指针指向const
,所以我觉得应该可以对任何连续的数据进行良好的处理。但是,以下方法不起作用:
import ctypes
import wave
_native_lib = ctypes.cdll.LoadLibrary('libsound.so')
_native_function = _native_lib.process_sound_data
_native_function.argtypes = [ctypes.POINTER(ctypes.c_int16),
ctypes.c_size_t]
_native_function.restype = ctypes.c_int
wav_path = 'hello.wav'
with wave.open(wav_path, mode='rb') as wav_file:
wav_bytes = wav_file.readframes(wav_file.getnframes())
data_start = ctypes.POINTER(ctypes.c_int16).from_buffer(wav_bytes) # ERROR: data is immutable
_native_function(data_start, len(wav_bytes)//2)
手动将wav_bytes
复制到bytearray
可以构造指针,但会导致本机代码段错误,表明接收到的地址是错误的(它通过单元测试,并从C ++读取数据)。通过正确的地址解决此问题从技术上可以解决问题,但我觉得有更好的方法。
当然可以只获取某些数据的地址,并保证它是正确的格式并且不会被更改吗?我不想不必将我所有以Python方式存储的音频数据深深复制为ctypes格式,因为如果我能找到指向它们的指针,大概字节就在某个地方!
理想情况下,我希望能够做这样的事情
data_start = cast_to(address_of(data[0]), c_int16_pointer)
_native_function(data_start, len(data))
然后可以与具有[0]
和len
的任何内容一起使用。有没有办法在ctypes中做这样的事情?如果不是,是否有技术原因导致它不可能实现?还有其他我应该使用的东西吗?
答案 0 :(得分:1)
这应该为您工作。将array
用于可写缓冲区,并创建引用该缓冲区的ctypes数组。
data = array.array('h',wav_bytes)
addr,size = data.buffer_info()
arr = (c_short * size).from_address(addr)
_native_function(arr,size)
或者,要跳过将wav_bytes
的副本复制到data
数组中,可以使用argtypes中的指针类型。 ctypes
知道如何将字节字符串转换为c_char_p
。指针只是一个地址,因此_native_function
将收到该地址,但在内部将其用作int*
:
_native_function.argtypes = c_char_p,c_size_t
_native_function(wav_bytes,len(wav_bytes) // 2)
解决“底层缓冲区不可写”错误的另一种方法是利用c_char_p
,它允许使用不可变的字节字符串,然后将其显式转换为所需的指针类型:
_native_function.argtypes = POINTER(c_short),c_size_t
p = cast(c_char_p(wav_bytes),POINTER(c_short))
_native_function(p,len(wav_bytes) // 2)
在后一种情况下,您必须确保不实际写入缓冲区,因为它会破坏保存数据的不变的Python对象。
答案 1 :(得分:0)
我环顾了CPython Bug跟踪器,看是否以前曾出现过这种错误,似乎是raised as an issue in 2011。我同意发布者的观点,认为这是一个严重的错误设计,但当时的开发人员似乎并没有这么做。
Eryk Sun对该线程的评论表明,实际上有可能直接使用ctypes.cast
。这是评论的一部分:
cast
呼叫ctypes._cast(obj, obj, typ)
。_cast
是ctypes函数指针,定义如下:_cast = PYFUNCTYPE(py_object, c_void_p, py_object, py_object)(_cast_addr)
由于
cast
进行了FFI调用,将第一个arg转换为c_void_p
,因此您可以直接将bytes
强制转换为指针类型:>>> from ctypes import * >>> data = b'123\x00abc' >>> ptr = cast(data, c_void_p)
对于我来说尚不清楚,这是标准实际上是必需的,还是仅仅是CPython实现的细节,但是以下内容在CPython中对我有用:
import ctypes
data = b'imagine this string is 16-bit sound data'
data_ptr = ctypes.cast(data, ctypes.POINTER(ctypes.c_int16))
ctypes.cast(obj, type)
此函数类似于C中的强制转换运算符。它返回类型新的实例,该实例指向与obj相同的内存块。 type必须是指针类型,而obj必须是可以解释为指针的对象。
因此,看来CPython认为bytes
'可以解释为指针'。在我看来,这似乎有些可疑,但是这些现代的隐藏指针的语言有一种弄乱我直觉的方式。