来自Python的C / C ++(CFFI,pybind11)

主要的

我们继续讨论如何从Python3调用C / C ++主题 。 现在我们使用cffipybind11库 。 在上一篇文章中讨论了通过ctypes的方法。


ç


一个测试库,用于演示如何使用带有各种类型参数的全局变量,结构和函数。
测试


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; }; 

测试


 #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; } 

该库与ctypes文章中的库完全相同。


CFFI


这是专门用于C语言的库 从该库的描述中:


与Python中几乎所有的C代码进行交互

其中一些几乎被发现。


对于实验, 使用1.12.3版本,您可以在此处阅读有关内容。


仅用 2个词就可以了解这个库, CFFI在我们的库顶部生成其绑定并将其编译为一个我们可以使用的库。


安装方式


pip3安装cffi


组装方式


构建脚本将收集我们库中的绑定。


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') 

巨蟒


一个通过Python通过CFFI使用C的 示例


 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)) 

要使用C ++代码,您需要为其编写C绑定。 有关通过ctypes方法的文章介绍了如何执行此操作。 链接下面。


CFFI的优缺点


优点


  • Python中使用时的简单语法
  • 无需重新编译源库

缺点


  • 组装不方便,您需要注册所有头文件和库的路径
  • 再创建1个动态库,该库使用原始库
  • 不支持以下指令:
     #ifdef __cplusplus extern "C" { #endif ... #ifdef __cplusplus } #endif 

     #ifndef _TEST_H_ #define _TEST_H_ ... #endif /* _TEST_H_ */ 

pybind11


相比之下, pybind11是专门为使用C ++设计的 。 实验使用2.3.0版,您可以在此处阅读有关内容。 她没有收集C源代码,所以我将它们翻译成C ++源代码。


安装方式


pip3安装pybind11


组装方式


我们需要为我们的库编写一个构建脚本。
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'} ) 

我们执行它:


 python3 setup.py build --build-lib=./lib 

C ++


在库源中,您需要添加:


  • pybind11头文件
     #include <pybind11/pybind11.h> 
  • 允许我们定义python模块的宏
     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); }; 

巨蟒


一个通过pybind11Python使用C的 示例


 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)) 

pybind11的优缺点


优点


  • Python中使用时的简单语法

缺点


  • 您需要编辑C ++源,或为它们编写绑定
  • 有必要从源头收集必要的库

每个方法从1000开始的平均测试执行时间为:


  • ctypes:-0.0004987692832946777秒-
  • CFFI:-0.00038521790504455566秒-
  • pybind:-0.0004547207355499268秒-

+,-,因为每次结果都略有不同。 另外,我花了很多时间在打印上,我懒得关闭它(把这个时间当作常数,因为在所有测试中都一样)。 但是,函数调用和从中获取结果仍然存在时间差异。


参考文献


Source: https://habr.com/ru/post/zh-CN468099/


All Articles