Create python object from memory address (using gi.repository)
Asked Answered
S

4

8

Sometimes I need to call a gtk/gobject function that only exists in C, but returns an object that has a python wrapper. Previously I used a solution based on ctypes that worked well:

http://faq.pygtk.org/index.py?req=show&file=faq23.041.htp

Now that I swiched from PyGtk ("import gtk") to GObject-introspection ("from gi.repository import Gtk"), what can I use instead?

Spermary answered 29/12, 2011 at 13:13 Comment(0)
S
8

The _PyGObject_API interface has changed at some point. I needed to drop the register_sinkfunc function. The following works:

from gi.repository import Gio, GLib
import gi
import ctypes

class _PyGObject_Functions(ctypes.Structure):
   _fields_ = [
       ('register_class',
        ctypes.PYFUNCTYPE(ctypes.c_void_p, ctypes.c_char_p,
                          ctypes.c_int, ctypes.py_object,
                          ctypes.py_object)),
       ('register_wrapper',
        ctypes.PYFUNCTYPE(ctypes.c_void_p, ctypes.py_object)),
       ('lookup_class',
        ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_int)),
       ('newgobj',
        ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
       ]

class PyGObjectCPAI(object):
   def __init__(self):
       PyCObject_AsVoidPtr = ctypes.pythonapi.PyCObject_AsVoidPtr
       PyCObject_AsVoidPtr.restype = ctypes.c_void_p
       PyCObject_AsVoidPtr.argtypes = [ctypes.py_object]
       addr = PyCObject_AsVoidPtr(ctypes.py_object(
           gi._gobject._PyGObject_API))
       self._api = _PyGObject_Functions.from_address(addr)

   def pygobject_new(self, addr):
       return self._api.newgobj(addr)

capi = PyGObjectCPAI()

To get an object from a pointer:

obj = capi.pygobject_new(pointer)

to get a pointer from a (g)object:

pointer = hash(obj)

I have to add, in my case this didn't help me solve my actual problem. I was trying to interface with dconf, but dconf returns values of type GVariant, which does not inherit from GObject. It seems PyGI/GObject unfortunately does not expose the neccessary functions to turn a C (*GVariant) into a Python GLib.Variant. I guess it's of those times when you have to throw away your initial approach and try something different.

Spermary answered 3/1, 2012 at 14:34 Comment(0)
S
4

The code in jdm's answer isn't compatible with Python 3. Since CObject is deprecated in Python 2.7 and 3.1, and removed starting with 3.2, I tweaked the code to use Capsule (available in 2.7 and since 3.1):

import ctypes, gi

class _PyGObject_Functions(ctypes.Structure):
    _fields_ = [
        ('register_class',
            ctypes.PYFUNCTYPE(ctypes.c_void_p, ctypes.c_char_p,
                ctypes.c_int, ctypes.py_object, ctypes.py_object)),
        ('register_wrapper',
            ctypes.PYFUNCTYPE(ctypes.c_void_p, ctypes.py_object)),
        ('lookup_class',
            ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_int)),
        ('newgobj',
            ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
        ]

class PyGObjectCAPI(object):
    def __init__(self):
        self._as_void_ptr.restype = ctypes.c_void_p
        self._as_void_ptr.argtypes = [ctypes.py_object]
        addr = self._as_void_ptr(ctypes.py_object(
            gi._gobject._PyGObject_API))
        self._api = _PyGObject_Functions.from_address(addr)

    @staticmethod
    def _as_void_ptr(obj):
        name = ctypes.pythonapi.PyCapsule_GetName(obj)
        return ctypes.pythonapi.PyCapsule_GetPointer(obj, name)

    def pygobject_new(self, addr):
        return self._api.newgobj(addr)

capi = PyGObjectCAPI()

(I also renamed it to PyGobjectCAPI -- not sure if CPAI stood for something, but it made more sense to me this way.)

Slade answered 10/8, 2015 at 6:53 Comment(0)
D
3

PyGOject api changed since AlliedEnvy update

import gi
import ctypes
from ctypes import pythonapi

class _PyGObject_Functions(ctypes.Structure):
    _fields_ = [
        ('pygobject_register_class',
            ctypes.PYFUNCTYPE(ctypes.c_void_p)),
        ('pygobject_register_wrapper',
            ctypes.PYFUNCTYPE(ctypes.c_void_p)),
        ('pygobject_lookup_class',
            ctypes.PYFUNCTYPE(ctypes.c_void_p)),
        ('pygobject_new',
            ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
        ]

  class PyGObjectCAPI(object):
    def __init__(self):
        addr = self._as_void_ptr(gi._gobject._PyGObject_API)
        self._api = _PyGObject_Functions.from_address(addr)

    @classmethod
    def _capsule_name(cls, capsule):
        pythonapi.PyCapsule_GetName.restype = ctypes.c_char_p
        pythonapi.PyCapsule_GetName.argtypes = [ctypes.py_object]
        return pythonapi.PyCapsule_GetName(capsule)

    @classmethod
    def _as_void_ptr(cls, capsule):
        name = cls._capsule_name(capsule)
        pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
        pythonapi.PyCapsule_GetPointer.argtypes = [
            ctypes.py_object, ctypes.c_char_p]
        return pythonapi.PyCapsule_GetPointer(capsule, name)

    def to_object(self, addr):
        return self._api.pygobject_new(addr)

capi = PyGObjectCAPI()
Disjointed answered 4/4, 2017 at 16:6 Comment(0)
L
0

With the advent of instrospection files (.typelib, .gir), the same API should be available regardless of the language being used, that is, if you're using a C function not in the API, you're probably using a function intended to be used only internally.

Leila answered 29/12, 2011 at 17:36 Comment(1)
In theory that is true. However I wanted to use a function from libdconf, and there is currently no binding for that on my OS (Ubuntu Oneric). Also, it is thinkable that one has to deal with a custom library (not part of Gtk & co.) that returns a pointer to a GObject, so in niche cases this still might be useful.Spermary

© 2022 - 2024 — McMap. All rights reserved.