Python3调用C/C++动态库(使用SWIG)
Posted Fu_Lin_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python3调用C/C++动态库(使用SWIG)相关的知识,希望对你有一定的参考价值。
欢迎大家关注我的公众号,有更多精彩等着大家挖掘:
文章目录
前言
我之前写过一篇python3 ctypes模块使用方法与心得体会— int* ,char*等指针类型互转,如何转换对应的参数如,int*,char*等。
今天发现如果使用C++的STL相关参数,无解啊~, 如下面提供的导出函数:
int parse(std::vector<char> & dump, std::vector<unsigned char> & dumpinfo);
看到要用Python 传参 std::vector & 给C++接口,一阵头大,因为Ctypes已经完全不能满足这个需求了,查边全网,发现都是无解,要么就是自己定义一套基于ctypes的 std::vector * 方法来和python结合使用,这样太low,因为python传过去的只能是 void *, 然后各种中间转换,导出,麻烦至极,后面发现原来 swig 这个库,完全可以无缝调用C++的所有接口参数,在不改变原动态库的代码下,我打算自己再写一套基于swig编译的c++ 动态库,这个动态库就去加载调用我上面的C++的导出parse函数,等于就是包装了一层,因为so别人已经提供好了,你不能再让别人又下载基于swig的方式编译出so文件,所以只能自己中间编译一套swig的C++动态库来两方调用,下面看具体swig的具体使用,可以说完全比ctypes 好用太多了,因为不需要涉及到类型的转换,就和python内部使用一致!
下载安装SWIG 和 PCRE
网上有直接 apt -get install swig 安装的,不过我这边是使用的源码安装,更加方便自己的版本控制!
Linux源码下载地址
SWIG官网下载地址页面
如果上面的地址打开缓慢,或者打不开,可以使用中国开源软件地址,如下:
sourceforge PCRE 全部版本
sourceforge SWIG 全部版本
下载最新的即可,不过看上面官网说是swig4.0.2对应pcre8.44, 我想8.45最新版也没啥问题的,不过我是通过官网地址下载的8.44
Linux安装 pcre 到用户指定目录
tar -jxvf pcre-8.44.tar.bz2
chmod -R 777 pcre-8.44
cd pcre-8.44/
./configure --prefix=/usr/local/pcre/
make
sudo make install
安装好了以后,手动添加软链接
ln -s /usr/local/pcre/lib/libpcre.so /lib/libpcre.so
ln -s /usr/local/pcre/lib/libpcre.so.1 /lib/libpcre.so.1
手动添加bin路径到profile文件
sudo vim /etc/profile
在最后添加一行:
PATH=/usr/local/pcre/bin:$PATH
保存后重新加载生效:
source /etc/profile
提示:
如果没有指定目录参数 –prefix 可以不用手动添加软件接以及bin路径,否则安装swig就会报错,如果报错不要慌,执行swig的
make clean
然后重新手动添加pcre的链接和路径位置,重新 configure 即可
Linux安装swig 到用户指定目录
tar -zxvf swig-4.0.2.tar.gz
cd swig-4.0.2
./configure --prefix=/usr/local/swig
make
sudo make install
如果pcre安装成功后,执行上面命令安装成功会提示如下
然后还是一样,手动添加bin路径到profile文件中去
打开profile文件
sudo vim /etc/profile
在最后添加一行:
PATH=/usr/local/swig/bin:$PATH
保存后重新加载生效:
source /etc/profile
关键步骤查看swig版本
成功与否就看这个命令了!查看swig版本
swig -version
如果发现不生效,直接将swig的bin目录加入到PATH变量中去
win系统的SWIG下载地址,免PCRE
swig官网地址
打开网址如果觉得慢,或者无响应。直接通过迅雷下载该链接(赋值连接,打开迅雷自动识别下载,秒下载好!)
下载地址:https://udomain.dl.sourceforge.net/project/swig/swigwin/swigwin-4.0.2/swigwin-4.0.2.zip
win系统版本安装解压缩swigwin-4.0.2.zip
会有个 swig.exe 文件,说明下载的zip是正确的
win系统swig配置环境变量
为了让路径不出现中文,防止意外情况,我直接复制到D盘的根目录下,然后添加系统环境如下:
win系统swig检测安装结果
允许Cmd命令,输入swig --help,如下图
说明win系统的swig安装成功!!!
SWIG的使用
swig介绍
SWIG是 Simplified Wrapper and Interface Generator 的缩写,可以用来给C/C++程序生成脚本语言的接口,这样就可以使用脚本语言去调用C/C++程序。
本文主要讲述使用SWIG给C++程序生成python3接口。
C++类导出示例讲解
首先编写一个简单的 类word
word.h如下:
#ifndef __WORD_H__
#define __WORD_H__
#include <string>
class word
{
public:
word(std::string content);
virtual ~word();
virtual void updateWord(std::string new_content);
std::string getWord(void);
private:
std::string m_content;
};
#endif
word.cpp如下:
#include "word.h"
word::word(std::string content) : m_content(content)
{
}
word::~word()
{}
void word::updateWord(std::string new_content)
{
m_content = new_content;
}
std::string word::getWord(void)
{
return m_content;
}
然后编写 swig 的输入文件 example.i, 关键所在~ (打通C++和Python之间的桥梁文件)
/*--- File: example.i ---*/
%module example
%{
#include "word.h"
%}
%include "std_string.i"
%include "word.h"
下面是简单解释:
- 第一行 是注释,表示文件名字
- 第二行 表示模块名字叫 example,后面使用python调用模块时就是import这个模块名
- %{…%} 里包含的是要被wrap的C/C++函数、类或者变量的声明,也可以使用头文件,因为头文件里一般都是声明,这部分内容会被直接拷贝到swig生成的wrap文件里
- 最后2行 的%include是swig的专有指令,std_string.i 是用来处理C++的string类,而出现的word.h,是用来告诉swig对word.h里声明的内容进行分析并生成对应的wrapper
以上三个文件放在同一目录下:(仅仅演示Linux系统的,生成和执行命令在Win上也是一样的)
Linux系统文件如下:
然后在当前文件夹下打开 CMD 或者 PowerShell 输入以下命令:
swig -c++ -python example.i
会生成 example.py 和 example_wrap.cxx,如下:
然后输入以下个命令:
linux下:
g++ -fpic -c example_wrap.cxx word.cpp -I/usr/local/python3/include/python3.6m
如果是在 win系统下
g++ -fpic -c example_wrap.cxx word.cpp -ID:\\Python37\\include
切记D:\\Python37\\include路径中间不能有空格,否则会报错
其中的 -I 是为了引入 Python.h ,这个文件可以通过 sudo find / -iname “Python.h” 来进行查找,注意如果想以后用python2去调用C++程序,那么就要引入python2的Python.h,这里引入的是python3的Python.h。
接着输入下面的命令,生成动态库:
g++ -shared example_wrap.o word.o -o _example.so
最终生成的文件如下
在该目录下使用python3按照如下操作进行测试:
测试OK。经过上述操作后,实际有用的文件是 _example.so和example.py,
可以把这2个文件单独拷贝到别的路径下,然后再进行之前的python3测试,也是OK的。
测试C导出函数
我准备在word中新增加C导出函数看怎么样?
word.h中:
word.cpp
然后重新执行上面的命令
swig -c++ -python example.i
g++ -fpic -c example_wrap.cxx word.cpp -I/usr/local/python3/include/python3.6m
g++ -shared example_wrap.o word.o -o _example.so
在该目录下使用python3按照如下操作进行测试:
可以看到,通过引用的模块,也是可以直接调用导出的C方法,太nice了!!!而且不需要任何的变量转换,但是方法不能直接调用,看上面也知道报错了,必须通过导入的模块 example.xxx 来调用。
C++处理函数参数
C函数参数就不用多说了,全是基础类型,而C++函数参数有很多种,所以下面分3个情况分别进行分析
C/C++常规类型
常规类型如 int,double,char 等等,比较简单,传参数直接传值就可以了(常规类型是适用于所有C导出函数的,上面也测试了~)。我们给class word增加一个方法 testFunc(),其参数有int,double,char。
word.h如下,注意引入了iostream:
#ifndef WORD_H
#define WORD_H
#include <string>
#include <iostream>
extern "C"{
void foobar(int i);
}
class word
{
public:
word(std::string content);
virtual ~word();
virtual void updateWord(std::string new_content);
std::string getWord(void);
int testFunc(int data1, double data2, char data3); // new added
private:
std::string m_content;
};
#endif // WORD_H
word.cpp 如下,
#include "word.h"
word::word(std::string content) : m_content(content)
{
}
word::~word()
{}
void word::updateWord(std::string new_content)
{
m_content = new_content;
}
std::string word::getWord(void)
{
return m_content;
}
void foobar(int i)
{
printf("Printing from mylib.so %d\\n", i);
}
int word::testFunc(int data1, double data2, char data3)
{
std::cout << "data1: " << data1 << "\\n";
std::cout << "data2: " << data2 << "\\n";
std::cout << "data3: " << data3 << "\\n";
return 100;
}
example.i 内容如下:
/*File: example.i*/
%module example
%{
#include "word.h"
%}
%include "std_string.i"
%include "std_iostream.i"
%include "word.h"
按照上一节的步骤进行操作生成wrapper,然后输入终端下输入python3去测试,
引用类型
对于引用类型的参数,该怎么办呢?假设要wrapp的类如下:
word.h如下: 新增testReference 方法
class word
{
public:
word(std::string content);
virtual ~word();
virtual void updateWord(std::string new_content);
std::string getWord(void);
int testFunc(int data1, double data2, char data3);
void testReference(int& data);// new added
private:
std::string m_content;
};
word.cpp如下,
void word::testReference(int& data)
{
data = 100;
}
example.i 的内容如下,
/*File: example.i*/
%module example
%include typemaps.i
%apply int &OUTPUT {int &};
%{
#include "word.h"
%}
%include "std_string.i"
%include "std_iostream.i"
%include "word.h"
%include typemaps.i 是引入swig的类型映射,%apply int &OUTPUT { int &}; 是用来处理int的引用类型。
然后执行之前的编译语句:
swig -c++ -python example.i
g++ -fpic -c example_wrap.cxx word.cpp -I/usr/local/python3/include/python3.6m
g++ -shared example_wrap.o word.o -o _example.so"
ok后,编写python脚本test.py(注意,test.py是要跟examle.py和_example.so在同一目录下),如下
import example
obj = example.word("test")
ret = obj.testReference()
print('ret is {}'.format(ret))
然后执行 python3 test.py,得到运行结果:
可以看到引用类型的值作为函数返回值返回了,这样就可以拿到其值了。
细心的同学可能发现test.py里调用testReference()时没有传递参数,这是为什么呢?
回顾下testReference()的c++实现,对于data这个引用类型变量,我们只是对其赋值,并没有操作其原本的值,意思也就是data只作为输出使用:赋值后,testReference()的调用者就可以拿到data的新值。这样在test.py里调用testReference()时就不需要传递参数了,只需要接受其返回值就行了。
这也是example.i 里 %apply int &OUTPUT { int &}; 语句中OUTPUT的由来。
那如果我们在c++函数里要操作引用类型的变量值怎么办(这是很普遍的用法),如下:
void word::testReference(int& data)
{
data += 100;
}
data自加100
那么 example.i 的写法就需要改改:
/*File: example.i*/
%module example
%include typemaps.i
%apply int &INOUT { int &};
%{
#include "word.h"
%}
%include "std_string.i"
%include "std_iostream.i"
%include "word.h"
由之前的OUTPUT改为INOUT,因为我们既要使用data的原始值,又要获取函数调用后的data值,那么data就既有输出又有输入功能了。
写好 example.i 后再次运行前面的编译命令。
test.py脚本如下:
import example
obj = example.word("test")
data = 70
ret = obj.testReference(data)
print('ret is {}'.format(ret))
这个时候就需要传参数了。运行如下:
如果函数的返回类型不是void怎么办,那么该返回哪个呢?假设testReference()如下:
int testReference(int& data);
word.cpp实现如下:
int word::testReference(int& data)
{
data += 100;
return 500;
}
example.i 不变,test.py里修改打印,
import example
obj = example.word("test")
data = 70
ret = obj.testReference(data)
print(ret)
最后执行 python3 test.py,结果如下:
可以看到swig使用了python的list很好的处理了这种情况。
本小结以int举例,其它类型也是类似的
指针类型
有了上节对引用的了解,这节对于指针就很容易理解了,只是把&换成*。
这里就只讲一个例子,假设要wrap的c++类如下,还是使用上面的例子,新增testPointer方法:
word.h如下
class word
{
public:
word(std::string content);
virtual ~word();
virtual void updateWord(std::string new_content);
std::string getWord(void);
int testFunc(int data1, double data2, char data3);
int testPointer(int* data);// new added
private:
std::string m_content;
};
word.cpp如下,
int word::testPointer(int* data)
{
*data += 100;
return 500;
}
example.i 的写法如下(只是把&换成了*):
/*File: example.i*/
%module example
%include typemaps.i
%apply int *INOUT { int *};
%{
#include "word.h"
%}
%include "std_string.i"
%include "std_iostream.i"
%include "word.h"
执行之前的三行编译命令,ok后编写python脚本test.py,如下
import example
obj = example.word("test")
data = 200
ret = obj.testPointer(data)
print(ret)
然后执行python3 test.py,结果如下:
本小节以int举例,其它类型也是类似的。
处理STL
这里只讲下std::string和std::vector,其它的可以去swig官网教程去查看或者谷歌查查。
std::string
其实前面的例子也讲到过 std::string 的swig处理,这里就再重新简单讲下,重新设计word类如下:
word.h代码如下:
#ifndef WORD_H
#define WORD_H
#include <string>
class word
{
public:
word();
std::string testFunc(std::string& data);
};
#endif // WORD_H
word.cpp代码如下:
#include "word.h"
word::word()
{
}
std::string word::testFunc(std::string& data)
{
return data + "123";
}
example.i 内容如下:
/*--- File: example.i ---*/
%module example
%include "std_string.i"
%apply std::string &INOUT {std::string&}
%{
#include "word.h"
%}
%include "word.h"
这里使用了**%include std_string.i去处理std::string**,将其转为python的字符串类型。如果参数不是引用,那么就不需要 %apply std::string &INOUT {std::string&} 这句话了。
执行之前的三行编译命令,ok后编写python脚本test.py,如下,
import example
obj = example.word()
ret = obj.testFunc("abc")
print(ret)
执行python3 test.py,输出如下:
std::vector
vector是个模板,实例化时需要指定类型,这里以int和unsigned int举例,
word.h如下
#include <vector>
class word
{
public:
word();
~word();
int testFunc(const std::vector<int>& data1, const std::vector<unsigned int>& data2);
};
word.cpp如下
word::word()
{
// TODO: constructor
}
word::~word()
{
// TODO: destructor
}
int word::testFunc(const std::vector<int>& data1, const std::vector<unsigned int>& data2)
{
if (data1.size() == 3)
return data1[0]+data2[0]+data1[1]+data2[1]+data1[2]+data2[2];
return 0;
}
example.i 内容如下,
/*--- File: example.i ---*/
%module example
%{
#include "word.h"
%}
%include "std_vector.i"
namespace std {
%template(VectorInt) vector<int>;
%template(VectorUInt32) vector<unsigned int>;
}
%include "word.h"
这里使用了 %include “std_vector.i” ,用来处理std::vector,另外对需要用到的 vector 类型进行模板处理,用到哪种就要声明哪种,本例程使用了int和unsigned int。
执行之前的三行编译命令后,编写测试脚本test.py如下,
import example
obj = example.word()
#data1 = [1,2,3]
#data2 = CMake、SWIG 和共享库