【问题标题】:How to work with UTF-16 in python ctypes?如何在 python ctypes 中使用 UTF-16?
【发布时间】:2016-05-31 17:08:20
【问题描述】:

我有一个在 API 中使用 utf-16 的外国 C 库:作为函数参数、返回值和结构成员。

在 Windows 上它可以使用 ctypes.c_wchar_p,但在 OSX 下 ctypes 在 c_wchar 中使用 UCS-32,我找不到支持 utf-16 的方法。

这是我的研究:

  1. 使用 _SimpleCData 子类化为redefine _check_retval_

    • 它允许将 utf-16 透明地转换为 Python 字符串。
    • 可以作为C结构成员放置
    • 但它不允许将字符串作为参数处理,它的 from_param() 方法从未被调用过(为什么?): func('str', b'W\x00B\x00\x00\x00') # passed without conversion
  2. 通过from_param() 方法使用自己的类型。

    • 优点:可以使用构造函数初始化,也可以在将字符串传递给函数时动态编码:
    • 缺点:不能用作函数返回类型或结构成员。

这里是:

ustr = myutf16('hello')
func(ustr)
func('hello')   # calls myutf16.from_param('hello')

【问题讨论】:

  • 是否必须使用“ctypes”而不是“unicode”和“codecs”?
  • 非常可取。当然,我可以手动编码和解码 utf-16,但是我需要为每个函数调用创建很多包装器。
  • 我认为从安全/可理解性的角度来看,在内部仅使用“unicode”对象或“UTF-8”字符串,并且仅在调用其他系统和库的情况下执行转换会更好与其他方法相比,长期来看。我不会在系统中传递其他类型的字符串,除非编码/解码开销使得必须这样做。混合大量不同的字符串类型会使代码变得非常具有挑战性。
  • 如果你只使用 Python 2,那么你可以调用 ctypes.set_conversion_mode('utf-16le', 'strict'),它允许你通过转换为一个临时的 UTF-16 缓冲区来传递 unicode 字符串。同样,它允许您将unicode 分配给c_char_p 结构字段。但我不推荐这种方法,因为它没有反映在 getfunc 行为中,它仍然是一个以 null 结尾的 char *,而且它不适用于 Python 3。
  • @eryksun 感谢描述符的提及,这对我来说听起来很有趣。

标签: python ctypes utf-16 python-unicode wchar-t


【解决方案1】:

您可以在 c_char_p 子类中覆盖 from_param 以将 unicode 字符串编码为 UTF-16。您可以添加 _check_retval_ 方法将 UTF-16 结果解码为 unicode 字符串。对于结构字段,您可以使用处理设置和获取属性的描述符类。将该字段设为c_char_p 类型的私有_name,并将描述符设置为公共name。例如:

import sys
import ctypes

if sys.version_info[0] > 2:
    unicode = str

def decode_utf16_from_address(address, byteorder='little',
                              c_char=ctypes.c_char):
    if not address:
        return None
    if byteorder not in ('little', 'big'):
        raise ValueError("byteorder must be either 'little' or 'big'")
    chars = []
    while True:
        c1 = c_char.from_address(address).value
        c2 = c_char.from_address(address + 1).value
        if c1 == b'\x00' and c2 == b'\x00':
            break
        chars += [c1, c2]
        address += 2
    if byteorder == 'little':
        return b''.join(chars).decode('utf-16le')
    return b''.join(chars).decode('utf-16be')

class c_utf16le_p(ctypes.c_char_p):
    def __init__(self, value=None):
        super(c_utf16le_p, self).__init__()
        if value is not None:
            self.value = value

    @property
    def value(self,
              c_void_p=ctypes.c_void_p):
        addr = c_void_p.from_buffer(self).value
        return decode_utf16_from_address(addr, 'little')

    @value.setter
    def value(self, value,
              c_char_p=ctypes.c_char_p):
        value = value.encode('utf-16le') + b'\x00'
        c_char_p.value.__set__(self, value)

    @classmethod
    def from_param(cls, obj):
        if isinstance(obj, unicode):
            obj = obj.encode('utf-16le') + b'\x00'
        return super(c_utf16le_p, cls).from_param(obj)

    @classmethod
    def _check_retval_(cls, result):
        return result.value

class UTF16LEField(object):
    def __init__(self, name):
        self.name = name

    def __get__(self, obj, cls,
                c_void_p=ctypes.c_void_p,
                addressof=ctypes.addressof):
        field_addr = addressof(obj) + getattr(cls, self.name).offset
        addr = c_void_p.from_address(field_addr).value
        return decode_utf16_from_address(addr, 'little')

    def __set__(self, obj, value):
        value = value.encode('utf-16le') + b'\x00'
        setattr(obj, self.name, value)

示例:

if __name__ == '__main__':
    class Test(ctypes.Structure):
        _fields_ = (('x', ctypes.c_int),
                    ('y', ctypes.c_void_p),
                    ('_string', ctypes.c_char_p))
        string = UTF16LEField('_string')

    print('test 1: structure field')
    t = Test()
    t.string = u'eggs and spam'
    print(t.string)

    print('test 2: parameter and result')
    result = None

    @ctypes.CFUNCTYPE(c_utf16le_p, c_utf16le_p)
    def testfun(string):
        global result
        print('parameter: %s' % string.value)
        # callbacks leak memory except for simple return
        # values such as an integer address, so return the
        # address of a global variable.
        result = c_utf16le_p(string.value + u' and eggs')
        return ctypes.c_void_p.from_buffer(result).value

    print('result: %s' % testfun(u'spam'))

输出:

test 1: structure field
eggs and spam

test 2: parameter and result
parameter: spam
result: spam and eggs

【讨论】:

  • 太棒了!唯一的问题,我可以在没有“.value”的情况下直接从 c_utf16le_p 访问“str”方法吗?
  • 您可以将调用unicode (3.x str) 方法的方法添加到c_utf16le_p self.value 并添加一个__unicode__ (3.x __str__) 特殊方法用于打印等。当它是NULL 指针时,您需要考虑None 的值。但是作为函数结果,它已经返回了unicode 字符串,所以我不知道添加这些方法会有多大用处。
猜你喜欢
  • 2011-10-10
  • 1970-01-01
  • 2011-03-09
  • 2019-05-15
  • 2020-08-02
  • 2014-04-22
  • 1970-01-01
  • 1970-01-01
  • 2016-07-23
相关资源
最近更新 更多