We continue the topic of how to call C / C ++ from Python3 . Now we use the cffi , pybind11 libraries . The method through ctypes was discussed in a previous article.
A test library to demonstrate working with global variables, structures, and functions with arguments of various types.
test.h
typedef struct test_st_s test_st_t; extern int a; extern double b; extern char c; int func_ret_int(int val); double func_ret_double(double val); char *func_ret_str(char *val); char func_many_args(int val1, double val2, char val3, short val4); test_st_t *func_ret_struct(test_st_t *test_st); struct test_st_s { int val1; double val2; char val3; };
test.c
#include <stdio.h> #include <stdlib.h> #include "test.h" int a = 5; double b = 5.12345; char c = 'X'; int func_ret_int(int val) { printf("C get func_ret_int: %d\n", val); return val; } double func_ret_double(double val) { printf("C get func_ret_double: %f\n", val); return val; } char * func_ret_str(char *val) { printf("C get func_ret_str: %s\n", val); return val; } char func_many_args(int val1, double val2, char val3, short val4) { printf("C get func_many_args: int - %d, double - %f, char - %c, short - %d\n", val1, val2, val3, val4); return val3; } test_st_t * func_ret_struct(test_st_t *test_st) { if (test_st) { printf("C get test_st: val1 - %d, val2 - %f, val3 - %c\n", test_st->val1, test_st->val2, test_st->val3); } return test_st; }
The library is exactly the same as in the ctypes article.
This is a library for working exclusively with C. From the description of this library:
Interact with almost any C code from Python
Some of this was almost found.
For the experiment, version 1.12.3 was used , you can read about it here .
A little about this library in 2 words, CFFI generates its binding on top of our library and compiles it into a library with which we will work.
pip3 install cffi
The build script that will collect the binding around our library.
build.py
import os import cffi if __name__ == "__main__": ffi = cffi.FFI() # PATH = os.getcwd() # test.h # build.py with open(os.path.join(PATH, "src/c/test.h")) as f: ffi.cdef(f.read()) ffi.set_source("_test", # cffi, _ # test.h, _test '#include "../src/c/test.h"', # libtest.so ( ) # _test.cpython-36m-x86_64-linux-gnu.so ( CFFI) libraries=[os.path.join(PATH, "lib/test"), "./test"], library_dirs=[PATH, 'objs/'], ) # _test lib ffi.compile(tmpdir='./lib')
An example of working with C from Python through CFFI :
from cffi import FFI import sys import time # _test sys.path.append('.') sys.path.append('lib/') sys.path.append('../../lib/') # import _test ### ## C ### print("CFFI\n") print("C\n") start_time = time.time() ## # ## print(' :') print('ret func_ret_int: ', _test.lib.func_ret_int(101)) print('ret func_ret_double: ', _test.lib.func_ret_double(12.123456789)) # cdata , . print('ret func_ret_str: ', _test.ffi.string(_test.lib.func_ret_str('Hello!'.encode('utf-8'))).decode("utf-8")) print('ret func_many_args: ', _test.lib.func_many_args(15, 18.1617, 'X'.encode('utf-8'), 32000).decode("utf-8")) ## # ## print('\n :') print('ret a: ', _test.lib.a) # . _test.lib.a = 22 print('new a: ', _test.lib.a) print('ret b: ', _test.lib.b) print('ret c: ', _test.lib.c.decode("utf-8")) ## # ## print('\n :') # test_st = _test.ffi.new("test_st_t *") test_st.val1 = 5 test_st.val2 = 5.1234567 test_st.val3 = 'Z'.encode('utf-8') ret = _test.lib.func_ret_struct(test_st) # C print('ret val1 = {}\nret val2 = {}\nret val3 = {}'.format(ret.val1, ret.val2, ret.val3.decode("utf-8"))) # print("--- %s seconds ---" % (time.time() - start_time))
To work with C ++ code, you need to write a C binding for it. The article about the method through ctypes describes how to do this. Link below.
Pros :
Cons :
#ifdef __cplusplus extern "C" { #endif ... #ifdef __cplusplus } #endif
#ifndef _TEST_H_ #define _TEST_H_ ... #endif /* _TEST_H_ */
pybind11, by contrast, is designed specifically for working with C ++ . Version 2.3.0 was used for the experiment, you can read about it here . She does not collect C sources, so I translated them into C ++ sources.
pip3 install pybind11
We need to write a build script for our library.
build.py
import pybind11 from distutils.core import setup, Extension ext_modules = [ Extension( '_test', # pybind11 ['src/c/test.cpp'], # include_dirs=[pybind11.get_include()], # pybind11 language='c++', # extra_compile_args=['-std=c++11'], # ++11 ), ] setup( name='_test', # pybind11 version='1.0.0', author='djvu', author_email='djvu@inbox.ru', description='pybind11 extension', ext_modules=ext_modules, requires=['pybind11'], # pybind11 package_dir = {'': 'lib'} )
We execute it:
python3 setup.py build --build-lib=./lib
In the library source you need to add:
#include <pybind11/pybind11.h>
PYBIND11_MODULE(_test, m)
namespace py = pybind11; // _test PYBIND11_MODULE(_test, m) { /* * */ m.def("func_ret_int", &func_ret_int); m.def("func_ret_double", &func_ret_double); m.def("func_ret_str", &func_ret_str); m.def("func_many_args", &func_many_args); m.def("func_ret_struct", &func_ret_struct); /* * */ m.attr("a") = a; m.attr("b") = b; m.attr("c") = c; /* * */ py::class_<test_st_t>(m, "test_st_t") .def(py::init()) // . , Python // C, C++ ( C ++ ) .def_readwrite("val1", &test_st_t::val1) // .def_readwrite("val2", &test_st_t::val2) .def_readwrite("val3", &test_st_t::val3); };
An example of working with C from Python via pybind11 :
import sys import time # _test sys.path.append('lib/') # import _test ### ## C ### print("pybind11\n") print("C\n") start_time = time.time() ## # ## print(' :') print('ret func_ret_int: ', _test.func_ret_int(101)) print('ret func_ret_double: ', _test.func_ret_double(12.123456789)) # cdata . print('ret func_ret_str: ', _test.func_ret_str('Hello!'.encode('utf-8'))) print('ret func_many_args: ', _test.func_many_args(15, 18.1617, 'X'.encode('utf-8'), 32000)) ## # ## print('\n :') print('ret a: ', _test.a) # . _test.a = 22 print('new a: ', _test.a) print('ret b: ', _test.b) print('ret c: ', _test.c) ## # ## print('\n :') # _test_st = _test.test_st_t() #print(dir(_test_st)) _test_st.val1 = 5 _test_st.val2 = 5.1234567 _test_st.val3 = 'Z'.encode('utf-8') ret = _test.func_ret_struct(_test_st) # C print('ret val1 = {}\nret val2 = {}\nret val3 = {}'.format(ret.val1, ret.val2, ret.val3)) # print("--- %s seconds ---" % (time.time() - start_time))
Pros :
Cons :
+, - because the results were slightly different each time. Plus, time was spent printing, which I was too lazy to turn off (take this time as a constant, because it will be ~ the same in all tests). But still, there is a time difference in the function calls and obtaining the results from them.