C++调用python
在C/C++中嵌入Python,可以使用Python提供的强大功能,通过嵌入Python可以替代动态链接库形式的接口,这样可以方便地根据需要修改脚本代码,而不用重新编译链接二进制的动态链接库。至少你可以把它当成文本形式的动态链接库,需要的时候还可以改一改,只要不改变接口, C++的程序一旦编译好了,再改就没那么方便了。
第一种方式:通过找到Python模块,类,方法,构造参数来调用。
第二中方式,就是通过构造出一个Python的脚本,用python引擎来执行。
第一种方式可能更为优雅,符合大多数的反射调用的特点。(如c#的反射机制,c#调用Com+,c#调用javascript脚本等)。
一个问题:两种语言互相调用的时候,需要做数据结构(如基本类型,字符串,整数类型等,以及自定义的类等类型)间的转换,共享内存中的一个对象。比如,如何将C++的对象实例传入python中,并在python中使用。c++和python并不在一个进程中,因此可以使用boost的shared_ptr来实现。Python调用C++,换句话说就是需要把C++封装成Python可以“理解”的类型。同理可知C++怎么去调用Python脚本。
下面这个例子,主要是演示了c++调用python,可以在c++中形成一个python脚本,然后利用PyRun_SimpleString调用;并且,构造一个c++的对象,传入到python中,并在python的脚本中调用其函数。
安装python3.4,然后配置系统环境变量。
安装Visual Studio2010(注意可以不用安装其它好多东西,只要安装c++就可以了)。[Visual Studio相关设置]
vs中新建一个win32控制台应用程序,一路确定完成。
1. c++调用python需要在vs2010中的cpp文件中加入,这个头文件在python安装目录Python\include下
要成功引入就要把Python.h的头文件目录(如D:\python3.4.2\include放在菜单 > 项目 > 属性 > C/C++ > 常规 > 附加包含目录下(或者右键项目)
2. 还需要一个python34.lib,如果不导入的话,会提示你出现这个文件的缺失。文件在python\libs下,找到此文件之后进入VS2010,菜单 >项目 > 属性 > 配置属性 > VC++目录 > 库目录,把刚才的绝对路径(如D:\python3.4.2\libs)添加进去,此时变成这样的了:D:\python3.4.2\libs;$(VCInstallDir)lib;$(VCInstallDir)atlmfc\lib;$(WindowsSdkDir)lib;$(FrameworkSDKDir)\lib。这样就可以在VC程序中执行python文件了。
或者把D:\python3.4.2\libs放进项目 > 属性 > 配置属性 > 链接器 > 常规 > 附加库目录中。
Note: 上面的设置是对某个模式生效,如果将运行模式从debug改成了release,要再进去设置,否则设置不成功。
将PythonInvoke.cpp文件改成下面的代码,用于调用python程序helloworld
// PythonInvoke.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include
void main(){
Py_Initialize(); /*初始化python解释器,告诉编译器要用的python编译器*/
PyRun_SimpleString("import helloworld"); /*调用python文件*/
PyRun_SimpleString("helloworld.printHello()");/*调用python文件中的函数*/
Py_Finalize(); /*结束python解释器,释放资源*/
system("pause");
}
Note: 当python代码有错误时,PyImport_ImportModule函数返回NULL;
另一种调用方式的代码
#include//前面所做的一切配置都是为了调用这个头文件和相关库
#include
using namespace std;
int main(){
Py_Initialize();//使用python之前,要调用Py_Initialize();这个函数进行初始化
PyObject * pModule = NULL;//声明变量
PyObject * pFunc = NULL;// 声明变量
pModule =PyImport_ImportModule("helloworld");//这里是要调用的文件名
pFunc= PyObject_GetAttrString(pModule, "Hello");//这里是要调用的函数名
PyEval_CallObject(pFunc, NULL);//调用函数
Py_Finalize();//调用Py_Finalize,这个根Py_Initialize相对应的。
return 0;
}
在项目源文件中,添加文件命令为helloworld.py
def printHello():
print("Hello World!")
.py文件保存在.cpp同目录下
Note: 不能将python文件的名字命名为test.py,否则报错,由于test.py是python内置python脚本文件,也就是python有自己的test.py文件,并且其优先级比你的高。。
如果你安装的python是64位的,则vs2010中需要把解决方案平台定位‘X64’的模式下,否则配置不成功。
报错:fatal error LNK1112: 模块计算机类型“X86”与目标计算机类型“x64”冲突。[fatal error LNK1112]
release模式下运行不用设置太多东西
修改运行选项
Debug改为Release
win32下拉配置,新建x64,一路确定
注意,执行下面之前要在release模式下再设置一次VS2010的配置
运行
运行成功!
1. Debug下,python/libs目录下的python34.lib需要复制并重命名为python34_d.lib的形式
设置:项目 > 属性 > 配置属性 > 链接器 > 输入 > 附加依赖库 > python34_d.lib。
[源码编译 python 生成 python26_d.lib ; 处理 error c101008d]
为什么会有python34.lib和python34_d.lib的差别就是因为:python_d.lib是 库的调试后形式,当我们以debug模式编译工程时,python就用这个lib文件,但是这个文件是不可用 的。对于这点,最快的办法就是强制要求python在任何情况下都是用非调试版本,就可以了。
就是说,不重命名的解决方法,对python头文件python/include/pyconfig.h进行修改:
# if defined(_DEBUG)
# pragma comment(lib,"python34_d.lib")
# elif defined(Py_LIMITED_API)
# pragma comment(lib,"python3.lib")
# else
# pragma comment(lib,"python34.lib")
# endif /* _DEBUG */
将DEBUG条件下的lib由python34_d.lib改为python34.lib.
# if defined(_DEBUG)
# pragma comment(lib,"python34.lib")
2. 64位debug下的方案解决
右键项目名,点击属性,弹出项目属性页,找到链接器—高级,修改右侧的目标计算机,选择有X64的那个选项。如果没有,则选择编译器Configuration Manager中new,添加amd64等平台,然后工程属性中选择x64。
这一步好像也不用,只要在运行时选择x64就可以了。见3.运行。
Note: 属性 - 链接器 - 命令行 -附加选项:如果里面有"/MACHINE:I386"之类的,要删了。
3. 32位库改成64位库
项目 > 属性 > 配置属性 > Vc++目录> 库目录,这里要将32位库改成64位库,相当重要!
$(VCInstallDir)lib\amd64
$(VCInstallDir)atlmfc\lib\amd64
$(WindowsSdkDir)lib\x64
如:将D:\python3.4.2\libs;$(VCInstallDir)lib;$(VCInstallDir)atlmfc\lib;$(WindowsSdkDir)lib;$(FrameworkSDKDir)\lib
换成D:\python3.4.2\libs;$(VCInstallDir)lib\amd64;$(VCInstallDir)atlmfc\lib\amd64;$(WindowsSdkDir)lib\x64;$(FrameworkSDKDir)\lib
没有这样设置会报错:msvcprtd.lib(MSVCP100D.dll) : fatal error LNK1112: module machine type ‘X86’ conflicts with target machine type ‘x64’
msvcprtd.lib(MSVCP100D.dll) : fatal error LNK1112: 模块计算机类型“X86”与目标计算机类型“x64”冲突
还有一个cmake类似的问题[fatal error LNK1112: 模块计算机类型“X86”与目标计算机类型“x64”冲突——我的解决方案]
3. 运行:右键项目名,选择清理解决方案,清理完成之后重新生成解决方案,然后选择X64平台编译器去debug,便可以调试成功。
c++调用python时报错LINK : fatal error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏
这个是由于日志文件引起的,可以将项目\属性\配置属性\清单工具\输入和输出\嵌入清单:原来是“是”,改成“否”。
或者将项目\属性\配置属性\链接器\清单文件\生成清单:原来是“是”,改成“否”。
如果仍然无效,判断是否已经安装了VS2012,如果已经安装,需要安装VS2010 sp1补丁。
但是如果程序要加写rc,必须带清单才能正常使用。所以这种治标不治本的方法失效了。
还有一种解决方案:
出现这个问题的原因:可能是因为系统最近多次更新,出现了两个版本的cvtres.exe。而系统变量里将这俩都引用了,编译的时候,不知道用哪个了,导致出错。所以要删掉一个。
一个在C:\Windows\Microsoft.NET\Framework\v4.0.30319\cvtres.exe,另一个在你安装VS的软件目录..\Microsoft Visual Studio 10.0\vc\bin\cvtres.exe
然后右键属性-->详细信息 查看两者版本号,把老的Kill掉,就完了。
[彻底解决 LINK : fatal error LNK1123: 转换到 COFF 期间失败: 文件无效或损坏]
编译时_RTC_Shutdown和_RTC_InitBase相关错误的解决方法:
error LNK2001: 无法解析的外部符号 _RTC_Shutdown;error LNK2001: 无法解析的外部符号 _RTC_InitBase
右键点击项目,修改:属性 > 配置属性 > C/C++ > 代码生成 > 基本运行时检查,将值从“两者(......)”改为“默认值”。
[编译时_RTC_Shutdown和_RTC_InitBase相关错误的解决方法]
LINK : error LNK2001: 无法解析的外部符号 mainCRTStartup
其它错误
error LNK2019: 无法解析的外部符号 __imp_system,该符号在函数 main 中被引用
原因是system("pause");没有include
编译选项, 需要手动指定Python 的include 路径, 和链接接路径。
代码:
g++ Python.cpp -o Python-I/usr/include/python2.5 -L/usr/lib/python2.5-lpython2.5
调用Python函数时,参数的传递,就是c++的类型,怎么转换成Python的类型;另外一个问题是,Python函数的返回值,怎么转换成C++中的类型。
在C程序中用Python脚本传递参数,或者获得Python脚本的返回值,则要使用更多的函数来编写C程序。由于Python有自己的数据类型,因此在C程序中要使用专门的API对相应的数据类型进行操作。
1.数字与字符串处理
在Python/C API中提供了Py_BuildValue()函数对数字和字符串进行转换处理,使之变成Python中相应的数据类型。其函数原型如下所示。
PyObject* Py_BuildValue( const char *format, ...)
其参数含义如下。
· format:格式化字符串,如表8-1所示。
Py_BuildValue()函数中剩余的参数即要转换的C语言中的整型、浮点型或者字符串等。其返回值为PyObject型的指针。在C语言中,所有的Python类型都被声明为PyObject型。
2.列表操作
在Python/C API中提供了PyList_New()函数用以创建一个新的Python列表。PyList_New()函数的返回值为所创建的列表。其函数原型如下所示。
PyObject* PyList_New( Py_ssize_t len)
其参数含义如下。
· len:所创建列表的长度。
当列表创建以后,可以使用PyList_SetItem()函数向列表中添加项。其函数原型如下所示。
int PyList_SetItem( PyObject *list, Py_ssize_t index, PyObject *item)
其参数含义如下。
· list:要添加项的列表。
· index:所添加项的位置索引。
· item:所添加项的值。
同样可以使用Python/C API中PyList_GetItem()函数来获取列表中某项的值。PyList_GetItem()函数返回项的值。其函数原型如下所示。
PyObject* PyList_GetItem( PyObject *list, Py_ssize_t index)
其参数含义如下。
· list:要进行操作的列表。
· index:项的位置索引。
Python/C API中提供了与Python中列表操作相对应的函数。例如列表的append方法对应于PyList_Append()函数。列表的sort方法对应于PyList_Sort()函数。列表的reverse方法对应于PyList_Reverse()函数。其函数原型分别如下所示。
int PyList_Append( PyObject *list, PyObject *item)
int PyList_Sort( PyObject *list)
int PyList_Reverse( PyObject *list)
对于PyList_Append()函数,其参数含义如下。
· list:要进行操作的列表。
· item:要参加的项。
对于PyList_Sort()和PyList_Reverse()函数,其参数含义相同。
· list:要进行操作的列表。
3.元组操作
在Python/C API中提供了PyTuple_New()函数,用以创建一个新的Python元组。PyTuple_New()函数返回所创建的元组。其函数原型如下所示。
PyObject* PyTuple_New( Py_ssize_t len)
其参数含义如下。
· len:所创建元组的长度。
当元组创建以后,可以使用PyTuple_SetItem()函数向元组中添加项。其函数原型如下所示。
int PyTuple_SetItem( PyObject *p, Py_ssize_t pos, PyObject *o)
其参数含义如下所示。
· p:所进行操作的元组。
· pos:所添加项的位置索引。
· o:所添加的项值。
可以使用Python/C API中PyTuple_GetItem()函数来获取元组中某项的值。PyTuple_GetItem()函数返回项的值。其函数原型如下所示。
PyObject* PyTuple_GetItem( PyObject *p, Py_ssize_t pos)
其参数含义如下。
· p:要进行操作的元组。
· pos:项的位置索引。
当元组创建以后可以使用_PyTuple_Resize()函数重新调整元组的大小。其函数原型如下所示。
int _PyTuple_Resize( PyObject **p, Py_ssize_t newsize)
其参数含义如下。
· p:指向要进行操作的元组的指针。
· newsize:新元组的大小。
4.字典操作
在Python/C API中提供了PyDict_New()函数用以创建一个新的字典。PyDict_New()函数返回所创建的字典。其函数原型如下所示。
PyObject* PyDict_New()
当字典创建后,可以使用PyDict_SetItem()函数和PyDict_SetItemString()函数向字典中添加项。其函数原型分别如下所示。
int PyDict_SetItem( PyObject *p, PyObject *key, PyObject *val)
int PyDict_SetItemString( PyObject *p, const char *key, PyObject *val)
其参数含义如下。
· p:要进行操作的字典。
· key:添加项的关键字,对于PyDict_SetItem()函数其为PyObject型,对于PyDict_SetItemString()函数其为char型。
· val:添加项的值。
使用Python/C API中的PyDict_GetItem()函数和PyDict_GetItemString()函数来获取字典中某项的值。它们都返回项的值。其函数原型分别如下所示。
PyObject* PyDict_GetItem( PyObject *p, PyObject *key)
PyObject* PyDict_GetItemString( PyObject *p, const char *key)
其参数含义如下。
· p:要进行操作的字典。
· key:添加项的关键字,对于PyDict_GetItem()函数其为PyObject型,对于PyDict_GetItemString()函数其为char型。
使用Python/C API中的PyDict_DelItem()函数和PyDict_DelItemString()函数可以删除字典中的某一项。其函数原型如下所示。
int PyDict_DelItem( PyObject *p, PyObject *key)
int PyDict_DelItemString( PyObject *p, char *key)
其参数含义如下。
· p:要进行操作的字典。
· key:添加项的关键字,对于PyDict_DelItem()函数其为PyObject型,对于PyDict_DelItemString()函数其为char型。
使用Python/C API中的PyDict_Next()函数可以对字典进行遍历。其函数原型如下所示。
int PyDict_Next( PyObject *p, Py_ssize_t *ppos, PyObject **pkey, PyObject **pvalue)
其参数含义如下。
· p:要进行遍历的字典。
· ppos:字典中项的位置,应该被初始化为0。
· pkey:返回字典的关键字。
· pvalue:返回字典的值。
在Python/C API中提供了与Python中字典操作相对应的函数。例如字典的item方法对应于PyDict_Items()函数。字典的keys方法对应于PyDict_Keys()函数。字典的values方法对应于PyDict_Values()函数。其函数原型分别如下所示。
PyObject* PyDict_Items( PyObject *p)
PyObject* PyDict_Keys( PyObject *p)
PyObject* PyDict_Values( PyObject *p)
其参数含义如下。
· p:要进行操作的字典。
5.释放资源
Python使用引用计数机制对内存进行管理,实现自动垃圾回收。在C/C++中使用Python对象时,应正确地处理引用计数,否则容易导致内存泄漏。在Python/C API中提供了Py_CLEAR()、Py_DECREF()等宏来对引用计数进行操作。
当使用Python/C API中的函数创建列表、元组、字典等后,就在内存中生成了这些对象的引用计数。在对其完成操作后应该使用Py_CLEAR()、Py_DECREF()等宏来销毁这些对象。其原型分别如下所示。
void Py_CLEAR( PyObject *o)
void Py_DECREF( PyObject *o)
其参数含义如下。
· o:要进行操作的对象。
对于Py_CLEAR()其参数可以为NULL指针,此时,Py_CLEAR()不进行任何操作。而对于Py_DECREF()其参数不能为NULL指针,否则将导致错误。
6.模块与函数
使用Python/C API中的PyImport_Import()函数可以在C程序中导入Python模块。PyImport_Import()函数返回一个模块对象。其函数原型如下所示。
PyObject* PyImport_Import( PyObject *name)
其参数含义如下。
· name:要导入的模块名。
使用Python/C API中的PyObject_CallObject()函数和PyObject_CallFunction()函数,可以在C程序中调用Python中的函数。其参数原型分别如下所示。
PyObject* PyObject_CallObject( PyObject *callable_object, PyObject *args)
PyObject* PyObject_CallFunction( PyObject *callable, char *format, ...)
对于PyObject_CallObject()函数,其参数含义如下。
· callable_object:要调用的函数对象。
· args:元组形式的参数列表。
对于PyObject_CallFunction()函数,其参数含义如下。
· callable_object:要调用的函数对象。
· format:指定参数的类型。
· ...:向函数传递的参数。
使用Python/C API中的PyModule_GetDict()函数可以获得Python模块中的函数列表。PyModule_GetDict()函数返回一个字典。字典中的关键字为函数名,值为函数的调用地址。其函数原型如下所示。
PyObject* PyModule_GetDict( PyObject *module)
其参数含义如下。
· module:已导入的模块对象。
8.2.3 在C中嵌入Python实例
在VC++ 6.0中新建一个名为“EmbPython”的空“Win32 Console Application”工程。向其添加如下所示的“EmbPython.c”文件。
程序输出如下所示。
-==在C中嵌入Python==-
使用Python中的sum函数求解下列数之和
0 1 2 3 4
Using Function sum
The result is: 10
使用Python中的函数分割以下字符串:
this is an example
结果如下所示:
this
is
an
example
按回车键退出程序
C++转换成Python类型,Py_BuildValue()
http://www.python.org/doc/1.5.2p2/ext/buildValue.html
PyObject* pArgs=PyTuple_New(1); //有几个参数,就是几
PyTuple_SetItem(pArgs,0,Py_BuildValue("i",3)); //初始第一个参数,数据类型是i,就是int,值是3
返回值转换如,PyArg_ParseTuple[PyArg_ParseTuple]
“如果没有参数从python到C++, 是正常的,但是有参数就废了报错”。我尝试实现并找到答案。
http://stackoverflow.com/questions/145270/calling-c-c-from-python
1. 无参数 函数声明C可用函数
2. 有参数 那么实用SWIG 也就是我们需要一个接口文件 即
z.i file
%{
#include "z.h"
extern 函数名(参数1, 参数2,...);
%}
SWIG在不同语言互相调用发挥很重要的作用。
SWIG
有一个外部工具叫SWIG,是Simplified Wrapper and Interface Generator 的缩写。其作者为David Beazley,同时也是Python Essential Referenc 一书的作者。这个工具可以根据特别注释过的C/C++头文件生成能给Python,Tcl 和Perl 使用的包装代码。使用SWIG 可以省去你写前面所说的样板代码的时间。你只要关心怎么用C/C++解决你的实际问题就好了。你所要做的就是按SWIG 的格式编写文件,其余的就都由SWIG 来完成。你可以通过下面的网址找到关于SWIG 的更多信息。
http://swig.org
Pyrex
创建C/C++扩展的一个很明显的坏处是你必须要写C/C++代码。你能利用它们的优点,但更重要的是,你也会碰到它们的缺点。Pyrex 可以让你只取扩展的优点,而完全没有后顾之忧。它是一种更偏向Python 的C 语言和Python 语言的混合语言。事实上,Pyrex 的官方网站上就说“Pyrex 是具有C 数据类型的Python“。你只要用Pyrex 的语法写代码,然后运行Pyrex 编译器去编译源代码。Pyrex会生成相应的C 代码,这些代码可以被编译成普通的扩展。你可以在它的官方网站下载到Pyrex:
http://cosc.canterbury.ac.nz/~greg/python/Pyrex
Psyco
Pyrex 免去了我们再去写纯C 代码的麻烦。不过,你要去学会它的那一套与众不同的语法。最后,你的Pyrex 代码还是会被转成C 的代码。无论你用C/C++,C/C++加上SWIG,还是Pyrex,都是因为你想要加快你的程序的速度。如果你可以在不改动你的Python 代码的同时,又能获得速度的提升,那该多好啊。
Psyco 的理念与其它的方法截然不同。与其改成C 的代码,为何不让你已有的Python 代码
运行的更快一些呢?Psyco 是一个just-in-time(JIT)编译器,它能在运行时自动把字节码转为本地代码运行。所以,
你只要(在运行时)导入Psyco 模块,然后告诉它要开始优化代码就可以了。而不用修改自己的代
码。Psyco 也可以检查你代码各个部分的运行时间,以找出瓶颈所在。你甚至可以打开日志功能,来
查看Psyco 在优化你的代码的时候,都做了些什么。你可以访问以下网站获取更多的信息:
http://psyco.sf.net
嵌入
嵌入是Python 的另一功能。与把C 代码包装到Python 中的扩展相对的,嵌入是把Python 解释器包装到C 的程序中。这样做可以给大型的,单一的,要求严格的,私有的并且(或者)极其重要的应用程序内嵌Python 解释器的能力。一旦内嵌了Python,世界完全不一样了。
Python 提供了很多官方文档供写扩展的人参考:
扩展与嵌入
http://docs.python.org/ext
Python/C API
http://docs.python.org/api
分发Python 模块
http://docs.python.org/dist
[python核心编程2e.d. - 扩展Python]
from:http://blog.csdn.net/pipisorry/article/details/49532341
ref:python doc:Python/C API Reference Manual
python doc:EmbeddingPython in Another Application
[Google开发了一个框架让C++和Python交互更容易]让开发者为C++程序生成Python的接口包装器
网友评论