NiceLib is a package for rapidly developing “nice” Python bindings to C libraries, using
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.
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!
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
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 = NiceNI.Task('myTask') task.StartTask()
_init_ when defining
Task, the string
'myTask' is passed to
NiceNI.CreateTask, whose return value (a task handle) is stored in the
Automatically Processing Headers¶
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),
platform-specific predefined macros (like
#pragma once is supported, but other
#pragma directives are ignored.
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.
- Creating Low-Level Bindings
- Creating Mid-Level Bindings
- API Documentation