Extending Python Using Ctypes

James Teh

NetBox Blue Pty Ltd

What Is ctypes?

ctypes is an advanced ffi (Foreign Function Interface) package for Python 2.3 and higher. In Python 2.5 it is already included.

ctypes allows to call functions in dlls/shared libraries and has extensive facilities to create, access and manipulate simple and complicated C data types in Python - in other words: wrap

ctypes works on Windows, Windows CE, Mac OS X, Linux, Solaris, FreeBSD, OpenBSD. It may also run on other systems, provided that libffi supports this platform.

ctypes is licensed under the MIT License.

Getting Started

In Linux, the full file name, including extension, must be specified to load a library, so attribute access does not work. An alternative to using the CDLL constructor as described above is to use the LoadLibrary method of the dll loader:
>>> libc = cdll.LoadLibrary("libc.so.6")

cdll loads libraries which export functions using the standard cdecl calling convention. On Windows, there is also windll and oledll, which both call functions using the stdcall calling convention. oledll assumes that functions return a Windows HRESULT error code, which is used to automatically raise WindowsError exceptions when the call fails.

Simple Data Types

Calling Functions

Note that you need to load libm in the same way that libc was loaded. On Linux:
>>> libm = CDLL("libm.so.6")
On Windows, the math functions are also in msvcrt, so this will suffice:
>>> libm = libc

You can also access extern variables in libraries by using the in_dll method on a ctypes type. For example:

>>> c_int.in_dll(libc, 'errno')
c_long(0)

Specifying Argument and Return Types

Pointers

Just as in C, you need to be careful when accessing pointer indexes other than 0, as you can access arbitrary memory locations!

Pointers

Arrays

create_string_buffer() can also accept an initial string as the first parameter. If no size is specified, the size of the string is used.

Returned Pointers

Note the use of None here to indicate NULL.

Error Handling

  • The object returned by errcheck if no exception is raised is the object returned to the user.
  • errcheck could do a lot more! For example, it could call strerror() to obtain the error message.
  • It would have been better to at least set libc.fopen.restype to c_void_p, as fopen()/code> returns a pointer.

Structs

  • Structs are created by subclassing Structure and setting the _fields_ attribute:
    >>> class tm(Structure):
    ...   _fields_ = (('tm_sec', c_int), ('tm_min', c_int), ('tm_hour', c_int),
    ...     ('tm_day', c_int), ('tm_mon', c_int), ('tm_year', c_int),
    ...     ('tm_wday', c_int), ('tm_yday', c_int), ('tm_isdst', c_int))
    ...
    >>> t = c_int(); libc.time(byref(t))
    1185921393
    >>> libc.localtime.restype = POINTER(tm)
    >>> lt = libc.localtime(byref(t)).contents
    >>> "%d/%02d/%02d" % (lt.tm_year + 1900, lt.tm_mon, lt.tm_day)
    '2007/07/01'
    
  • Can define _fields_ after class definition if a struct must refer to itself; e.g. pointer to next item in linked list
  • Can work with unions by subclassing Union in a similar way

Callback Functions

  • Let's sort an array of ints using qsort()
  • qsort() requires a callback function to compare array items
  • First, create a type for the callback function:
    >>> cmpfunc = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
  • Define the comparison function with this type:
    >>> @cmpfunc
    ... def mycmp(a, b):
    ...   return cmp(a[0], b[0])
    ...
    >>> mycmp
    <CFunctionType object at 0x7ff7348c>
    

CFUNCTYPE is a factory function which creates types for C functions. The first argument is the result type and subsequent arguments are the types for the arguments expected by the function.

Callback Functions

  • Now let's use it:
    >>> ia = (c_int * 4)(3, 5, 2, 1)
    >>> [i for i in ia]
    [3, 5, 2, 1]
    >>> libc.qsort.restype = None
    >>> libc.qsort(ia, len(ia), sizeof(c_int), mycmp)
    >>> [i for i in ia]
    [1, 2, 3, 5]
    
  • CFUNCTYPE creates types for functions using the cdecl calling convention; WINFUNCTYPE is for the Windows stdcall calling convention
  • Must keep references to function objects as long as they are used from C code!

Failing to keep a reference to C function objects may result in them being garbage collected. This will cause a crash when a callback is made.

Real World Example: NVDA

  • NonVisual Desktop Access (NVDA)
  • A free, open-source screen reader for Microsoft Windows
  • http://www.nvda-project.org/
  • Was created by Michael Curran in 2006
  • Already rivals commercial screen readers which cost thousands in some areas
  • Uses ctypes and comtypes heavily to access Windows APIs
  • Uses ctypes callbacks to respond to Windows events