NiceLib¶
NiceLib is a package for rapidly developing “nice” Python bindings to C libraries, using cffi
.
It lets you turn a C function like this
int my_c_function(int arg1, float arg2, uint* out_arr, int out_arr_size, int reserved);
into a Python function that can be called like this
out_arr = my_c_function(arg1, arg2)
just by defining a signature like this
class MyLib(NiceLib):
...
my_c_function = Sig('in', 'in', 'arr', 'len', 'ignore')
while giving you easy error handling and more.
Installing¶
NiceLib is available on PyPI:
$ pip install nicelib
If you would like to use the development version, download and extract a zip of the source from our GitHub page or clone it using git. Now install:
$ cd /path/to/NiceLib
$ pip install .
Feedback / Contributing¶
For contributing, reporting issues, and providing feedback, see our GitHub page. Feedback is greatly appreciated!
Introduction¶
NiceLib essentially consists of two layers:
Tools for directly using C headers to generate an importable Python module
An interface for quickly and cleanly defining a Pythonic mid-level binding
Mid-level means that functions take input arguments, return output arguments, and raise Exceptions on error. NiceLib tries to simplify commonly-seen C idioms, like using user-created buffers for returning output strings. Take, for example, this toy binding for the NIDAQmx library:
from nicelib import load_lib, NiceLib, Sig
class NiceNI(NiceLib):
_info_ = load_lib('ni')
_prefix_ = 'DAQmx'
GetSysDevNames = Sig('buf', 'len')
Now, we can simply call NiceNI.GetSysDevName(code)
, which is equivalent to the following:
lib_info = load_lib('ni')
ffi, lib = lib_info._ffi, lib_info.lib
def GetSysDevNames():
buflen = 512
buf = ffi.new('char[]', buflen)
ret = lib.DAQmxGetSysDevNames(buf, buflen)
return ffi.string(buf), ret
Many of our C library’s functions may use status codes as their return value. To automatically check the return code and raise an exception if warranted, we can use _ret_
:
from nicelib import RetHandler
# Define a new return handler
@RetHandler(num_retvals=0)
def daq_errorcheck(ret):
if ret != 0:
raise DAQError(ret)
class NiceNI(NiceLib):
_ret_ = daq_errorcheck
[...]
Often a C library will have method-like functions, each of which take a handle as its first argument. These translate naturally into Python objects, obviating you of the need to pass the handle all the time. For example, here’s a partial definition of a Task object, again wrapping NIDAQmx:
class NiceNI(NiceLib):
[...]
CreateTask = Sig('in', 'out')
class Task(NiceObject):
_init_ = 'CreateTask'
StartTask = Sig('in')
ReadAnalogScalarF64 = Sig('in', 'in', 'out', 'ignore')
For both of these function signatures, the first 'in'
argument is the Task handle. We can then use the class to make and use Task
objects:
task = NiceNI.Task('myTask')
task.StartTask()
By specifying _init_
when defining Task
, the string 'myTask'
is passed to NiceNI.CreateTask
, whose return value (a task handle) is stored in the Task
instance.
Automatically Processing Headers¶
The awesome cffi
package greatly improves wrapping C libraries in Python by allowing you to load header files directly, instead of manually churning out mind-numbing ctypes
boilerplate. However it’s not perfect—in particular, it makes a fairly limited attempt at preprocessing header files, meaning that in many cases you’ll have to preprocess them yourself, either manually or by running e.g. the gcc preprocessor. NiceLib provides preprocessing facilities that aim to allow you to use unmodified header files to generate a “compiled” ffi module, without requiring a C compiler.
NiceLib’s preprocessor has basic support for both object-like and simple function-like macros, which it translates into equivalent 1 portable Python source code. For example
#define CONST_VAL (1 << 4) | (1 << 1)
#define LTZ(val) ((val) < 0)
becomes the Python code:
CONST_VAL = (1 << 4) | (1 << 1)
def LTZ(val):
return (val) < 0
The preprocessor also supports conditionals (#ifdef
and friends), #include
s, and
platform-specific predefined macros (like __linux__
, __WIN64
, and __x86_64
).
Currently, #pragma once
is supported, but other #pragma
directives are ignored.
- 1
Note that, due to the nature of the C preprocessor, this generated code cannot always be truly equivalent. However, in the overwhelming majority of cases, the macros defined in library header files are quite simple.