• PyObjects From Python Via ctypes

    From Lawrence D?Oliveiro@3:633/10 to All on Thu Jun 11 07:41:20 2026
    I see ctypes has a py_object type, but you can?t seem to do much with
    it. The following might be more useful:

    ---- file: pyobjhack.py
    import ctypes as ct

    def _init_hack() :
    # works out the values of TRACE_REFS and PYOBJECT_HEADER_SIZE.
    global TRACE_REFS, PYOBJECT_HEADER_SIZE
    o = object()
    # CPython implementation detail: ?id()? function returns address of object header
    if ct.c_size_t.from_address(id(o)).value == 1 :
    TRACE_REFS = False
    PYOBJECT_HEADER_SIZE = 2
    elif ct.c_size_t.from_address(id(o) + 2 * ct.sizeof(ct.c_void_p)).value == 1 :
    TRACE_REFS = True
    PYOBJECT_HEADER_SIZE = 4
    else :
    raise RuntimeError("cannot determine pyobject header size")
    #end if
    # header of every PyObject has ob_refcnt (type ssize_t) followed by ob_type (type pointer),
    # but these might be preceded by a pair of next/prev pointers if Py_TRACE_REFS is defined.
    # Only way I can figure out to determine which is the case is to construct an object
    # with a known refcount of 1, and see if I can find the value 1 at the start.
    # Confirm that the refcount field behaves as expected:
    o2 = o
    assert ct.c_size_t.from_address(id(o) + (PYOBJECT_HEADER_SIZE - 2) * ct.sizeof(ct.c_void_p)).value == 2
    del o2
    assert ct.c_size_t.from_address(id(o) + (PYOBJECT_HEADER_SIZE - 2) * ct.sizeof(ct.c_void_p)).value == 1
    #end _init_hack
    _init_hack()
    del _init_hack

    class PyObject(ct.Structure) :
    "header part of a PyObject structure."

    _fields_ = \
    (
    (
    [],
    [
    ("_ob_next", ct.c_void_p),
    ("_ob_prev", ct.c_void_p),
    ],
    )[TRACE_REFS]
    +
    [
    ("ob_refcnt", ct.c_ssize_t),
    ("ob_type", ct.c_void_p),
    ]
    )

    @classmethod
    def from_obj(celf, obj) :
    "aliases the object header to a PyObject struct."
    return \
    celf.from_address(id(obj))
    #end from_obj

    #end PyObject

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)
  • From Lawrence D?Oliveiro@3:633/10 to All on Thu Jun 11 07:44:15 2026
    On Thu, 11 Jun 2026 07:41:20 -0000 (UTC), I wrote:

    I see ctypes has a py_object type, but you can?t seem to do much
    with it. The following might be more useful:

    Example use: accessing a more powerful Cairo wrapper in a context
    where the GUI toolkit wrapper (e.g. PyGObject for GTK) wants to
    give you only a pycairo object.

    ----
    import ctypes as ct
    import qahirah as qah
    from pyobjhack import PyObject

    class PyCairo_Context(ct.Structure) :
    "extract cairo_t pointer from pycairo.Context object."
    _fields_ = \
    [
    ("header", PyObject),
    ("ctx", ct.c_void_p),
    ]

    @classmethod
    def get_ctx(celf, pyctx) :
    return \
    ct.c_void_p(celf.from_address(id(pyctx)).ctx).value
    #end get_ctx

    #end PyCairo_Context

    def pycairo_to_qah(ctx) :
    "returns a qah.Context object wrapping the cairo_t pointer from" \
    " a given cairo.Context object."
    gx = PyCairo_Context.get_ctx(ctx)
    qah.cairo.cairo_reference(gx) # grab one for myself
    return \
    qah.Context(gx)
    #end pycairo_to_qah

    --- PyGate Linux v1.5.15
    * Origin: Dragon's Lair, PyGate NNTP<>Fido Gate (3:633/10)