python与C/C++相互调用

https://www.zhihu.com/question/23003213 知乎详细总结
http://www.voidcn.com/article/p-wmqbbket-bdm.html 实例
http://www.voidcn.com/article/p-zgwjtool-bdh.html 实例

C/C++调用python

python作为一种胶水语言可以很灵活的嵌入到C++和java等主语言里面进行互操作实现扩展功能。

方法1:使用python提供的C接口(基础)

使用python提供给C/C++的API,将python程序编程文本形式的动态链接库,可以热更新,非常方便。

API介绍

以下是一些API的介绍:

void Py_Initialize(void)

初始化Python解释器,如果初始化失败,继续下面的调用会出现各种错误,可惜的是此函数没有返回值来判断是否初始化成功,如果失败会导致致命错误。

int Py_IsInitialized(void)

检查是否已经进行了初始化,如果返回0,表示没有进行过初始化。

void Py_Finalize()

反初始化Python解释器,包括子解释器,调用此函数同时会释放Python解释器所占用的资源。

int PyRun_SimpleString(const char *command)

实际上是一个宏,执行一段Python代码。

PyObject* PyImport_ImportModule(char *name)

导入一个Python模块,参数name可以是*.py文件的文件名。类似Python内建函数import。

PyObject* PyModule_GetDict( PyObject *module)

相当于Python模块对象的dict属性,得到模块名称空间下的字典对象。

PyObject* PyRun_String(const char* str, int start,PyObject* globals, PyObject* locals)

执行一段Python代码。

int PyArg_Parse(PyObject* args, char* format, …)

把Python数据类型解析为C的类型,这样C程序中才可以使用Python里面的数据。

PyObject* PyObject_GetAttrString(PyObject o, charattr_name)

返回模块对象o中的attr_name 属性或函数,相当于Python中表达式语句,o.attr_name。

PyObject* Py_BuildValue(char* format, …)

和PyArg_Parse刚好相反,构建一个参数列表,把C类型转换为Python对象,使得Python里面可以使用C类型数据。

PyObject* PyEval_CallObject(PyObject* pfunc, PyObject*pargs)

此函数有两个参数,而且都是Python对象指针,其中pfunc是要调用的Python 函数,一般说来可以使用PyObject_GetAttrString()获得,pargs是函数的参数列表,通常是使用Py_BuildValue()来构建。

C++向Python传递参数

C++向Python传参数是以元组(tuple)的方式传过去的,因此我们实际上就是构造一个合适的Python元组就可以了,要用到PyTuple_New,Py_BuildValue,PyTuple_SetItem等几个函数,其中Py_BuildValue可以有其它一些的替换函数。

   PyObject* pyParams = PyTuple_New(2);

   PyObject* pyParams1= Py_BuildValue("i",5);

   PyObject* pyParams2= Py_BuildValue("i",6);

   PyTuple_SetItem(pyParams,0, pyParams1);

   PyTuple_SetItem(pyParams,1, pyParams2);

   pRet = PyEval_CallObject(pFunc, pyParams);

也可以直接使用PyObject* Py_BuildValue(char *format, …) 函数来直接来构造tuple,此函数的使用也很简单,记住一些转换的格式常量即可轻松进行转换(格式常量有点类似printf)。譬如s 表示字符串,i表示整型变量,f表示浮点数,o表示一个Python对象等等。

Py_BuildValue("")                       None

Py_BuildValue("i",123)                 123

Py_BuildValue("iii",123, 456, 789)     (123, 456, 789)

Py_BuildValue("s","hello")             'hello'

Py_BuildValue("ss","hello", "world")    ('hello', 'world')

Py_BuildValue("s#","hello", 4)         'hell'

Py_BuildValue("()")                     ()

Py_BuildValue("(i)",123)               (123,)

Py_BuildValue("(ii)",123, 456)         (123, 456)

Py_BuildValue("(i,i)",123, 456)        (123, 456)

Py_BuildValue("[i,i]",123, 456)        [123, 456]

Py_BuildValue("{s:i,s:i}",

             "abc", 123, "def", 456)    {'abc': 123, 'def': 456}

Py_BuildValue("((ii)(ii))(ii)",

             1, 2, 3, 4, 5, 6)         (((1, 2), (3, 4)), (5, 6))
C++获得Python的返回值

Python传回给C++的都是PyObject对象,因此可以调用Python里面的一些类型转换API来把返回值转换成C++里面的类型。类似PyInt_AsLong,PyFloat_AsDouble这些系列的函数。Python比较喜欢传回一个元组,可以使用PyArg_ParseTuple这个函数来解析。这个函数也要用到上面的格式常量)。还有一个比较通用的转换函数是PyArg_Parse,也需要用到格式常量。

源代码测试

建一个python文件
python_called.py

def add_func(x,y):
    return x+y

在同目录下建C文件或者C++文件
main.cpp

#include <iostream>
#include "Python.h" //这里要包含头文件

//C/C++中调用python函数的函数,这里采用单返回值
int function_from_python(int a,int b)
{
    //初始化
    Py_Initialize();

    //定义参数
    int res;
    PyObject *pModule=NULL;
    PyObject *pFunc=NULL;
    PyObject *pArgs=NULL;
    PyObject *pResult=NULL;

    //导入被调用的py文件名
   if(!(pModule=PyImport_Import(PyString_FromString("python_called"))))
    {
        std::cout<<"get module failed!"<<std::endl;
        exit(0);
      }

    //获得要调用的函数名
    if(!(pFunc=PyObject_GetAttrString(pModule, "add_func")))
    {
        std::cout<<"get func failed!"<<std::endl;
        exit(0);
    }

    //传入参数
    pArgs=Py_BuildValue("ii",a,b);

    //执行函数
    pResult=PyObject_CallObject(pFunc, pArgs);

    //返回值为C++
    res = PyInt_AsLong(pResult);

    //释放
    if(pArgs)
        Py_DECREF(pArgs);
    if(pFunc)
        Py_DECREF(pFunc);

    Py_Finalize();

    return res;
}

int main()
{
    std::cout<<"C/C++ call python function:"<<std::endl;
    std::cout<<function_from_python(3,5)<<std::endl;
    return 0;
}

在Windows平台下,打开Visual Studio命令提示符,编译命令为

cl main.cpp -I C:\Python27\include C:\Python27\libs\python27.lib

在Linux下编译命令为

g++ main.cpp -o main -I/usr/include/python2.7/ -lpython2.7

在Mac OS X 下的编译命令同上

编译完后运行可执行文件

C/C++ call python function:
8
注意:
  • 被调用的python文件必须与C++编译出来的可执行文件放在一个目录。
  • 可以建vs2013工程或者Qt工程或则makefile工程文件,在里面配置include和lib目录,更方便。

方法2:调用python文件并执行(基础)

还可以使用C/C++直接执行python文件程序,在控制台中运行。

源代码测试

建一个python文件
python_called.py

def add_func(x,y):
    return x+y

a=13
b=10
print "the result by python func:"
print add_func(a,b)

建C/C++文件
main.cpp

include <iostream>
include "Python.h" //这里要包含头文件

//C/C++中执行python文件
void exec_python_file()
{
    //初始化
    Py_Initialize();

    //choose1,执行单纯的内嵌字符串python代码,建议使用
    if(!PyRun_SimpleString("execfile('python_called.py')"))
        std::cout<<"execute python file program failed"<<std::endl;

    //choose2,执行python文件,不建议使用
    // char fileStr[]="python_called.py";
    // FILE *fp;
    // if(!(fp=fopen(fileStr,"r")))
    // std::cout<<"open python file failed!"<<std::endl;

    // if(!PyRun_SimpleFile(fp,fileStr))
    // std::cout<<"execute python file failed!"<<std::endl;

    // fclose(fp);

        //释放资源
        Py_Finalize();
}

int main()
{
    std::cout<<"C/C++ call python function:"<<std::endl;
    exec_python_file();
    return 0;
}

同样采用命令行或者IDE配置依赖项后编译执行。
运行结果

C/C++ call python function: the result by python func: 23
注意:
  • 同样的py文件必须和C/C++可执行文件在同一个目录。
  • PyRun_SimpleString方式其实是读一段字符串程序,可以用FILE或者fstream读进来文本文件然后传入也行,这样就可以用相对目录了。
  • 不建议用PyRun_SimpleFile的方式,因为这个API要求传入一个FILE指针,而微软的几个CRT版本FILE指针的定义有了变化,因此传入你使用VS2005编译的FILE指针或者其它版本的FILE极有可能崩溃,如果你想安全调用,最好是自己把Python的源代码使用和应用程序相同的环境一起编译出lib来使用。

方法3:使用Cpython(高级)

这是python的一个第三方组件,把Python代码直接变成C代码,此处略。

python调用C/C++
方法1:调用C/C++动态链接库(基础)

将C/C++的程序不经任何加工直接编译成动态链接库so或者dll,再使用python的ctypes调用即可

源代码测试

此处仅以linux下的so为例,因为windows下VS2013生成dll还有个导出库很麻烦。
cpp_dll.cpp

#include <iostream>
extern "C"
void add_func(int a,int b)
{
    std::cout<<"the result: "<<a+b<<std::endl;
}

在linux或者mac下用命令行编译成so

g++ -o cpp_dll.so -shared -fPIC cpp_dll.cpp

也可以在makefile里面配置

在windows下用vs2013的命令行编译从dll

cl /LD cpp_dll.cpp -I C:\Python27\include C:\Python27\libs\python27.lib

在windows下也可以用IDE生成dll
main.py

import ctypes
dll=ctypes.cdll.LoadLibrary
lib=dll("./cpp_dll.so") #in windows use dll
print "python call cpp dll:"
lib.add_func(2,3)

运行main.py即可

注意:
  • C++代码需要加extern “C”来按照C语言编译链接
  • 装在动态库的路径可以用相对路径
方法2:调用C/C++编写的python扩展模块(基础)

这种方法比较好,用C/C++编写python的扩展模块,在python程序里面import进去就可以调用接口

原代码测试

cpp_called.cpp

#include "Python.h"

extern "C"
int add_func(int a,int b) 
{
    return a+b;
}

extern "C"
static PyObject *_add_func(PyObject *self, PyObject *args)
{
    int _a,_b;
    int res;

    if (!PyArg_ParseTuple(args, "ii", &_a, &_b))
        return NULL;
    res = add_func(_a, _b);
    return PyLong_FromLong(res);
}

extern "C"
static PyMethodDef CppModuleMethods[] = 
{
    {
        "add_func",
        _add_func,
        METH_VARARGS,
        ""
    },
    {NULL, NULL, 0, NULL}
};

extern "C"
PyMODINIT_FUNC initcpp_module(void) 
{
    (void) Py_InitModule("cpp_module", CppModuleMethods);
}

函数介绍:

  • 包裹函数_add_func。它负责将Python的参数转化为C的参数(PyArg_ParseTuple),调用实际的add_function,并处理add_function的返回值,最终返回给Python环境。
  • 参数解析PyArg_ParseTuple,将python的变量解析成C/C++变量,按照ii,si,ss等格式
  • 导出表CppModuleMethods。它负责告诉Python这个模块里有哪些函数可以被Python调用。导出表的名字可以随便起,每一项有4个参数:第一个参数是提供给Python环境的函数名称,第二个参数是_add_function,即包裹函数。第三个参数的含义是参数变长,第四个参数是一个说明性的字符串。导出表总是以{NULL,NULL, 0,NULL}结束。
  • 导出函数initcpp_module。这个的名字不是任取的,是你的module名称添加前缀init。导出函数中将模块名称与导出表进行连接。

在windows下,用vs2013命令行编译成pyd文件,这个文件就可以被python识别成扩展模块

cl /LD cpp_called.cpp /o cpp_module.pyd -I C:\Python27\include C:\Python27\libs\python27.lib

也可以在IDE里面配置编译选项生成。

在linux或者mac系统下命令编译

g++ -fPIC -shared cpp_called.cpp -o cpp_module.so -I /usr/include/python2.7/ -lpython2.7

main.py

from cpp_module import add_func
print "python call C/C++ function:"
print add_func(7,12)

运行main.py文件

python call C/C++ function:
19
注意:
  • 按照C语言编译链接
  • 编译的模块放在python文件能识别的目录,最好放在同一个目录
方法3:调用二进制可执行文件(基础)

用python程序调用C/C++编译的可执行文件
cppexec.cpp

#include <iostream>

int add_func(int a,int b)
{
    return a+b;
}

int main()
{
    std::cout<<"the C/C++ run result:"<<std::endl;
    std::cout<<add_func(2,3)<<std::endl;
    return 0;
}

用命令行或者IDE编译成exe等执行文件
main.py

import os

cpptest="cppexec.exe" #in linux without suffix .exe
if os.path.exists(cpptest):
    f=os.popen(cpptest)
    data=f.readlines() #read the C++ printf or cout content
    f.close()
    print data

print "python execute cpp program:"
os.system(cpptest)
注意:
  • 可执行文件放在python文件可识别的目录,最好同一目录
方法4:使用 SWIG(高级)

这是一个第三方的针对python的扩展包,需要些配置文件,略。


python调用C/C++
一般性地给出三个推荐:

  • ctypes,在Python调用已经编译打包好的C语言动态链接库。
  • SWIG,通过声明一个.i文件(语法类似.h),用额外安装的swig命令自动生成一个C/C++与一个Python的包装文件,省略了手写这两层包装的工作。
  • Boost.Python,这是Boost项目的一部分,本质上是对#include <Python.h>的一个包装。开发略显复杂,难以配置编译。

简单起见,推荐SWIG。它还有distutils/setuptools的原生支持,配置起来非常方便。

在Python代码中调用C/C++代码
编译运行SWIG的example代码样例
用SWIG向Python提供C++里STL的容器
编译运行Boost.Python的HelloWorld


相关文章:
  1. Python与C++相互调用
  2. c 与 python相互调用
  3. python与C/C++互相调用
  4. Python与C/C++ 模块相互调用
  5. C/C++与Python互相调用
  6. makehuamn中python与C++/c相互调用
  7. python与C#的互相调用
  8. Python与c++的相互调用(一)
  9. java和python相互调用
  10. python c/c++ 互相调用
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,902评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,037评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,978评论 0 332
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,867评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,763评论 5 360
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,104评论 1 277
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,565评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,236评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,379评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,313评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,363评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,034评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,637评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,719评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,952评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,371评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,948评论 2 341

推荐阅读更多精彩内容