python模块与C和C++动态库相互调用实现过程示例


Posted in Python onNovember 02, 2021

Python调用C/C++

1、Python调用C动态链接库

Python调用C库比较简单,不经过任何封装打包成so,再使用python的ctypes调用即可。

C语言文件:pycall.c

# include "stdio.h"
#include "stdlib.h"
int foof(int a,  int b){
    printf("you input %d and %d",a,b);
    return a+b;
}

gcc编译生成动态库libpycall.so

# include "stdio.h"
#include "stdlib.h"
int foof(int a,  int b){
    printf("you input %d and %d",a,b);
    return a+b;
}

Python调用动态库的文件:pycall.py

import ctypes
ll = ctypes.cdll.LoadLibrary
lib = ll("Cfoo.so")
lib.foof(1,3)

运行结果:

python模块与C和C++动态库相互调用实现过程示例

2、Python调用C/C++原生态导出

Python解释器就是用C实现,因此只要我们的C++的数据结构能让Python认识,理论上就是可以被直接调用的。我们实现test1.cpp如下:

#include <Python.h> 
int Add(int x, int y) 
{ 
    return x + y; 
}   
int Del(int x, int y) 
{ 
    return x - y; 
}   
PyObject* WrappAdd(PyObject* self, PyObject* args) 
{ 
    int x, y; 
    if (!PyArg_ParseTuple(args, "ii", &x, &y)) 
    { 
        return NULL; 
    } 
    return Py_BuildValue("i", Add(x, y)); 
}   
PyObject* WrappDel(PyObject* self, PyObject* args) 
{ 
    int x, y; 
    if (!PyArg_ParseTuple(args, "ii", &x, &y)) 
    { 
        return NULL; 
    } 
    return Py_BuildValue("i", Del(x, y)); 
} 
static PyMethodDef test_methods[] = { 
    {"Add", WrappAdd, METH_VARARGS, "something"}, 
    {"Del", WrappDel, METH_VARARGS, "something"}, 
    {NULL, NULL} 
};  
extern "C"
void inittest1() 
{ 
    Py_InitModule("test1", test_methods);    
}

编译命令如下:

g++ -fPIC -shared test1.cpp -I/usr/include/python2.7 -o test1.so

-fPIC:生成位置无关目标代码,适用于动态连接;
-L path:表示在path目录中搜索库文件,如-L.表示在当前目录;
-I path:表示在path目录中搜索头文件;
-o file:制定输出文件为file;
-shared:生成一个共享库文件;

运行Python解释器,测试如下:

>>> import test1 
>>> test1.Add(1,2) 
3

这里要注意一下几点:

1.如果生成的动态库名字为test1,则源文件里必须有inittest1这个函数,且Py_InitModule的第一个参数必须是“test1”,否则Python导入模块会失败

2.如果是cpp源文件,inittest1函数必须用extern "C"修饰,如果是c源文件,则不需要。原因是Python解释器在导入库时会寻找initxxx这样的函数,而C和C++对函数符号的编码方式不同,C++在对函数符号进行编码时会考虑函数长度和参数类型,具体可以通过nm test1.so查看函数符号,c++filt工具可通过符号反解出函数原型

3.PyArg_ParseTuple()函数的定义:
arg参数必须是一个元组对象,它包含了从Python传递到C语言函数的参数列表。format参数必须是格式字符串, 具体将在下方解释.其余参数必须是变量的地址,其类型由格式字符串决定。为了能成功的转换,arg对象必须与格式匹配,并且前后一一对应。
虽然 PyArg_ParseTuple() 检查Python是否具有所需类型, 但是它不能检查传递给调用的C变量地址的有效性: 如果在那里出错,您的代码可能会崩溃,或者覆盖内存中的随机位置。所以要小心

字符串 备注
“s” (string or Unicode object) [char *] 将Python字符串或Unicode对象转换为指向字符串的C指针。不能为字符串本身提供存储;指向现有字符串的指针存储在您传递地址的字符指针变量中。C字符串以NULL结尾。Python字符串不能包含嵌入的空字节;如果有,就会引发TypeError异常。使用默认编码将Unicode对象转换为C字符串。如果转换失败,就会引发一个UnicodeError异常
“s#” (string,Unicode or any read buffer compatible object) [char *, int] s”上的这个变体存储在两个C变量中,第一个变量是指向字符串的指针,第二个变量是字符串的长度。在这种情况下,Python字符串可能包含嵌入的空字节。如果可能的话,Unicode对象会返回一个指向对象的默认编码字符串版本的指针。所有其他与读取缓冲区兼容的对象都将对原始内部数据表示的引用传回。
“z” (string or None) [char *] 与“s”类似,但是Python对象也可能是None,在这种情况下,C指针被设置为NULL。
“z#” (string or None or any read buffer compatible object) [char *, int] 这是"s#"就像"z"是"s"一样。
“u” (Unicode object) [Py_UNICODE *] 将Python Unicode对象转换为指向16位Unicode (UTF-16)数据的空端缓冲区的C指针。与“s”一样,不需要为Unicode数据缓冲区提供存储;指向现有Unicode数据的指针被存储到Py_UNICODE指针变量中,您传递的地址就是这个变量。
“u#” (Unicode object) [Py_UNICODE *, int] “u”上的这个变体存储在两个C变量中,第一个变量是指向Unicode数据缓冲区的指针,第二个变量是它的长度。
“es” (string,Unicode object or character buffer compatible object) [const char *encoding,char **buffer] 这个“s”的变体用于编码Unicode和转换为Unicode的对象到字符缓冲区。它只适用于不嵌入空字节的编码数据。

读取一个变体C变量C和商店为两个变量,第一个指针指向一个编码名称字符串(encoding),第二个一个指向指针的指针一个字符缓冲区(**buffer,缓冲用于存储编码数据)和第三个整数指针(*buffer_length,缓冲区长度)。
编码名称必须映射到已注册的编解码器。如果设置为NULL,则使用默认编码。

PyArg_ParseTuple()将使用PyMem_NEW()分配一个所需大小的缓冲区,将已编码的数据复制到这个缓冲区中,并调整*buffer以引用新分配的存储。调用方负责调用PyMem_Free()以在使用后释放分配的缓冲区。
编码名称必须映射到已注册的编解码器。如果设置为NULL,则使用默认编码。操作方式有两种:

如果buffer指向空指针,PyArg_ParseTuple()将使用PyMem_NEW()分配一个所需大小的缓冲区,将已编码的数据复制到这个缓冲区,并调整buffer以引用新分配的存储。调用方负责在使用后调用PyMem_Free()来释放分配的缓冲区。

如果buffer指向非空指针(已经分配的缓冲区),PyArg_ParseTuple()将使用这个位置作为缓冲区,并将buffer_length解释为缓冲区大小。然后,它将把编码后的数据复制到缓冲区中,并终止(0-terminate)它。缓冲区溢出以异常信号表示。

在这两种情况下,都将*buffer_length设置为编码数据的长度,没有后面的0字节(0-byte)。

字符串 备注
“b” (integer) [char] 将Python整数转换为存储在C语言char中的一个小int(tiny int)。
“h” (integer) [short int] 将Python整数转换为C语言short int。
“i” (integer) [int] 将Python整数转换为普通的C语言int。
“l” (integer) [long int] 将Python整数转换为C语言long int。
“c” (string of length 1) [char] 将长度为1的字符串表示的Python字符转换为C语言char。
“f” (float) [float] 将Python浮点数转换为C语言float。
“d” (float) [double] 将Python浮点数转换为C 语言double。
“D” (complex) [Py_complex] 将Python复数转换为C语言Py_complex结构。
“O” (object) [PyObject *] 在C对象指针中存储Python对象(不进行任何转换)。因此,C程序接收传递的实际对象。对象的引用计数没有增加。存储的指针不是空的(NULL)。
“O!” (object)[typeobject, PyObject *] 将Python对象存储在C对象指针中。这类似于“O”,但是接受两个C参数:第一个是Python类型对象的地址,第二个是对象指针存储在其中的C变量(类型为PyObject *)的地址。如果Python对象没有所需的类型,就会引发类型错误(TypeError)。
“O&” (object)[converter,anything] 通过转换器函数将Python对象转换为C变量。这需要两个参数:第一个是函数,第二个是C变量(任意类型)的地址,转换为void *。该转换器功能依次调用如下:status = converter(object,address);对象是要转换的Python对象,地址是传递给PyArg_ConvertTuple()的void *参数。对于成功的转换,返回的状态应该是1,如果转换失败,则返回0。当转换失败时,converter(函数名可能错误)函数应该引发异常。
“S” (string) [PyStringObject *] 与“O”类似,但要求Python对象是字符串对象。如果对象不是字符串对象,则引发类型错误(TypeError)。C变量也可以声明为PyObject *。
“U” (Unicode string) [PyUnicodeObject *] 与“O”类似,但要求Python对象是Unicode对象。如果对象不是Unicode对象,则引发类型错误(TypeError)。C变量也可以声明为PyObject *。
“t#” (read-only character buffer) [char *, int] 与“s#”类似,但接受任何实现只读缓冲区接口的对象。char *变量设置为指向缓冲区的第一个字节,int设置为缓冲区的长度。只接受单段缓冲对象;对所有其他类型都引发类型错误(TypeError)。
“w” (read-write character buffer) [char *] 类似于“s”,但接受任何实现读写缓冲区接口的对象。调用者必须通过其他方法确定缓冲区的长度,或者使用“w#”。只接受单段缓冲对象;对所有其他类型都引发类型错误(TypeError)。
“w#” (read-write character buffer) [char *, int] 与“s#”类似,但接受任何实现读写缓冲区接口的对象。char *变量设置为指向缓冲区的第一个字节,int设置为缓冲区的长度。只接受单段缓冲对象;对所有其他类型都引发类型错误(TypeError)。
“(items)” (tuple) [matching-items] 对象必须是一个Python序列,其长度是项中的格式单元数。C参数必须对应于项中的单个格式单元。序列的格式单元可以嵌套。

3、Python调用C/C++通过boost实现

我们使用和上面同样的例子,实现test2.cpp如下:

#include <boost/python/module.hpp> 
#include <boost/python/def.hpp> 
using namespace boost::python;  
int Add(const int x, const int y) 
{ 
    return x + y; 
} 
  
int Del(const int x, const int y) 
{ 
    return x - y; 
}  
BOOST_PYTHON_MODULE(test2) 
{ 
    def("Add", Add); 
    def("Del", Del); 
}

其中BOOST_PYTHON_MODULE的参数为要导出的模块名字,编译命令如下:

g++ test2.cpp -fPIC -shared -o test2.so -I/usr/include/python2.7 -I/usr/local/include -L/usr/local/lib -lboost_python

注意: 编译时需要指定boost头文件和库的路径,我这里分别是/usr/local/include和/usr/local/lib

或者通过setup.py导出模块:

#!/usr/bin/env python 
from distutils.core import setup 
from distutils.extension import Extension   
setup(name="PackageName", 
 ext_modules=[ 
  Extension("test2", ["test2.cpp"], 
  libraries = ["boost_python"]) 
 ])

Extension的第一个参数为模块名,第二个参数为文件名
执行如下命令:

python setup.py build

这时会生成build目录,找到里面的test2.so,并进入同一级目录,验证如下:

>>> import test2 
>>> test2.Add(1,2) 
3
>>> test2.Del(1,2) 
-1

4、Python调用C/C++通过导出类

test3.cpp实现如下:

#include <boost/python.hpp> 
using namespace boost::python;   
class Test 
{ 
    public: 
        int Add(const int x, const int y) 
        { 
            return x + y; 
        } 
 
        int Del(const int x, const int y) 
        { 
            return x - y; 
        } 
};   
BOOST_PYTHON_MODULE(test3) 
{ 
    class_<Test>("Test") 
        .def("Add", &Test::Add) 
        .def("Del", &Test::Del); 
}

注意:BOOST_PYTHON_MODULE里的.def使用方法有点类似Python的语法,等同于:

class_<Test>("Test").def("Add", &Test::Add); 
class_<Test>("Test").def("Del", &Test::Del);

编译命令如下:

g++ test3.cpp -fPIC -shared -o test3.so -I/usr/include/python2.7 -I/usr/local/include/boost -L/usr/local/lib -lboost_python

测试如下:

>>> import test3 
>>> test = test3.Test() 
>>> test.Add(1,2) 
3
>>> test.Del(1,2) 
-1

5、Python调用C/C++通过导出变参函数

test4.cpp实现如下:

#include <boost/python.hpp> 
using namespace boost::python; 
  
class Test 
{ 
    public: 
        int Add(const int x, const int y, const int z = 100) 
        { 
        return x + y + z; 
        } 
}; 
  
int Del(const int x, const int y, const int z = 100) 
{ 
    return x - y - z; 
} 
  
BOOST_PYTHON_MEMBER_FUNCTION_OVERLOADS(Add_member_overloads, Add, 2, 3) 
BOOST_PYTHON_FUNCTION_OVERLOADS(Del_overloads, Del, 2, 3) 
  
BOOST_PYTHON_MODULE(test4) 
{ 
    class_<Test>("Test") 
        .def("Add", &Test::Add, Add_member_overloads(args("x", "y", "z"), "something")); 
    def("Del", Del, Del_overloads(args("x", "y", "z"), "something")); 
    }

这里Add和Del函数均采用了默认参数,Del为普通函数,Add为类成员函数,这里分别调用了不同的宏,宏的最后两个参数分别代表函数的最少参数个数和最多参数个数
编译命令如下:

g++ test4.cpp -fPIC -shared -o test4.so -I/usr/include/python2.7 -I/usr/local/include/boost -L/usr/local/lib -lboost_python

测试如下:

>>> import test4 
>>> test = test4.Test() 
>>> print test.Add(1,2) 
103
>>> print test.Add(1,2,z=3) 
6
>>> print test4.Del(1,2) 
-1
>>> print test4.Del(1,2,z=3) 
-1

6、Python调用C/C++通过导出带Python对象的接口

既然是导出为Python接口,调用者难免会使用Python特有的数据结构,比如tuple,list,dict,由于原生态方法太麻烦,这里只记录boost的使用方法,假设要实现如下的Python函数功能:

def Square(list_a) 
{ 
    return [x * x for x in list_a] 
}
``
`
即对传入的list每个元素计算平方,返回list类型的结果,代码如下:

```cpp
#include <boost/python.hpp> 
  
boost::python::list Square(boost::python::list& data) 
{ 
    boost::python::list ret; 
    for (int i = 0; i < len(data); ++i) 
    { 
        ret.append(data[i] * data[i]); 
    } 
 
    return ret; 
} 
BOOST_PYTHON_MODULE(test5) 
{ 
    def("Square", Square);  
}

编译命令如下

g++ test5.cpp -fPIC -shared -o test5.so -I/usr/include/python2.7 -I/usr/local/include/boost -L/usr/local/lib -lboost_python

测试如下:

>>> import test5 
>>> test5.Square([1,2,3]) 
[1, 4, 9]

boost实现了boost::python::tuple, boost::python::list, boost::python::dict这几个数据类型,使用方法基本和Python保持一致,具体方法可以查看boost头文件里的boost/python/tuple.hpp及其它对应文件
另外比较常用的一个函数是boost::python::make_tuple() ,使用方法如下

boost::python::tuple(int a, int b, int c) 
{  
    return boost::python::make_tuple(a, b, c); 
}

以上就是python模块与C和C++动态库相互调用实现过程示例的详细内容,更多关于python模块与C和C++动态库相互调用的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
python中Flask框架简单入门实例
Mar 21 Python
Python中用于计算对数的log()方法
May 15 Python
python select.select模块通信全过程解析
Sep 20 Python
Python 12306抢火车票脚本 Python京东抢手机脚本
Feb 06 Python
详解Django的model查询操作与查询性能优化
Oct 16 Python
使用Python获取并处理IP的类型及格式方法
Nov 01 Python
PyQt5 QTable插入图片并动态更新的实例
Jun 18 Python
对python中assert、isinstance的用法详解
Nov 27 Python
python3 BeautifulSoup模块使用字典的方法抓取a标签内的数据示例
Nov 28 Python
详解如何在PyCharm控制台中输出彩色文字和背景
Aug 17 Python
python 实现非极大值抑制算法(Non-maximum suppression, NMS)
Oct 15 Python
python 实用工具状态机transitions
Nov 21 Python
Qt自定义Plot实现曲线绘制的详细过程
Nov 02 #Python
Python 正则模块详情
Nov 02 #Python
Python 数据可视化之Bokeh详解
Nov 02 #Python
Python 数据可视化之Matplotlib详解
分位数回归模型quantile regeression应用详解及示例教程
Python常遇到的错误和异常
Nov 02 #Python
Python 数据可视化之Seaborn详解
You might like
PHP一些常用的正则表达式字符的一些转换
2008/07/29 PHP
php下尝试使用GraphicsMagick的缩略图功能
2011/01/01 PHP
基于php split()函数的用法详解
2013/06/05 PHP
解析php中获取系统信息的方法
2013/06/25 PHP
javascript 出生日期和身份证判断大全
2008/11/13 Javascript
图片上传插件jquery.uploadify详解
2013/11/15 Javascript
JS获取当前网址、主机地址项目根路径
2013/11/19 Javascript
非常棒的jQuery图片轮播效果
2016/04/17 Javascript
vue项目中api接口管理总结
2018/04/20 Javascript
Angular6 发送手机验证码按钮倒计时效果实现方法
2019/01/08 Javascript
layui数据表格 table.render 报错的解决方法
2019/09/29 Javascript
vue项目在webpack2实现移动端字体自适配功能
2020/06/02 Javascript
js实现头像上传并且可预览提交
2020/12/25 Javascript
python初学之用户登录的实现过程(实例讲解)
2017/12/23 Python
pandas 使用apply同时处理两列数据的方法
2018/04/20 Python
在windows下Python打印彩色字体的方法
2018/05/15 Python
python2与python3爬虫中get与post对比解析
2019/09/18 Python
Django框架下静态模板的继承操作示例
2019/11/08 Python
利用Tensorboard绘制网络识别准确率和loss曲线实例
2020/02/15 Python
pyqt5 QlistView列表显示的实现示例
2020/03/24 Python
keras 回调函数Callbacks 断点ModelCheckpoint教程
2020/06/18 Python
纯CSS3实现绘制各种图形实现代码详细整理
2012/12/26 HTML / CSS
金蝶的一道SQL笔试题
2012/12/18 面试题
写给保洁员表扬信
2014/01/08 职场文书
先进德育工作者事迹材料
2014/01/24 职场文书
公务员试用期满考核材料
2014/05/22 职场文书
农村党建工作汇报材料
2014/10/27 职场文书
国际贸易实务实训报告
2014/11/05 职场文书
邀请函模板
2015/02/02 职场文书
新学期开学寄语2016
2015/12/04 职场文书
《唯一的听众》教学反思
2016/02/18 职场文书
如何使用pdb进行Python调试
2021/06/30 Python
Go语言基础知识点介绍
2021/07/04 Golang
Go中的条件语句Switch示例详解
2021/08/23 Golang
Sql Server之数据类型详解
2022/02/28 SQL Server
分析SQL窗口函数之取值窗口函数
2022/04/21 Oracle