我正在尝试将一段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:*:' 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。)
答案 0 :(得分: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)]
要调用此函数,请使用elements
传递byref
的实例:
e = elements()
lib.func(ctypes.byref(e))
访问1-D 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
作为字符数组的c_char
数组elementB
是特殊的,具有value
和raw
属性。获取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
字符串。您可以使用value
字符串或(在Python 2中)将unicode
设置为8位字符串。使用当前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')
现在分配'\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)