Python中调用C/C++程序
在Python中调用C/C++代码有两种方式:一种是将C/C++代码编译成动态链接库,并打包成Python模块,在Python中通过import导入模块,调用其中的函数;另一种是将C/C++代码编译成动态链接库,在Python中通过ctypes.cdll.LoadLibrary()
直接加载该动态链接库,调用其中的函数。
将C/C++代码打包成Python模块
安装swig
- 安装pcre
-
下载pcre源码
-
解压缩
$ tar xzvf pcre2-10.36.tar.gz
-
安装
$ cd pcre2-10.36 $ ./configure --prefix=/usr/local $ make $ make install
-
- 安装swig
-
下载swig源码
-
解压缩
$ tar -xvf swig-4.0.2.tar
-
安装
$ cd swig-4.0.2 $ ./configure --prefix=/usr/local/swig $ make $ sudo make install
-
编写C/C++程序
-
factorial.c
int factorial(int n) { if (n < 0) { return -1; } if (n == 0) { return 1; } return n * factorial(n-1); }
-
factorial.h
int factorial(int n);
-
factorial.i
%module basic %{ #include "factorial.h" %} %include "factorial.h";
在
%{%}
中包含相关的头文件。
封装代码
$ swig -python factorial.i
执行该命令后,会生成factorial_wrap.c
文件。如果是c++程序,则增加-c++
选项。
生成动态链接库文件
- 编写setup.py文件
from distutils.core import setup, Extension factorial_module = Extension('_basic', # Module name, '_' is necessary sources = ['factorial_wrap.c', # Wrapped interface file 'factorial.c' ] ) setup(name = 'basic', # Package name version = '1.0', author = 'Thinker', description = 'Compute factorial', ext_modules = [factorial_module], py_modules = ['basic'] # List of modules needed to be wrapped
- 生成动态链接库
其中,$ sudo python setup.py build_ext install
build_ext
表示生成c/c++的扩展模块,install
表示将生成的.py文件和.so动态链接库拷贝到Python的第三方模块目录(如/Library/Python/2.7/site-packages
)中,并在该目录中生成.egg-info
文件。如果将install
替换为--inplace
选项,则会在源程序目录下生成.py文件和.so动态链接库。
在Python中调用C/C++函数
import basic
basic.factorial(10)
在Python中加载c/c++动态链接库
-
编写C/C++程序
factorial.cint factorial(int n) { if (n < 0) { return -1; } if (n == 0) { return 1; } return n * factorial(n-1); }
-
编译生成动态链接库文件
$ gcc -o factorial.so -shared -fPIC factorial.c
-
在Python中调用
from ctypes import * basic = cdll.LoadLibrary('/Users/wisdom/program/basic/factorial.so') basic.factorial(5)
在macOS中,须给出动态链接库文件的绝对路径,在Linux中只需给出相对路径。
Python与C/C++程序间传递图像
-
编写C++程序
showimage.cpp#include "showimage.h" using namespace cv; extern "C" { /* * Abstract: Convert data to image and show it * Parameters: width, height: width and height of image * data: Pointer of the image data */ void show(int width, int height, uchar* data) { Mat image(height, width, CV_8UC3); uchar * pimage; int index = 0; for (int row = 0; row < height; row++) { // Assign row pointer pimage = image.ptr<uchar>(row); for (int col = 0; col < width; col++) { for (int channel = 0; channel < 3; channel++) { // Assign data to image pimage[3*col+channel] = data[index++]; } } } imshow("Image", image); waitKey(0); } }
-
编写C++程序头文件
showimage.h#include <opencv2/opencv.hpp> extern "C" { void show(int width, int height, uchar* data); }
-
编译生成动态链接库
$ gcc -o image.so -shared -fPIC showimage.cpp -lopencv_core -lopencv_highgui
-
编写Python程序
import cv2 import ctypes from ctypes import cdll import numpy as np # Read image image = cv2.imread('test.jpg') image_data = np.asarray(image, dtype=np.uint8) # Convert data to one dimension char array of C language and get the pointer of the array image_data = image_data.ctypes.data_as(ctypes.c_char_p) # Load C/C++ dynamic library image_fun = cdll.LoadLibrary("./image.so") # Show image by C++ function image_fun.show(image.shape[1], image.shape[0], image_data)
-
执行Python程序
$ python3 showimage.py
执行后,Python程序会读入图像,将其转换成C类型的字符数组,然后加载C语言动态链接库,将字符数组的指针及图像宽、高传给C语言的
show()
函数。show()
函数会将传入的字符数组转换成图像,并显示出来。
问题及解决
- 问题:在macOS系统中,通过swig将c语言程序打包成python模块,在python中import时,出现错误提示
ImportError: dlopen(./_basic.so, 2): no suitable image found. Did find: file system relative paths not allowed in hardened programs
解决方法:这主要是由于Python的第三方模块目录中没有_basic.so
和basic.py
文件。在sudo python setup.py build_ext
命令中加入install
选项,以将生成的.py文件和.so动态链接库拷到Python的第三方模块目录中。同时,应避免当前目录也有该.py文件和动态链接库文件,否则会出现查找错误的情况。