【问题标题】:Strange ctypes behaviour on python callable wrapping c callable with c_char_p argtypepython callable wrapping c callable with c_char_p argtype 上的奇怪 ctypes 行为
【发布时间】:2020-12-17 12:30:30
【问题描述】:

我在以下测试程序中观察到与 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 函数中包装了一个可调用的 python c。 目标是向 python 函数传递一个指向 c 字符串的指针,让它修改它的内容(提供一个假值),然后返回给调用者。

我首先通过 ctypes 函数 create_string_buffer 创建一个可变字符串缓冲区。 从例子可以看出,字符串缓冲区确实是可变的。

之后,我使用 ctypes.CFUNCTYPE(ct.c_int, ct.c_char_p) 创建了一个 c 函数原型,然后使用我的 python 函数实例化该原型,该函数应该使用相同的签名来调用。最后我用我的可变字符串缓冲区调用 python 函数。

让我恼火的是,当函数被调用时,传递给该函数形状的参数从<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'

【问题讨论】:

  • 如果将参数类型更改为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))。这将要求您将参数类型更改为指向 char 数组的指针。 signature = ct.CFUNCTYPE(ct.c_int, ct.POINTER(ct.c_char * 9)) 并在函数内修改该指针的内容。 a_c_string.contents.value = b"87654321" 总而言之,这应该会给你你想要的行为。
  • 这也行。作为副作用,它需要我传递精确宽度的字符串。这可能是一个优势,也可能不是。就我而言,这很好。

标签: python-3.x string byte ctypes mutable


【解决方案1】:

ctypes.c_char_p 会自动转换为 Python bytes。如果您不想要这种行为,请使用:

  • ctypes.POINTER(ctypes.c_char))
  • class PCHAR(ctypes.c_char_p): pass(派生抑制行为)

请注意,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''

【讨论】:

  • 这对我有帮助。奇怪的是,如果我使用 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_pbeeing 只是ct.POINTER(ct.c_char) 的别名。
  • @pqans c_char_p 具有转换为 Python 字节对象的特殊处理。这不仅仅是一个别名。
  • 那是我误解的根源。很高兴知道。
  • @pqnas c_void_p 有类似的特殊处理。它转换为 Python None 或 Python int
  • "请注意,LP_c_char 没有 .value 属性,所以我必须直接 [...]" -> 需要改用 .contents.value
猜你喜欢
  • 2014-07-14
  • 2010-12-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多