PyMaemo/Accessing APIs without Python bindings

(Introduction)
Line 3: Line 3:
== Introduction ==
== Introduction ==
-
There are many libraries written in C that do not have native Python bindings yet. In Maemo, one of such libraries is libabook, used to manipulate the address book on Maemo devices.
+
There are many libraries written in C that do not have native Python bindings yet. In Maemo, one of such libraries is libosso-abook, which manipulates the address book on Maemo devices.
-
While a full binding is still very useful, in most cases you need to use just a couple of functions and data structures to get your work done. Instead of waiting for a binding to be implemented, you can use Python's "ctypes" module, which allows to directly call functions and access data structures from C libraries.
+
While a full binding is very useful, in most cases you need to use just a couple of functions and data structures to get your work done. Instead of waiting for a binding to be implemented, you can use Python's <code>ctypes</code> module, which allows to directly call functions and access data structures from C libraries.
-
This document will explain how to do call C library functions using ctypes, using libabook as an example. The idea has been borrowed from [http://hermes.garage.maemo.org/ Hermes] application source code, which in turn is based on the trick described on the [http://faq.pygtk.org/index.py?req=show&file=faq23.041.htp PyGTK FAQ].
+
This document will explain how to do call C library functions using <code>ctypes</code>, using mainly libosso-abook as an example. The idea has been borrowed from [http://hermes.garage.maemo.org/ Hermes] application source code, which in turn is based on the trick described on the [http://faq.pygtk.org/index.py?req=show&file=faq23.041.htp PyGTK FAQ].
-
This document is not meant to be a complete <code>ctypes</code> guide; for that, be sure to read the [http://docs.python.org/library/ctypes.html official API documentation].
+
This document is not meant to be a complete <code>ctypes</code> guide; for that, be sure to read the [http://docs.python.org/library/ctypes.html official API documentation]. It is assumed that you have read that document before continuing.
== Basic usage ==
== Basic usage ==
-
Let's say you want to use libc's printf, for some reason. All you need in python is:
+
Let's say you want to use printf() from the GNU C Library. All you need in Python is:
  import ctypes
  import ctypes
Line 24: Line 24:
  c_printf('Hello libc')
  c_printf('Hello libc')
-
Remember that those are C functions, not Python ones, so you must supply arguments of the correct type to avoid undefined behavior. As a example, if I pass an integer to the above function, I get
+
Remember that those are C functions, not Python ones, so you must supply arguments of the correct type to avoid undefined behavior (such as segmentation faults). As an example, if an integer is passed to the above function, you get
  >>> c_printf(1)
  >>> c_printf(1)
  Segmentation fault (core dumped)
  Segmentation fault (core dumped)
-
== Wrapping function arguments ==
+
== Initializing libosso-abook ==
-
Quoting http://docs.python.org/library/ctypes.html#calling-functions:
+
libosso-abook needs to be initialized by calling [http://maemo.org/api_refs/5.0/5.0-final/libosso-abook/libosso-abook-osso-abook-init.html#osso-abook-init osso-abook-init()]. This function takes pointers to argc, argv and a OSSO context, respectively. Here is how to call this function using ctypes:
-
: None, integers, longs, byte strings and unicode strings are the only native Python objects that can directly be used as parameters in these function calls. None is passed as a C NULL pointer, byte strings and unicode strings are passed as pointer to the memory block that contains their data (char * or wchar_t *). Python integers and Python longs are passed as the platforms default C int type, their value is masked to fit into the C type.
+
# Load and initialize libosso-abook
 +
osso_abook = ctypes.CDLL('libosso-abook-1.0.so.0')
 +
argv_type = ctypes.c_char_p * len(sys.argv)
 +
argv = argv_type(*sys.argv)
 +
argc = ctypes.c_int(len(sys.argv))
 +
osso_abook.osso_abook_init(ctypes.byref(argc), ctypes.byref(argv))
-
For all other types, a <code>ctypes</code> C wrapper must be used; you can find them at http://docs.python.org/library/ctypes.html#fundamental-data-types.  
+
The byref() function returns a pointer to the given data.
-
An usage example of these wrappers is shown below:
+
== Calling a function which returns a GObject (i.e. a constructor) ==
-
>>> libc.printf("a integer: %d, a double: %f\n", 42, 3.14)
+
Every GObject instance created by a C library must have a corresponding Python object, so that it can be manipulated on Python code. This object, which acts like a "wrapper" around the C pointer, is created using the pygobject_new() C function. Unfortunately, pygobject_new() is not a plain function, but a macro which points to a function pointer in a struct (see /usr/include/pygtk-2.0/pygobject.h on the "#define pygobject_new ..." line), making it a little more complex to be called from Python. The snippet of code below, borrowed from [http://faq.pygtk.org/index.py?req=show&file=faq23.041.htp PyGTK FAQ], will take care of this:
-
Traceback (most recent call last):
+
-
  File "<stdin>", line 1, in <module>
+
-
ctypes.ArgumentError: argument 3: <type 'exceptions.TypeError'>: Don't know how  to convert parameter 3
+
-
Integers are fine, but <code>ctypes</code> can't handle Python doubles directly. <code>ctypes.c_double()</code> will solve the problem here:
+
# ctypes wrapper for pygobject_new(), based on code snippet from
 +
# http://faq.pygtk.org/index.py?req=show&file=faq23.041.htp
 +
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)),
 +
        ('register_sinkfunc',
 +
            ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
 +
        ('lookupclass',
 +
            ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_int)),
 +
        ('newgobj',
 +
            ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
 +
    ]
-
  >>> libc.printf("a integer: %d, a double: %f\n", 42, ctypes.c_double(3.14))
+
  class PyGObjectCPAI(object):
-
a integer: 42, a double: 3.140000
+
    def __init__(self):
 +
        py_obj = ctypes.py_object(gobject._PyGObject_API)
 +
        addr = ctypes.pythonapi.PyCObject_AsVoidPtr(py_obj)
 +
        self._api = _PyGObject_Functions.from_address(addr)
-
== References ==
+
    def pygobject_new(self, addr):
 +
        return self._api.newgobj(addr)
-
<references/>
+
Adding this to your code, you will be able to create Python objects from an arbitrary GObject pointer, simply using something like:
 +
 
 +
capi = PyGObjectCPAI()
 +
c_obj = c_function_returning_gobject(...)
 +
obj = capi.pygobject_new(c_obj)
 +
 
 +
== Calling a osso_abook_contact_chooser_new() ==
 +
 
 +
As an example of a function call, let's call the osso_abook_contact_chooser_new() constructor from Python, which creates a "contact chooser" dialog useful to select one of more contacts:
 +
 
 +
c_chooser = osso_abook.osso_abook_contact_chooser_new(None, "Choose a contact")
 +
chooser = capi.pygobject_new(c_chooser)

Revision as of 19:01, 1 February 2010

This is an on-going document, and will be linked on the PyMaemo main page once finished

Contents

Introduction

There are many libraries written in C that do not have native Python bindings yet. In Maemo, one of such libraries is libosso-abook, which manipulates the address book on Maemo devices.

While a full binding is very useful, in most cases you need to use just a couple of functions and data structures to get your work done. Instead of waiting for a binding to be implemented, you can use Python's ctypes module, which allows to directly call functions and access data structures from C libraries.

This document will explain how to do call C library functions using ctypes, using mainly libosso-abook as an example. The idea has been borrowed from Hermes application source code, which in turn is based on the trick described on the PyGTK FAQ.

This document is not meant to be a complete ctypes guide; for that, be sure to read the official API documentation. It is assumed that you have read that document before continuing.

Basic usage

Let's say you want to use printf() from the GNU C Library. All you need in Python is:

import ctypes
libc = ctypes.CDLL('libc.so.6')
libc.printf('Hello world!')

In a few words, you create an object correspondent to the library you need and use it to call the function directly. You can also store the functions in plain python objects to use them easily later:

c_printf = libc.printf
c_printf('Hello libc')

Remember that those are C functions, not Python ones, so you must supply arguments of the correct type to avoid undefined behavior (such as segmentation faults). As an example, if an integer is passed to the above function, you get

>>> c_printf(1)
Segmentation fault (core dumped)

Initializing libosso-abook

libosso-abook needs to be initialized by calling osso-abook-init(). This function takes pointers to argc, argv and a OSSO context, respectively. Here is how to call this function using ctypes:

# Load and initialize libosso-abook
osso_abook = ctypes.CDLL('libosso-abook-1.0.so.0')
argv_type = ctypes.c_char_p * len(sys.argv)
argv = argv_type(*sys.argv)
argc = ctypes.c_int(len(sys.argv))
osso_abook.osso_abook_init(ctypes.byref(argc), ctypes.byref(argv))

The byref() function returns a pointer to the given data.

Calling a function which returns a GObject (i.e. a constructor)

Every GObject instance created by a C library must have a corresponding Python object, so that it can be manipulated on Python code. This object, which acts like a "wrapper" around the C pointer, is created using the pygobject_new() C function. Unfortunately, pygobject_new() is not a plain function, but a macro which points to a function pointer in a struct (see /usr/include/pygtk-2.0/pygobject.h on the "#define pygobject_new ..." line), making it a little more complex to be called from Python. The snippet of code below, borrowed from PyGTK FAQ, will take care of this:

# ctypes wrapper for pygobject_new(), based on code snippet from
# http://faq.pygtk.org/index.py?req=show&file=faq23.041.htp
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)),
        ('register_sinkfunc',
            ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
        ('lookupclass',
            ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_int)),
        ('newgobj',
            ctypes.PYFUNCTYPE(ctypes.py_object, ctypes.c_void_p)),
    ]
class PyGObjectCPAI(object):
    def __init__(self):
        py_obj = ctypes.py_object(gobject._PyGObject_API)
        addr = ctypes.pythonapi.PyCObject_AsVoidPtr(py_obj)
        self._api = _PyGObject_Functions.from_address(addr)
    def pygobject_new(self, addr):
        return self._api.newgobj(addr)

Adding this to your code, you will be able to create Python objects from an arbitrary GObject pointer, simply using something like:

capi = PyGObjectCPAI()
c_obj = c_function_returning_gobject(...)
obj = capi.pygobject_new(c_obj)

Calling a osso_abook_contact_chooser_new()

As an example of a function call, let's call the osso_abook_contact_chooser_new() constructor from Python, which creates a "contact chooser" dialog useful to select one of more contacts:

c_chooser = osso_abook.osso_abook_contact_chooser_new(None, "Choose a contact")
chooser = capi.pygobject_new(c_chooser)