提问人:Wör Du Schnaffzig 提问时间:12/17/2020 更新时间:12/18/2020 访问量:190
python 上的奇怪 ctypes 行为 可调用包装 c 可调用 c_char_p argtype
Strange ctypes behaviour on python callable wrapping c callable with c_char_p argtype
问:
我在以下测试程序中观察到一个奇怪的 ctypes 相关行为:
import ctypes as ct
def _pyfunc(a_c_string):
print(type(a_c_string))
a_c_string.value = b"87654321"
return -123
my_str_buf = ct.create_string_buffer(b"test1234")
print(type(my_str_buf))
my_str_buf[3] = b'*'
print(my_str_buf.value)
my_str_buf.value = b"4321test"
print(my_str_buf.value)
signature = ct.CFUNCTYPE(ct.c_int, ct.c_char_p)
pyfunc = signature(_pyfunc)
pyfunc(my_str_buf)
print(my_str_buf.value)
该示例通过 ctypes api 将 python c 可调用项包装在 python 函数中。 目标是将 python 函数传递一个指向 c 字符串的指针,让它修改其内容(提供假值),然后返回给调用方。
我首先通过 ctypes 函数创建一个可变字符串缓冲区。
从示例中可以看出,字符串缓冲区确实是可变的。create_string_buffer
之后,我使用创建一个 c 函数原型,然后使用我的 python 函数实例化该原型,该函数应该使用相同的签名调用。最后,我使用可变字符串缓冲区调用python函数。ctypes.CFUNCTYPE(ct.c_int, ct.c_char_p)
令我恼火的是,传递给该函数形状的参数在调用函数时从类型转移到类型。不幸的是,原来的可变数据类型变成了一个完全无用的不可变字节对象。<class 'ctypes.c_char_Array_9'>
<class 'bytes'>
这是 ctypes 错误吗?Python 版本为 3.6.6。
下面是输出:
<class 'ctypes.c_char_Array_9'>
b'tes*1234'
b'4321test'
<class 'bytes'>
Traceback (most recent call last):
File "_ctypes/callbacks.c", line 234, in 'calling callback function'
File "C:/Users/andree/source/Python_Tests/ctypes_cchar_prototype.py", line 5, in _pyfunc
a_c_string.value = b"87654321"
AttributeError: 'bytes' object has no attribute 'value'
b'4321test'
预期输出:
<class 'ctypes.c_char_Array_9'>
b'tes*1234'
b'4321test'
<class 'ctypes.c_char_Array_9'>
b'87654321'
答:
1赞
Mark Tolonen
12/18/2020
#1
ctypes.c_char_p
自动转换为 Python 。如果不希望出现此行为,请使用任一方法:bytes
ctypes.POINTER(ctypes.c_char))
class PCHAR(ctypes.c_char_p): pass
(派生抑制行为)
请注意,an 没有属性,因此我必须直接取消引用指针以影响值的更改。LP_c_char
.value
此外,请注意不要超过传入的可变缓冲区的长度。我添加为附加参数。length
例:
import ctypes as ct
@ct.CFUNCTYPE(ct.c_int, ct.POINTER(ct.c_char), ct.c_size_t)
def pyfunc(a_c_string,length):
new_data = b'87654321\x00' # ensure new null termination is present.
if len(new_data) > length: # ensure new data doesn't exceed buffer length
return 0 # fail
for i,c in enumerate(new_data):
a_c_string[i] = c
return 1 # pass
my_str_buf = ct.create_string_buffer(10)
result = pyfunc(my_str_buf,len(my_str_buf))
print(result,my_str_buf.value)
my_str_buf = ct.create_string_buffer(8)
result = pyfunc(my_str_buf,len(my_str_buf))
print(result,my_str_buf.value)
1 b'87654321'
0 b''
评论
0赞
Wör Du Schnaffzig
12/18/2020
这对我有帮助。奇怪的是,如果我使用 vs.以为蜜蜂只是一个别名。ct.CFUNCTYPE(ct.c_int, ct.POINTER(ct.c_char), ct.c_size_t)
ct.CFUNCTYPE(ct.c_int, ct.c_char_p, ct.c_size_t)
ct.c_char_p
ct.POINTER(ct.c_char)
0赞
Mark Tolonen
12/18/2020
@pqans具有转换为 Python 字节对象的特殊处理。它不仅仅是一个别名。c_char_p
0赞
Wör Du Schnaffzig
12/18/2020
这就是我误解的根源。很高兴知道。
0赞
Mark Tolonen
12/18/2020
@pqnas也有类似的特殊处理。它转换为 Python 或 Python .c_void_p
None
int
0赞
Wör Du Schnaffzig
12/21/2020
“请注意,LP_c_char没有 .value 属性,所以我不得不直接 [...]” -> 需要改用 。.contents.value
评论
ct.c_char * 9
signature = ct.CFUNCTYPE(ct.c_int, ct.c_char * 9)
my_str_buf
my_str_buf
pyfunc(ct.byref(my_str_buf))
signature = ct.CFUNCTYPE(ct.c_int, ct.POINTER(ct.c_char * 9))
a_c_string.contents.value = b"87654321"