The C language combines all the power of assembly language with all the ease-of-use of assembly language.
%cat add.h
int add(int a, int b);
%cat add.c
#include "add.h" int add(int a, int b) { return a + b; }
class AddTest(unittest.TestCase):
def test_addition(self):
module = load('add')
self.assertEqual(module.add(1, 2), 1 + 2)
run(AddTest)
test_addition (__main__.AddTest) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.131s OK
def load(filename):
# load source code
source = open(filename + '.c').read()
includes = open(filename + '.h').read()
# pass source code to CFFI
ffibuilder = cffi.FFI()
ffibuilder.cdef(includes)
ffibuilder.set_source(filename + '_', source)
ffibuilder.compile()
# import and return resulting module
module = importlib.import_module(filename + '_')
return module.lib
%cat sum.h
int sum(int a);
%cat sum.c
#include "sum.h" static int _sum = 0; int sum(int a) { _sum += a; return _sum; }
class SumTest(unittest.TestCase):
def setUp(self):
self.module = load('sum')
def test_zero(self):
self.assertEqual(self.module.sum(0), 0)
def test_one(self):
self.assertEqual(self.module.sum(1), 1)
def test_multiple(self):
self.assertEqual(self.module.sum(2), 2)
self.assertEqual(self.module.sum(4), 2 + 4)
run(SumTest)
test_multiple (__main__.SumTest) ... ok test_one (__main__.SumTest) ... ok test_zero (__main__.SumTest) ... ok ---------------------------------------------------------------------- Ran 3 tests in 0.263s OK
def load(filename):
source = open(filename + '.c').read()
includes = open(filename + '.h').read()
ffibuilder = cffi.FFI()
ffibuilder.cdef(includes)
ffibuilder.set_source(filename + '_', source)
ffibuilder.compile()
module = importlib.import_module(filename + '_')
return module.lib
def load(filename):
# generate random name
name = filename + '_' + uuid.uuid4().hex
source = open(filename + '.c').read()
includes = open(filename + '.h').read()
ffibuilder = cffi.FFI()
ffibuilder.cdef(includes)
ffibuilder.set_source(name, source)
ffibuilder.compile()
module = importlib.import_module(name)
return module.lib
%cat types.h
typedef struct { float real; float imaginary; } complex;
%cat complex.h
#include "types.h" complex add(complex a, complex b);
%cat complex.c
#include "complex.h" complex add(complex a, complex b) { a.real += b.real; a.imaginary += b.imaginary; return a; }
class ComplexTest(unittest.TestCase):
def setUp(self):
self.module = load('complex')
def test_addition(self):
result = self.module.add([0, 1], [2, 3])
self.assertEqual(result.real, 2)
self.assertEqual(result.imaginary, 4)
run(ComplexTest)
test_addition (__main__.ComplexTest) ... ok ---------------------------------------------------------------------- Ran 1 test in 0.105s OK
def load(filename):
name = filename + '_' + uuid.uuid4().hex
source = open(filename + '.c').read()
includes = open(filename + '.h').read()
ffibuilder = cffi.FFI()
ffibuilder.cdef(includes)
ffibuilder.set_source(name, source)
ffibuilder.compile()
module = importlib.import_module(name)
return module.lib
def load(filename):
name = filename + '_' + uuid.uuid4().hex
source = open(filename + '.c').read()
# handle preprocessor directives
includes = preprocess(open(filename + '.h').read())
ffibuilder = cffi.FFI()
ffibuilder.cdef(includes)
ffibuilder.set_source(name, source)
ffibuilder.compile()
module = importlib.import_module(name)
return module.lib
def preprocess(source):
return subprocess.run(['gcc', '-E', '-P', '-'],
input=source, stdout=subprocess.PIPE,
universal_newlines=True, check=True).stdout
%cat gpio_lib.h
int read_gpio0(void); int read_gpio1(void);
%cat gpio.h
int read_gpio(int number);
%cat gpio.c
#include "gpio.h" #include "gpio_lib.h" int read_gpio(int number) { switch (number) { case 0: return read_gpio0(); case 1: return read_gpio1(); default: return -1; } }
class GPIOTest(unittest.TestCase):
def setUp(self):
self.module, self.ffi = load('gpio')
def test_read_gpio0(self):
@self.ffi.def_extern()
def read_gpio0():
return 42
self.assertEqual(self.module.read_gpio(0), 42)
def test_read_gpio1(self):
read_gpio1 = unittest.mock.MagicMock(return_value=21)
self.ffi.def_extern('read_gpio1')(read_gpio1)
self.assertEqual(self.module.read_gpio(1), 21)
read_gpio1.assert_called_once_with()
run(GPIOTest)
test_read_gpio0 (__main__.GPIOTest) ... ok test_read_gpio1 (__main__.GPIOTest) ... ok ---------------------------------------------------------------------- Ran 2 tests in 0.222s OK
def load(filename):
name = filename + '_' + uuid.uuid4().hex
source = open(filename + '.c').read()
includes = preprocess(open(filename + '.h').read())
ffibuilder = cffi.FFI()
ffibuilder.cdef(includes)
ffibuilder.set_source(name, source)
ffibuilder.compile()
module = importlib.import_module(name)
return module.lib
def load(filename):
name = filename + '_' + uuid.uuid4().hex
source = open(filename + '.c').read()
# preprocess all header files for CFFI
includes = preprocess(''.join(re.findall('\s*#include\s+.*', source)))
# prefix external functions with extern "Python+C"
local_functions = FunctionList(preprocess(source)).funcs
includes = convert_function_declarations(includes, local_functions)
ffibuilder = cffi.FFI()
ffibuilder.cdef(includes)
ffibuilder.set_source(name, source)
ffibuilder.compile()
module = importlib.import_module(name)
# return both the library object and the ffi object
return module.lib, module.ffi
class FunctionList(pycparser.c_ast.NodeVisitor):
def __init__(self, source):
self.funcs = set()
self.visit(pycparser.CParser().parse(source))
def visit_FuncDef(self, node):
self.funcs.add(node.decl.name)
class CFFIGenerator(pycparser.c_generator.CGenerator):
def __init__(self, blacklist):
super().__init__()
self.blacklist = blacklist
def visit_Decl(self, n, *args, **kwargs):
result = super().visit_Decl(n, *args, **kwargs)
if isinstance(n.type, pycparser.c_ast.FuncDecl):
if n.name not in self.blacklist:
return 'extern "Python+C" ' + result
return result
def convert_function_declarations(source, blacklist):
return CFFIGenerator(blacklist).visit(pycparser.CParser().parse(source))