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实现处理管道的方法
Jun 04 Python
tensorflow实现softma识别MNIST
Mar 12 Python
Java实现的执行python脚本工具类示例【使用jython.jar】
Mar 29 Python
Python中collections模块的基本使用教程
Dec 07 Python
django 环境变量配置过程详解
Aug 06 Python
python运用sklearn实现KNN分类算法
Oct 16 Python
使用Python来做一个屏幕录制工具的操作代码
Jan 18 Python
python_array[0][0]与array[0,0]的区别详解
Feb 18 Python
使用pycharm和pylint检查python代码规范操作
Jun 09 Python
Python带参数的装饰器运行原理解析
Jun 09 Python
Python批量获取并保存手机号归属地和运营商的示例
Oct 09 Python
5 分钟读懂Python 中的 Hook 钩子函数
Dec 09 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编写PDF文档生成器
2006/10/09 PHP
php+js iframe实现上传头像界面无跳转
2014/04/29 PHP
thinkPHP框架实现多表查询的方法
2018/06/14 PHP
Laravel5.5 手动分页和自定义分页样式的简单实现
2019/10/15 PHP
通过PHP实现用户注册后邮箱验证激活
2020/11/10 PHP
判断多个input type=file是否有已经选择好文件的代码
2012/05/23 Javascript
JQuery 常用方法和事件详细介绍
2013/04/18 Javascript
zTree插件之多选下拉菜单实例代码
2013/11/06 Javascript
jquery的trigger和triggerHandler的区别示例介绍
2014/04/20 Javascript
JavaScript里四舍五入函数round用法实例
2015/04/06 Javascript
jQuery实现ajax调用WCF服务的方法(附带demo下载)
2015/12/04 Javascript
详解jQuery事件
2017/01/13 Javascript
Input文本框随着输入内容多少自动延伸的实现
2017/02/15 Javascript
基于bootstrap实现bootstrap中文网巨幕效果
2017/05/02 Javascript
妙用Angularjs实现表格按指定列排序
2017/06/23 Javascript
JS获取填报扩展单元格控件的值的解决办法
2017/07/14 Javascript
Vue学习笔记之表单输入控件绑定
2017/09/05 Javascript
vue+element-ui集成随机验证码+用户名+密码的form表单验证功能
2018/08/05 Javascript
jquery使用FormData实现异步上传文件
2018/10/25 jQuery
零基础之Node.js搭建API服务器的详解
2019/03/08 Javascript
layui表单验证select下拉框实现验证的方法
2019/09/05 Javascript
Vue环境搭建+VSCode+Win10的详细教程
2020/08/19 Javascript
从零学Python之入门(四)运算
2014/05/27 Python
基于Python 装饰器装饰类中的方法实例
2018/04/21 Python
Python qqbot 实现qq机器人的示例代码
2019/07/11 Python
python递归法实现简易连连看小游戏
2020/03/25 Python
tornado+celery的简单使用详解
2019/12/21 Python
matplotlib quiver箭图绘制案例
2020/04/17 Python
Pycharm编辑器功能之代码折叠效果的实现代码
2020/10/15 Python
Wilson体育用品官网:美国著名运动器材品牌
2019/05/12 全球购物
数控技术与应用毕业生自荐信
2013/09/24 职场文书
总经理办公室主任岗位职责
2013/11/12 职场文书
个人合伙协议书范本
2014/10/14 职场文书
复兴之路观后感
2015/06/02 职场文书
远程教育学习心得体会
2016/01/23 职场文书
《自然之道》读后感3篇
2019/12/17 职场文书