将VBA类型/C结构移植到Python ctypes.Structure:固定长度字符串数组

1 投票
1 回答
566 浏览
提问于 2025-04-18 08:48

我正在尝试把一段VBA代码移植到Python。这项工作包括调用一个Windows DLL中的函数。这个函数需要一个指向C语言结构体的指针(在VBA中,这种结构体叫“Type”)作为参数。这个结构体里包含了固定长度的字符串和固定长度的字符串数组。我在用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: unsupported operand type(s) for *: 'c_char_Array_12' and 'int'”

我该如何正确地做到这一点呢?


更新 #1

我能成功声明以下内容:

import ctypes

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

elementA有一个“value”属性,但我找不到处理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的。)

1 个回答

3

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

作为字符数组的 elementBc_char 数组,特别处理后有 valueraw 属性。获取 value 属性会创建一个 Python 字符串,把数组当作一个以 null 结尾的 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。8 位字符串会根据当前的 ctypes 编码进行解码,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')

现在,分配 '\xc4\x80' 是有效的,因为转换编码设置为 UTF-8:

>>> 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)

撰写回答