The final article in the series on how to call C / C ++ from Python3 went through all the known ways to do this. This time I got to boost . What came of this read below.
C
I take the same example of a test library as a basis and make it a variation for a specific method. A test library to demonstrate working with global variables, structures, and functions with arguments of various types.
test.c:
#include "test.hpp" 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; } object func_ret_str(char *val) { printf("C get func_ret_str: %s\n", val); return object(string(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; }
test.h:
using namespace boost::python; using namespace std; #ifdef __cplusplus extern "C" { #endif typedef struct test_st_s test_st_t; typedef char * char_p; extern int a; extern double b; extern char c; int func_ret_int(int val); double func_ret_double(double val); object 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; }; #ifdef __cplusplus } #endif
How to compile:
g++ -g -fPIC -I/usr/include/python3.6 -I./src/c -o ./objs/test.o -c ./src/c/test.cpp g++ -fPIC -g -shared -o ./lib/_test.so ./objs/test.o -lboost_python3
The source compiles into a dynamic library.
python boost is similar in use to pybind11 , you also need to describe the functions that python will see. But in my opinion boost is more bulky and complex. For example:
def("func_ret_struct", &func_ret_struct, return_value_policy<reference_existing_object>());
The func_ret_struct function takes a pointer to a structure as an argument and returns the same pointer back. For it, you need to specify the rules of the returned object return_value_policy <reference_existing_object> () . reference_existing_objec says that the returned object already existed. If you specify manage_new_object, it will mean that we are returning a new object. In this case, such a script will fall into a segmentation fault on the garbage collector:
test_st = _test.test_st_t() ret = _test.func_ret_struct(test_st)
Because the garbage collector will first clear the data that test_st contains, and then wants to clear the data that the ret object contains. Which contains the same data that test_st contained, but it has already been cleared.
It is interesting how in this case to describe such a function (did not go deep) ?:
test_st_t * func_ret_struct(test_st_t *test_st) { if (test_st) { return test_st; } else { return (test_st_t *) malloc(sizeof(test_st_t)); } }
Such a function can return both an existing object and an existing one.
I also had a problem with such a function:
char * func_ret_str(char *val) { return val; }
As I understand it, it is impossible to get a pointer to the standard data type from boost in python. It is possible only on struct , class and union . If anyone knows a way to enlighten.
Python
For python, the module becomes native.
main.py:
Pros and cons of boost
Pros :
- simple syntax when used in Python
Cons :
- you need to edit C ++ sources, or write a binding for them
- boost alone is not easy
I try to comment the code as usual clearly.
The average test execution time on each method with 1000 starts:
- ctypes: - 0.0004987692832946777 seconds ---
- CFFI: - 0.00038521790504455566 seconds ---
- pybind: - 0.0004547207355499268 seconds ---
- C API: - 0.0003561973571777344 seconds ---
- boost: - 0.00037789344787597656 seconds ---
References