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之print详解
Sep 28 Python
Python实现基于HTTP文件传输实例
Nov 08 Python
修改Python的pyxmpp2中的主循环使其提高性能
Apr 24 Python
python使用pil进行图像处理(等比例压缩、裁剪)实例代码
Dec 11 Python
python利用socketserver实现并发套接字功能
Jan 26 Python
Python统计纯文本文件中英文单词出现个数的方法总结【测试可用】
Jul 25 Python
在IPython中进行Python程序执行时间的测量方法
Nov 01 Python
Python的条件锁与事件共享详解
Sep 12 Python
Python测试Kafka集群(pykafka)实例
Dec 23 Python
Python定时任务APScheduler原理及实例解析
May 30 Python
python时间time模块处理大全
Oct 25 Python
Python包管理工具pip的15 个使用小技巧
May 17 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
Laravel 5框架学习之Blade 简介
2015/04/08 PHP
Laravel 5 框架入门(四)完结篇
2015/04/09 PHP
Laravel中10个有用的用法小结
2019/05/06 PHP
PHP+redis实现的限制抢购防止商品超发功能详解
2019/09/19 PHP
php实现将数组或对象写入到文件的方法小结【三种方法】
2020/04/22 PHP
基于jquery的合并table相同单元格的插件(精简版)
2011/04/05 Javascript
javascript利用控件对windows的操作实现原理与应用
2012/12/23 Javascript
javascript中onclick(this)用法介绍
2013/04/19 Javascript
如何阻止复制剪切和粘贴事件为了表单内容的安全
2013/05/23 Javascript
jquery实现炫酷的叠加层自动切换特效
2015/02/01 Javascript
jQuery源码解读之addClass()方法分析
2015/02/20 Javascript
javascript实现仿腾讯游戏选择
2015/05/14 Javascript
JavaScript使用位运算符判断奇数和偶数的方法
2015/06/01 Javascript
JavaScript中数组的合并以及排序实现示例
2015/10/24 Javascript
如何处理JSON中的特殊字符
2016/11/30 Javascript
常用jQuery选择器汇总
2017/02/02 Javascript
完美解决input[type=number]无法显示非数字字符的问题
2017/02/28 Javascript
微信小程序之swiper滑动面板用法示例
2018/12/04 Javascript
python实现图片批量剪切示例
2014/03/25 Python
python数字图像处理之骨架提取与分水岭算法
2018/04/27 Python
python实现图书馆研习室自动预约功能
2018/04/27 Python
在django中自定义字段Field详解
2019/12/03 Python
Python如何批量生成和调用变量
2020/11/21 Python
如何强制垃圾回收
2015/10/06 面试题
销售人员自我评价
2014/02/01 职场文书
音乐教学反思
2014/02/02 职场文书
爱岗敬业演讲稿
2014/05/05 职场文书
日语专业求职信
2014/07/04 职场文书
2014年小学教学工作总结
2014/11/13 职场文书
2014年工作总结与下年工作计划
2014/11/27 职场文书
2014年政协委员工作总结
2014/12/01 职场文书
小区环境卫生倡议书
2015/04/29 职场文书
师范生教育见习总结
2015/06/23 职场文书
2015年国庆节新闻稿
2015/07/18 职场文书
2015年法律事务部工作总结
2015/07/27 职场文书
如何理解Vue简单状态管理之store模式
2021/05/15 Vue.js