【问题标题】:Porting a VBA Type / C struct to a Python ctypes.Structure: array of strings with fixed length将 VBA Type / C 结构移植到 Python ctypes.Structure:固定长度的字符串数组
【发布时间】:2014-07-27 03:03:15
【问题描述】:

我正在尝试将一段 VBA 代码移植到 Python。这项工作包括调用 Windows DLL 中的函数。该函数需要一个指向 C 结构的指针(在 VBA 中,它们被称为“类型”)作为参数。提到的结构包含固定长度的字符串以及固定长度的字符串数组。我正在努力寻找一种使用 ctypes 在 Python 中表达这一点的方法。

原始 VBA 代码包含如下语句:

Public Type elements
    elementA As String * 48
    elementB(3) As String * 12
End Type

我认为这在 C 中可以用以下方式表示:

struct elements
{
    char elementA[48];
    char elementB[4][12];
}

到目前为止我在 Python 中的尝试:

import ctypes

class elements(ctypes.Structure):
    _fields_ = [
        ("elementA", ctypes.create_string_buffer(48)), 
        ("elementB", ctypes.create_string_buffer(12) * 4)
        ]

我可以成功声明 elementA,尽管声明 elementB 失败并显示

"TypeError: *: 'c_char_Array_12' 和 'int' 的操作数类型不受支持"

如何以正确的方式做到这一点?


更新 #1

我可以成功声明以下内容:

import ctypes

class elements(ctypes.Structure):
    _fields_ = [
        ("elementA", ctypes.c_char * 48), 
        ("elementB", ctypes.c_char * 12 * 4)
        ]

elementA 暴露了一个“值”属性,而我找不到使用 elementB 的方法。如何阅读或更改其内容?


更新 #2

我想我理解这种行为。

>>> e = elements()
>>> e.elementA
''
>>> e.elementA = 'test'
>>> e.elementA
'test'
>>> e.elementB
<__main__.c_char_Array_12_Array_4 object at 0x9878ecc>
>>> e.elementB[0][:] == '\x00' * 12
True
>>> e.elementB[0][:]
'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
>>> e.elementB[0][:] = 'test'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: Can only assign sequence of same size
>>> e.elementB[0][:] = 'test' + '\x00' * 8
>>> e.elementB[0][:]
'test\x00\x00\x00\x00\x00\x00\x00\x00'
>>> testB = 'abcde'
>>> e.elementB[0][:] = testB + '\x00' * ( ctypes.sizeof(e.elementB[0]) - len(testB) )
>>> e.elementB[0][:]
'abcde\x00\x00\x00\x00\x00\x00\x00'
>>> e.elementB[0][:].rstrip('\x00')
'abcde'
>>> e.elementB[0].value
'abcde'
>>> e.elementB[0].value = 'abcdef'
>>> e.elementB[0][:]
'abcdef\x00\x00\x00\x00\x00\x00'

(这个问题指的是 Python 2.6 和 2.7。)

【问题讨论】:

  • @eryksun 感谢您的帮助。我将您的评论纳入了我的问题,但是,我在使用它时遇到了困难。 2D-char-array 没有公开任何类似“值”属性的东西,我不知道如何读取或写入该数组。
  • @eryksun 再次感谢。我想我现在理解了这种行为 - 请参阅我的问题的最后更新。你想把这个总结成一个答案吗?

标签: python c vba struct ctypes


【解决方案1】:

create_string_buffer 是创建c_char 数组实例的便捷函数。但是,字段定义需要 C 类型,而不是实例。例如:

import ctypes

class elements(ctypes.Structure):
    _fields_ = [("elementA", ctypes.c_char * 48), 
                ("elementB", ctypes.c_char * 12 * 4)]

假设你有一个如下定义的 C 函数:

lib.func.argtypes = [ctypes.POINTER(elements)]

要调用此函数,请使用byref 传递elements 的实例:

e = elements()
lib.func(ctypes.byref(e))

访问一维c_char 数组字段,例如elementA,在特殊情况下会返回 Python 字符串。但是访问二维数组,例如elementB,会返回一个 ctypes Array 实例。在elementB 的情况下,有 4 行,每行包含 12 列。

>>> len(e.elementB)
4
>>> map(len, e.elementB)
[12, 12, 12, 12]

sizeof 返回数组的大小(以字节为单位)。例如,elementB 的缓冲区由 48 个 c_char 元素组成,每个元素 1 个字节:

>>> ctypes.sizeof(e.elementB)
48

elementB 的 c_char 数组作为字符数组,在特殊情况下具有 value 和 raw 属性。获取value 属性会创建一个 Python 字符串,该字符串将数组视为以空字符结尾的 C 字符串。 raw 属性返回整个长度。您还可以使用这些属性分配 Python 字符串,并且都接受其中包含 null 的字符串。

>>> e.elementB[3].value = 'abc\x00def'
>>> e.elementB[3].value
'abc'
>>> e.elementB[3].raw
'abc\x00def\x00\x00\x00\x00\x00'

或者对数组进行切片得到子串:

>>> e.elementB[3][:]
'abc\x00def\x00\x00\x00\x00\x00'
>>> e.elementB[3][4:7]
'def'

c_wchar 数组只有value 属性,它返回一个unicode 字符串。您可以使用 unicode 字符串或(在 Python 2 中)8 位字符串来设置 value。使用当前的 ctypes 编码解码 8 位字符串,在 Windows 上默认为 'mbcs',否则默认为 'ascii'。 set_conversion_mode (Python 2) 设置默认编码:

>>> s = (ctypes.c_wchar * 12)()
>>> v = u'\u0100'.encode('utf-8')
>>> v
'\xc4\x80'
>>> s.value = v
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xc4 in position 0: 
ordinal not in range(128)
>>> old_mode = ctypes.set_conversion_mode('utf-8', 'strict')
>>> old_mode
('ascii', 'strict')

既然转换编码设置为 UTF-8,那么分配 '\xc4\x80' 就可以工作了:

>>> s.value = v
>>> s.value
u'\u0100'
>>> s[:]
u'\u0100\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

数组是可迭代的:

for row in e.elementB:
    row[:] = 'abcdefghijkl'

>>> print '\n'.join(row[::-1] for row in e.elementB)
lkjihgfedcba
lkjihgfedcba
lkjihgfedcba
lkjihgfedcba

ctypes 数据类型支持 Python 的缓冲区协议,以便与其他类型进行互操作:

>>> bytearray(e.elementB)
bytearray(b'abcdefghijklabcdefghijklabcdefghijklabcdefghijkl')

>>> import numpy as np
>>> np.frombuffer(e.elementB, dtype='uint8')
array([ 97,  98,  99, 100, 101, 102, 103, 104, 105, 106, 107, 108,  97,
        98,  99, 100, 101, 102, 103, 104, 105, 106, 107, 108,  97,  98,
        99, 100, 101, 102, 103, 104, 105, 106, 107, 108,  97,  98,  99,
       100, 101, 102, 103, 104, 105, 106, 107, 108], dtype=uint8)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-12-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多