【问题标题】:How are the different from_buffer() methods implemented for ctypes types?为 ctypes 类型实现的 from_buffer() 方法有何不同?
【发布时间】:2021-12-27 14:24:10
【问题描述】:

在pythonctypes模块的__init__.py文件中,ctypes类型被定义为_SimpleCData的子类。如:

from _ctypes import _SimpleCData
...
class c_long(_SimpleCData):
    _type_ = "l"

_SimpleCDatafrom_buffer() 的类方法。

所以我认为这个from_buffer() 方法也应该被子类继承。

但事实证明这些方法是不同的,因为它们位于不同的地址:

>>> ctypes.c_long.from_buffer
<built-in method from_buffer of _ctypes.PyCSimpleType object at 0x16f8fb0>
>>> ctypes._SimpleCData.from_buffer
<built-in method from_buffer of _ctypes.PyCSimpleType object at 0x7f19f9b09ae0>

不同的from_buffer() 方法如何/在哪里附加到每个ctypes 类型类?

添加 1 - 2021 年 12 月 28 日上午 9:24

我在同一个地址上使用c_int.from_address()c_char.from_address(),并解释不同的值。所以这些方法确实不一样。

>>> cint =ctypes.c_int.from_address(0x2bc9d60)
>>> cchar =ctypes.c_char.from_address(0x2bc9d60)
>>> cchar2 = ctypes.c_char.from_address(0x2bc9d61) # next byte
>>> hex(ctypes.addressof(cint))
'0x2bc9d60'
>>> hex(ctypes.addressof(cchar))
'0x2bc9d60'
>>> hex(ctypes.addressof(cchar2))
'0x2bc9d61'

>>> cint.value   # *(int *)(0x2bc9d60)  = 0x116 = 278
278
>>> cchar.value  # *(char *)(0x2bc9d60) = 0x16
b'\x16'
>>> cchar2.value # *(char *)(0x2bc9d61) = 0x1
b'\x01'

所以c_int 占用 4 个字节,c_char 仅占用 1 个字节。

这种多态性是如何实现的?

【问题讨论】:

  • 显示的地址是PyCSimpleType对象的不同实例的地址,而不是from_buffer方法。
  • @MarkTolonen 感谢您指出这一点。看来from_buffer() 方法源于_ctypes.PyCSimpleType。那么它们是一样的吗?

标签: python python-3.x ctypes


【解决方案1】:

这有点令人费解,所以请耐心等待。在_ctypes.c(3.8.5版)中,第5788行有:

    Py_TYPE(&Simple_Type) = &PyCSimpleType_Type;
    Simple_Type.tp_base = &PyCData_Type;
    if (PyType_Ready(&Simple_Type) < 0)
        return NULL;
    Py_INCREF(&Simple_Type);
    PyModule_AddObject(m, "_SimpleCData", (PyObject *)&Simple_Type);

这会将Simple_Type 的地址公开为名称_SimpleCDataSimple_Type(也就是 Python 世界中的 _SimpleCData)也继承自 PyCDataType(稍后会详细介绍)。一、Simple_Type的定义在5040行:

static PyTypeObject Simple_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "_ctypes._SimpleCData",
    sizeof(CDataObject),                        /* tp_basicsize */
    0,                                          /* tp_itemsize */
    0,                                          /* tp_dealloc */
    0,                                          /* tp_vectorcall_offset */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_as_async */
    (reprfunc)&Simple_repr,                     /* tp_repr */
    &Simple_as_number,                          /* tp_as_number */
    0,                                          /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    0,                                          /* tp_call */
    0,                                          /* tp_str */
    0,                                          /* tp_getattro */
    0,                                          /* tp_setattro */
    &PyCData_as_buffer,                         /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
    "XXX to be provided",                       /* tp_doc */
    (traverseproc)PyCData_traverse,             /* tp_traverse */
    (inquiry)PyCData_clear,                     /* tp_clear */
    0,                                          /* tp_richcompare */
    0,                                          /* tp_weaklistoffset */
    0,                                          /* tp_iter */
    0,                                          /* tp_iternext */
    Simple_methods,                             /* tp_methods */
    0,                                          /* tp_members */
    Simple_getsets,                             /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    (initproc)Simple_init,                      /* tp_init */
    0,                                          /* tp_alloc */
    GenericPyCData_new,                         /* tp_new */
    0,                                          /* tp_free */
};

这本质上是一个用于定义新类的元类。此结构的成员之一(带有注释 /* tp_methods */)引用了第 4996 行定义的以下数组:

static PyMethodDef Simple_methods[] = {
    { "__ctypes_from_outparam__", Simple_from_outparm, METH_NOARGS, },
    { NULL, NULL },

这应该是由该元类创建的类将具有的方法列表。可是等等!那只是一种方法。让我们看看它继承自的类,PyCSimpleType_Type 在第 2327 行:

PyTypeObject PyCSimpleType_Type = {
    PyVarObject_HEAD_INIT(NULL, 0)
    "_ctypes.PyCSimpleType",                                    /* tp_name */
    0,                                          /* tp_basicsize */
    0,                                          /* tp_itemsize */
    0,                                          /* tp_dealloc */
    0,                                          /* tp_vectorcall_offset */
    0,                                          /* tp_getattr */
    0,                                          /* tp_setattr */
    0,                                          /* tp_as_async */
    0,                                          /* tp_repr */
    0,                                          /* tp_as_number */
    &CDataType_as_sequence,             /* tp_as_sequence */
    0,                                          /* tp_as_mapping */
    0,                                          /* tp_hash */
    0,                                          /* tp_call */
    0,                                          /* tp_str */
    0,                                          /* tp_getattro */
    0,                                          /* tp_setattro */
    0,                                          /* tp_as_buffer */
    Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */
    "metatype for the PyCSimpleType Objects",           /* tp_doc */
    0,                                          /* tp_traverse */
    0,                                          /* tp_clear */
    0,                                          /* tp_richcompare */
    0,                                          /* tp_weaklistoffset */
    0,                                          /* tp_iter */
    0,                                          /* tp_iternext */
    PyCSimpleType_methods,                      /* tp_methods */
    0,                                          /* tp_members */
    0,                                          /* tp_getset */
    0,                                          /* tp_base */
    0,                                          /* tp_dict */
    0,                                          /* tp_descr_get */
    0,                                          /* tp_descr_set */
    0,                                          /* tp_dictoffset */
    0,                                          /* tp_init */
    0,                                          /* tp_alloc */
    PyCSimpleType_new,                                  /* tp_new */
    0,                                          /* tp_free */
};

它带来的方法是PyCSimpleType_methods在第2318行定义的:

static PyMethodDef PyCSimpleType_methods[] = {
    { "from_param", PyCSimpleType_from_param, METH_O, from_param_doc },
    { "from_address", CDataType_from_address, METH_O, from_address_doc },
    { "from_buffer", CDataType_from_buffer, METH_VARARGS, from_buffer_doc, },
    { "from_buffer_copy", CDataType_from_buffer_copy, METH_VARARGS, from_buffer_copy_doc, },
    { "in_dll", CDataType_in_dll, METH_VARARGS, in_dll_doc},
    { NULL, NULL },
};

因此,元类似乎构建了一个新类,包括元类的 struct 定义及其基类的tp_methods 成员指定的所有方法。 这应该导致from_buffer 方法对于元类创建的所有类的所有对象具有相同的地址。

参考您的帖子:

>>> ctypes.c_long.from_buffer
<built-in method from_buffer of _ctypes.PyCSimpleType object at 0x16f8fb0>
>>> ctypes._SimpleCData.from_buffer
<built-in method from_buffer of _ctypes.PyCSimpleType object at 0x7f19f9b09ae0>

我现在认为您误解了输出的内容。 0x16f8fb0 和 0x7f19f9b09ae0 不是 from_buffer 方法的地址,这两个对象应该相同,而是实现这些类型的 ctypes.PyCSimpleType 对象本身的地址。

Mark Tolonen 说得对,但如果你需要说服力(我也这样做了)...

【讨论】:

    【解决方案2】:

    列表[Python.Docs]: ctypes - A foreign function library for Python
    还列出了[GitHub]: python/cpython - (v3.9.9) This is Python version 3.9.9v3.9.9 是我将用作示例的版本)。我要提到的任何 (Python) 源文件都与此相关。

    我不会坚持使用不同的方法地址,因为它已经涵盖:它不是方法地址(方法地址在 Python 中没有任何意义)而是类对象的 (封装方法)。

    所有简单的 C 类型包装器都定义为 Lib/ctypes/__init__.py(如前所述),作为 _SimpleCData。它们之间唯一不同的是一个明显次要的细节:_type_ 类字段。检查[Python.Docs]: struct - Interpret strings as packed binary data 以获取可能的值。

    “真正的魔法”发生在 Modules/_ctypes/_ctypes.c(大部分情况下)。让我们从上到下解决问题(概念上,而不是文件中的位置)。我不打算列出行号(因为可以搜索将要提到的任何标识符名称):

    • CTypes 基于(外部)FFI

    • Simple_Type_SimpleCData 的类实现(这是它的显示名称)

    • PyCSimpleType_Type (_ctypes.PyCSimpleType) 是 Simple_Type (_SimpleCData)' s 元类(通过Py_SET_TYPE设置)。示例:

      [cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q070496724]> "e:\Work\Dev\VEnvs\py_pc064_03.09_test0\Scripts\python.exe"
      Python 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)] on win32
      Type "help", "copyright", "credits" or "license" for more information.
      >>> import ctypes as ct
      >>> import _ctypes as _ct
      >>>
      >>> SCD = _ct._SimpleCData
      >>> SCD
      <class '_ctypes._SimpleCData'>
      >>> dir(SCD)
      ['__bool__', '__class__', '__ctypes_from_outparam__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '_b_base_', '_b_needsfree_', '_objects', 'value']
      >>> SCD.__class__
      <class '_ctypes.PyCSimpleType'>
      
    • PyCSimpleType_methods 是(一些)PyCSimpleType_Type 的方法

    • CDataType_from_addressCDataType_from_buffer 是 2 种方法实现

    • 上述两种方法都依赖于 PyCData_AtAddress(注意 type 参数)。关键是:

      pd = (CDataObject *)((PyTypeObject *)type)->tp_alloc((PyTypeObject *)type, 0);
      
    • 回到PyCSimpleType_Type,它的实例创建方法是PyCSimpleType_new在创建_SimpleCData时调用这个 em> 类对象,而不是它们的实例

      • 注意 _type_ 成员(尽管最终名称会有所不同)。它会响铃吗?

      • 您还可以检查 SIMPLE_TYPE_CHARS 以获得一组熟悉的 char

    • 事情可以更深入(Modules/_ctypes/stgdict.c),但我认为这就足够了

    因此,c_* 类(简单的 C 类型包装器)没有针对这些方法的单独实现,但实现位于它们的基(元)类中,并且它们具有不同的属性值(符合OOP的继承概念)。

    我创建了一个小例子。

    code00.py

    #!/usr/bin/env python
    
    import ctypes as ct
    import sys
    
    
    def test_from_addr(addr):
        print("\nTest from_address(0x{:016X}) method for various simple C types".format(addr))
        types = (
            ct.c_ulonglong,
            ct.c_uint,
            ct.c_ushort,
            ct.c_ubyte,
        )
    
        for typ in types:
            print("{:s} representation: {:}".format(typ.__name__, hex(typ.from_address(addr).value)))
    
    
    def _simple_c_type_factory(spec):
        class SimpleCType(ct._SimpleCData):
            _type_ = spec
    
        return SimpleCType
    
    
    def test_simple_types(addr):
        print("\nTest simple C type instances creation from address: 0x{:016X}".format(addr))
        specs = "QLIHB"
        for spec in specs:
            typ = _simple_c_type_factory(spec)
            print("{:s} representation: {:}".format(spec, hex(typ.from_address(addr).value)))
    
    
    def main(*argv):
        ull0 = ct.c_ulonglong(0x1234567890ABCDEF)
        aull0 = ct.addressof(ull0)
    
        test_from_addr(aull0)
        test_simple_types(aull0)
    
    
    if __name__ == "__main__":
        print("Python {:s} {:03d}bit on {:s}\n".format(" ".join(elem.strip() for elem in sys.version.split("\n")),
                                                       64 if sys.maxsize > 0x100000000 else 32, sys.platform))
        rc = main(*sys.argv[1:])
        print("\nDone.")
        sys.exit(rc)
    

    输出

    [cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q070496724]> "e:\Work\Dev\VEnvs\py_pc064_03.09_test0\Scripts\python.exe" code00.py
    Python 3.9.9 (tags/v3.9.9:ccb0e6a, Nov 15 2021, 18:08:50) [MSC v.1929 64 bit (AMD64)] 064bit on win32
    
    
    Test from_address(0x00000199612CAE08) method for various simple C types
    c_ulonglong representation: 0x1234567890abcdef
    c_ulong representation: 0x90abcdef
    c_ushort representation: 0xcdef
    c_ubyte representation: 0xef
    
    Test simple C type instances creation from address: 0x00000199612CAE08
    Q representation: 0x1234567890abcdef
    L representation: 0x90abcdef
    I representation: 0x90abcdef
    H representation: 0xcdef
    B representation: 0xef
    
    Done.
    

    注意事项

    • 我只使用无符号类型,因为内存表示更清晰(有符号的情况完全相同)
    • 结果有点出人意料(最后部分匹配)。这是因为我的 CPU (Intel pc064) 是 little endian。在 big endian 系统上,事情会清楚得多。查看有关此主题的更多详细信息:[SO]: Python struct.pack() behavior (@CristiFati's answer)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-04-30
      • 2017-02-09
      • 2015-07-04
      相关资源
      最近更新 更多