一、Python调用C动态链接库
Python调用C库比较简单,不经过任何封装打包成so,再使用python的ctypes调用即可。
(1)C语言文件:pycall.c
/***gcc -o libpycall.so -shared -fPIC pycall.c*/ #include <stdio.h> #include <stdlib.h> int foo(int a, int b) { printf("you input %d and %d\n", a, b); return a+b; }
(2)gcc编译生成动态库libpycall.so:gcc -o libpycall.so -shared -fPIC pycall.c。使用g++编译生成C动态库的代码中的函数或者方法时,需要使用extern "C"来进行编译。
(3)Python调用动态库的文件:pycall.py
import ctypes ll = ctypes.cdll.LoadLibrary lib = ll("./libpycall.so") lib.foo(1, 3) print('***finish***')
补充说明:
stdcall调用约定:两种加载方式
Objdll = ctypes.windll.LoadLibrary("dllpath") Objdll = ctypes.WinDLL("dllpath")
cdecl调用约定:也有两种加载方式
Objdll = ctypes.cdll.LoadLibrary("dllpath") Objdll = ctypes.CDLL("dllpath") #其实windll和cdll分别是WinDLL类和CDll类的对象。
(4)运行结果:
二、Python调用C++(类)动态链接库
需要extern "C"来辅助,也就是说还是只能调用C函数,不能直接调用方法,但是能解析C++方法。不是用extern “C”,构建后的动态链接库没有这些函数的符号表。
(1)C++类文件:pycallclass.cpp
#include <iostream> using namespace std; class TestLib { public: void display(); void display(int a); }; void TestLib::display() { cout<<"First display"<<endl; } void TestLib::display(int a) { cout<<"Second display:"<<a<<endl; } extern "C" { TestLib obj; void display() { obj.display(); } void display_int() { obj.display(2); } }
(2)g++编译生成动态库libpycall.so:g++ -o libpycallclass.so -shared -fPIC pycallclass.cpp。
(3)Python调用动态库的文件:pycallclass.py
import ctypes def fun1(): # lib = ctypes.cdll.LoadLibrary("/mnt/hgfs/share/cspace/libpycallclass.so") lib = ctypes.CDLL("/mnt/hgfs/share/cspace/libpycallclass.so") print(lib.display()) print(lib.display_int(100))
(4)运行结果:
三、Python调用C/C++可执行程序
1)C/C++程序:main.cpp
#include <iostream> using namespace std; int test() { int a = 10, b = 5; return a+b; } int main() { cout<<"---begin---"<<endl; int num = test(); cout<<"num="<<num<<endl; cout<<"---end---"<<endl; }
(2)编译成二进制可执行文件:g++ -o testmain main.cpp。
(3)Python调用程序:main.py
import subprocess import os main = "./testmain" if os.path.exists(main): rc, out = subprocess.getstatusoutput(main) print('rc = %d, \nout = %s' % (rc, out)) print('*'*10) f = os.popen(main) data = f.readlines() f.close() print(data) print('*'*10) os.system(main)
(4)运行结果:
四、扩展Python(C++为Python编写扩展模块)
所有能被整合或导入到其它python脚本的代码,都可以被称为扩展。可以用Python来写扩展,也可以用C和C++之类的编译型的语言来写扩展。Python在设计之初就考虑到要让模块的导入机制足够抽象。抽象到让使用模块的代码无法了解到模块的具体实现细节。Python的可扩展性具有的优点:方便为语言增加新功能、具有可定制性、代码可以实现复用等。
为 Python 创建扩展需要三个主要的步骤:创建应用程序代码、利用样板来包装代码和编译与测试。
(1)创建应用程序代码和样板代码
#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} }; static struct PyModuleDef SpamModule = { PyModuleDef_HEAD_INIT, "t2", //name of module NULL, //module documentation, may be NULL -1, //size of per-interpreter state of the module, //or -1 if the module keeps state in global variables test_methods }; //初始化模块 //PyInit_t2名称 必须跟 t2 库文件名称关联 //可以如下测试: //import spam //dir(spam) PyMODINIT_FUNC PyInit_t2(void) { return PyModule_Create(&SpamModule); }
接口的代码被称为“样板”代码,它是应用程序代码与Python解释器之间进行交互所必不可少的一部分。样板主要分为4步:a、包含Python的头文件;b、为每个模块的每一个函数增加一个型如PyObject* Module_func()的包装函数;c、为每个模块增加一个型如PyMethodDef ModuleMethods[]的数组;d、增加模块初始化函数PyMODINIT_FUNC。
Python.h头文件在大多数类Unix系统中会在/usr/local/include/python2.x或/usr/include/python2.x目录中,系统一般都会知道文件安装的路径。
增加包装函数,所在模块名为Extest,那么创建一个包装函数叫Extest_fac(),在Python脚本中使用是先import Extest,然后调用Extest.fac(),当Extest.fac()被调用时,包装函数Extest_fac()会被调用,包装函数接受一个 Python的整数参数,把它转为C的整数,然后调用C的fac()函数,得到一个整型的返回值,最后把这个返回值转为Python的整型数做为整个函数调用的结果返回回去。其他两个包装函数Extest_doppel()和Extest_test()类似。
从Python到C的转换用PyArg_Parse系列函数,int PyArg_ParseTuple():把Python传过来的参数转为C;int PyArg_ParseTupleAndKeywords()与PyArg_ParseTuple()作用相同,但是同时解析关键字参数;它们的用法跟C的sscanf函数很像,都接受一个字符串流,并根据一个指定的格式字符串进行解析,把结果放入到相应的指针所指的变量中去,它们的返回值为1表示解析成功,返回值为0表示失败。从C到Python的转换函数是PyObject Py_BuildValue():把C的数据转为Python的一个对象或一组对象,然后返回之;Py_BuildValue的用法跟sprintf很像,把所有的参数按格式字符串所指定的格式转换成一个Python的对象。
C与Python之间数据转换的转换代码:
为每个模块增加一个型如PyMethodDef ModuleMethods[]的数组,以便于Python解释器能够导入并调用它们,每一个数组都包含了函数在Python中的名字,相应的包装函数的名字以及一个METH_VARARGS常量,METH_VARARGS表示参数以tuple形式传入。 若需要使用PyArg_ParseTupleAndKeywords()函数来分析命名参数的话,还需要让这个标志常量与METH_KEYWORDS常量进行逻辑与运算常量 。数组最后用两个NULL来表示函数信息列表的结束。
所有工作的最后一部分就是模块的初始化函数,以便于解释器能正确的调用模块中的函数。
(2)编译
g++ -fPIC -shared t2.cpp -I/home/liuqz/anaconda3/include/python3.8 -o t2.so
-fPIC:生成位置无关目标代码,适用于动态连接;
-L path:表示在path目录中搜索库文件,如-L.表示在当前目录;
-I path:表示在path目录中搜索头文件;
-o file:制定输出文件为file;
-shared:生成一个共享库文件;
把生成的so放到import能找到的目录(一般放在C:\Python34\DLLs\中)。
备注如果是window下,需要把dll重命名t2.pyd.
运行Python解释器,测试如下
import t2 def run_t2(): print(t2.Add(1, 12)) print(t2.Del(10, 5))
五、Boost::python编写C++扩展模块
5.1 编译boost::python库
5.1.1 下载源码
到boost官网下载boost源码,解压到想放到的位置,例如: E:\Learning\Boost\boost_1_69_0
5.1.2 编译boost的lib库
如何系统中只有一个VS,并且Python已加入环境变量,可以切换到boost源码路径下,简单执行以下命令进行编译:
.\bootstrap.bat .\b2.exe --with-python
可参考:Boost Getting Started on Windows , Installing Boost.Python on your System
其默认会在boost的stage文件夹下生成静态lib文件。
若需指定参数编译可运行以下命令,查看其帮助文档:
.\b2.exe --help
可参考:vs2017编译boost库 ,解决无法打开文件“libboost_filesystem-vc140-mt-1_58.lib” 问题,C++和Python的混合编程-Boost::python的编译和配置
5.2 VS项目配置
- VS2015建立名为Boost_Python_Sample的Win32的dll工程
- VS2015也可建立一个Win32 Console Applicantion,然后在VS->Project-
- >Properties::Genneral::Configuration Type里改成dll
工程设置(Project->Properties->VC++ Directories)
- Include Diretorise加上Boost根目录
- Include Diretorise加上Python的include目录
- Library Diretoties加上boost编译出来的lib目录
- Library Diretoties加上Python的libs目录
可参考:BOOST的AUTO link机制以及配置,Boost库解密——自动链接库(auto_link),C++和Python的混合编程-Boost::python的编译和配置
5.3 源码
(1)因为使用的是静态编译的boost::python库,所以在include头文件之前要加上BOOST_PYTHON_STATIC_LIB,因为在boost::python库的config.hpp中规定,如没定义
BOOST_PYTHON_STATIC_LIB ,则采用动态编译的库
#ifdef BOOST_PYTHON_STATIC_LIB # define BOOST_PYTHON_STATIC_LINK # elif !defined(BOOST_PYTHON_DYNAMIC_LIB) # define BOOST_PYTHON_DYNAMIC_LIB #endif
(2)示例代码
#define BOOST_PYTHON_STATIC_LIB #include <boost/python.hpp> #include <iostream> #include <string> using namespace std; using namespace boost::python; struct World { World(std::string msg) : msg(msg) {} // added constructor void set(std::string msg) { this->msg = msg; } std::string greet() { return msg; } std::string msg; }; BOOST_PYTHON_MODULE(bp) { class_<World>("World", init<std::string>()) //class_<World>("World") .def("greet", &World::greet) .def("set", &World::set) ; }
可参考:C++和Python的混合编程-Boost::python的编译和配置,Boost Exposing Classes,Boost.Python C++导出基本用法
(3)Python_Test_Sample为导出的模块dll,可直接将输出dll文件改成bp.pyd(和BOOST_PYTHON_MODULE(bp)中的bp保持一致),可将pyd文件拷贝到python的库目录下
(python —>lib —>site-packages),或者命令行直接进入pyd所在的目录。
(4)执行Python
新建bp.py,然后执行它,或者命令行执行:
import bp if __name__ == '__main__': planet = bp.World('rt') s = planet.greet() print(s) planet.set('howdy') s = planet.greet() print(s)
参考
3、Python调用windows下DLL详解 - ctypes库的使用
7、Boost Getting Started on Windows
8、Installing Boost.Python on your System
11、C++和Python的混合编程-Boost::python的编译和配置
13、boost github
15、vs2017编译boost库 ,解决无法打开文件“libboost_filesystem-vc140-mt-1_58.lib” 问题