一、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/C++_#include

二、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++_python_02

三、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/C++_python_03

四、扩展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之间数据转换的转换代码:

Python调用C/C++_#include_04


为每个模块增加一个型如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文件。

Python调用C/C++_Python_05

若需指定参数编译可运行以下命令,查看其帮助文档:

.\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

Python调用C/C++_#include_06

  • 工程设置(Project->Properties->VC++ Directories)
  • Include Diretorise加上Boost根目录
  • Include Diretorise加上Python的include目录
  • Library Diretoties加上boost编译出来的lib目录
  • Library Diretoties加上Python的libs目录

Python调用C/C++_Python_07

Python调用C/C++_python_08

可参考:​​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 ,则采用动态编译的库

Python调用C/C++_#include_09

 

#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)

 

参考

1、​​Python与C/C++的混合调用​​ 2、Python调用C++程序的几种方法
3、Python调用windows下DLL详解 - ctypes库的使用
4、​​​python3 C++扩展​

5、​​python3没有commands模块了​

6、Windows下为Python编译C扩展模块
7、​​​Boost Getting Started on Windows​​​ 
8、​​​Installing Boost.Python on your System​

9、​​Boost Exposing Classes​​​

10、​​Boost.Python C++导出基本用法​​​

11、​​C++和Python的混合编程-Boost::python的编译和配置​

​​ 12、Boost::Python的安装与调用教程
13、​​​boost github​​​

14、​​官网Boost.Python​​​

15、vs2017编译boost库 ,解决无法打开文件“libboost_filesystem-vc140-mt-1_58.lib” 问题
16、BOOST的AUTO link机制以及配置
17、Boost库解密——自动链接库(auto_link)
18、C++ Boost库的编译及使用