在 Python 中将 ctype 指针与 memmove 混淆使用

Confused using ctype pointers with memmove in Python

考虑以下 Python

>>> from ctypes import *
>>> from ctypes.wintypes import *
>>> class _Filename(Structure):
...     _fields_ = [("NameLengthInBytes",  USHORT),
...                 ("Name",               WCHAR * 1)]
...
>>> req = create_string_buffer(20)
>>> preq = cast(req, POINTER(_Filename))
>>> req
<ctypes.c_char_Array_20 object at 0x0000000002038DC8>
>>> preq.contents
<__main__._Filename object at 0x0000000002038EC8>
>>> preq.contents.NameLengthInBytes = 10
>>> memmove(preq.contents.Name, u"ABCDE", 10)
31932464L
>>> memoryview(req).tobytes()
'\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
>>> preq.contents.Name=u"Z"
>>> memoryview(req).tobytes()
'\n\x00Z\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

我很困惑。我希望 preqcontents 的地址与 req 相同。至少那是我认为 cast 会做的。

我想做的是创建一个连续的内存块,其中包含 NameLengthInBytes,紧接着是一个任意长度的宽字符串。我想编写代码,使其依赖于 _Filename 的字段名称,这样如果 _Filename 的定义发生变化(比如通过添加名称前的附加字段)代码仍会复制到正确的缓冲区位置。

任何人都可以帮助我了解 Python 和 ctypes 如何直接操作内存以及如何实现我想要的目标?

谢谢。

好的,我想出了我自己的问题。

首先我发现了一个提示here:

Note that ctypes does not have OOR (original object return), it constructs a new, equivalent object each time you retrieve an attribute:

>>> from ctypes import *
>>> i = c_int(42)
>>> pi = pointer(i)
>>> pi.contents is i
False
>>> pi.contents is pi.contents
False
>>>

这解释了为什么 req 和 preq.contents 不同。

接下来的问题是如何获取我需要的地址。我想出了这个功能:

def PackFilename(fn):
     bytes = len(fn) * sizeof(WCHAR)
     needed = sizeof(_Filename) + bytes
     req = create_string_buffer(needed)
     preq = cast(req, POINTER(_Filename))
     preq.contents.NameLengthInBytes = bytes
     memmove(addressof(preq[0]) + _Filename.Name.offset, fn, bytes)
     return preq.contents, needed

请注意,您必须将指向的_Filename 加上适当的偏移量来计算memmove 所需的地址。

用法是这样的:

>>> j,js=PackFilename(u"c:\jae\temp")
>>> j
<__main__._Filename object at 0x0000000001FF8DC8>
>>> js
26
>>> j.NameLengthInBytes
22
>>> j.Name
u'c'
>>> wstring_at(addressof(j) + _Filename.Name.offset, j.NameLengthInBytes/sizeof(WCHAR))
u'c:\jae\temp'

请注意,要解包名称字段,您必须反转您在包中所做的操作。可以将其放入 UnpackFilename 函数中:

def UnpackFilename(fn):
    if type(fn) is _Filename:
        size = fn.NameLengthInBytes / sizeof(WCHAR)
        return wstring_at(addressof(fn) + _Filename.Name.offset, size)
    else:
        log.debug("Unexpected argument type: %s", type(fn))
        raise TypeError()

我通过检查 PackFilename 函数中的内容(使用 memoryview(req).tobytes())确定所有字节都是一个连续的块。