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

时间:2021-08-07 12:57:42

Python调用C/C++

1、Python调用C动态链接库

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

C语言文件:pycall.c

?
1
2
3
4
5
6
# 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

?
1
2
3
4
5
6
# 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

?
1
2
3
4
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如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#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);   
}

编译命令如下:

?
1
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解释器,测试如下:

?
1
2
3
>>> 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如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#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的参数为要导出的模块名字,编译命令如下:

?
1
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导出模块:

?
1
2
3
4
5
6
7
8
#!/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的第一个参数为模块名,第二个参数为文件名
执行如下命令:

?
1
python setup.py build

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

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

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

test3.cpp实现如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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的语法,等同于:

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

编译命令如下:

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

测试如下:

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

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

test4.cpp实现如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#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为类成员函数,这里分别调用了不同的宏,宏的最后两个参数分别代表函数的最少参数个数和最多参数个数
编译命令如下:

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

测试如下:

?
1
2
3
4
5
6
7
8
9
10
>>> 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函数功能:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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); 
}

编译命令如下

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

测试如下:

?
1
2
3
>>> 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() ,使用方法如下

?
1
2
3
4
boost::python::tuple(int a, int b, int c)
    return boost::python::make_tuple(a, b, c);
}

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

原文链接:https://blog.csdn.net/weixin_38582851/article/details/121036465