在实际的工作中,为了方便利用python写的程序(因为python中有很多功能强大的函数库),有时需要进行c、c++与python的混合编程,特别是需要在c程序中调用python脚本。关于c程序调用python代码,除了官方文档,网上有很多好的文章可以参考,下面开展实践,实验的主要目的是利用c程序调用python函数画一幅图。
关于示例,最方便的是使用python官方文档(python362.chm)的例子。参考Embedding Python in Another Application一节的第一个例子,这是一个在c程序中直接运行python语句的例子。
#include
int
main(int argc, char *argv[])
{
wchar_t *program = Py_DecodeLocale(argv[0], NULL);
if (program == NULL) {
fprintf(stderr, "Fatal error: cannot decode argv[0]\n");
exit(1);
}
Py_SetProgramName(program); /* optional but recommended */
Py_Initialize();
PyRun_SimpleString("from time import time,ctime\n"
"print('Today is', ctime(time()))\n");
if (Py_FinalizeEx() < 0) {
exit(120);
}
PyMem_RawFree(program);
return 0;
}
运行这个程序,可以利用python的time库获得当前日期,这个程序看起来内容很多,但其实调用python脚本的关键语句只有3句,其他更多的是异常处理和内存管理。因此,这个程序可以简化为:
#include
int main(int argc, char* argv[])
{
Py_Initialize();//初始化python
PyRun_SimpleString("from time import time,ctime\n"
"print('Today is', ctime(time()))\n");//直接运行python代码
Py_FinalizeEx() ; //释放python
return 0;
}
该程序eg1b.c
的编译运行结果为:
D:\cpython>gcc -o eg1b.exe eg1b.c -Wall -lpython36 -LC:\Anaconda3\libs -IC:\Anaconda3\include
D:\cpython>eg1b
Today is Tue Oct 24 23:22:45 2017
D:\cpython>
注意到在执行的时候,如果不设置PATH环境变量,执行会出现错误,提示为缺少python36.dll,解决的方法是加入环境变量:
set PATH="C:\Anaconda3;%PATH%"
如果不设置PYTHONPATH环境变量,执行会出现错误,提示为:
Fatal Python error: Py_Initialize: unable to load the file system codec
ModuleNotFoundError: No module named 'encodings'
Current thread 0x000011d8 (most recent call first):
解决方法是加入PYTHONPATH环境变量,参考Py_Initialize fails - unable to load the file system codec,比如:
set PYTHONPATH="C:\Anaconda3;C:\Anaconda3\libs;C:\Anaconda3\DLLs;C:\Anaconda3\Lib;C:\Anaconda3\Library\bin;"
当然另一个解决方法是,将eg1b.exe
文件复制到C:\Anaconda3
目录下去运行,这样就不需要设置环境变量。
上述这种方法在官方文档中称为高层的嵌入,即:使用高层的接口来执行python的任意代码片段。但这种方法不存在数据的交互,如果需要数据交互,那么就需要采用下面一种方法:
这种实现数据交互的方法需要写更多的代码,主要需要完成3件事情:
我们仍然看官方文档提供的例子,这里直接做了简化,代码eg4.c
为:
#include
#include
int test()
{
PyObject *pName, *pModule, *pDict, *pFunc;
PyObject *pArgs, *pValue;
Py_Initialize();//初始化python
//引入模块
pName = PyUnicode_DecodeFSDefault("testsci");
pModule = PyImport_Import(pName);
//测试函数调用一
//直接获取模块中的函数
pFunc = PyObject_GetAttrString(pModule,"multiply");
//将c/c++类型数据转换为python类型,利用元组传递
pArgs = PyTuple_New(2);
pValue = PyLong_FromLong(3);
PyTuple_SetItem(pArgs, 0, pValue);
pValue = PyLong_FromLong(4);
PyTuple_SetItem(pArgs, 1, pValue);
//调用直接获得的函数,并传递参数
pValue = PyObject_CallObject(pFunc, pArgs);
printf("Result of call: %ld\n", PyLong_AsLong(pValue));
//测试函数调用二
//直接获取模块中的函数
pFunc = PyObject_GetAttrString(pModule,"plotmap");
//将c/c++类型的数据转换为python类型,利用元组传递
pArgs = PyTuple_New(1);
pValue = PyFloat_FromDouble(0.5);
PyTuple_SetItem(pArgs, 0, pValue);
//调用直接获得的函数,并传递参数
pValue = PyObject_CallObject(pFunc, pArgs);
Py_Finalize(); //释放python
return 0;
}
int main(int argc, char* argv[])
{
test();
return 0;
}
python脚本testsci.py
为:
import numpy as np
import pylab as pl
def plotmap(var):
x = np.linspace(0, var*np.pi, 100)
pl.plot(x, np.sin(x))
pl.show()
return 0
def multiply(a,b):
print("Will compute", a, "times", b)
c = 0
for i in range(0, a):
c = c + b
return c
if __name__=="__main__":
plotmap(1)
multiply(2,3)
该程序eg4.c
的编译运行结果为:
D:\cpython>gcc -o eg4.exe eg4.c -Wall -lpython36 -LC:\Anaconda3\libs -IC:\Anaconda3\include
eg4.c: In function 'test':
eg4.c:8:30: warning: unused variable 'pDict' [-Wunused-variable]
PyObject *pName, *pModule, *pDict, *pFunc;
^~~~~
D:\cpython>set PYTHONPATH="C:\Anaconda3;C:\Anaconda3\libs;C:\Anaconda3\DLLs;C:\Anaconda3\Lib;C:\Anaconda3\Library\bin;"
D:\cpython>eg4
Will compute 3 times 4
Result of call: 12
D:\cpython>
注意,这里绘图需要特殊的qt库来支持,如果没有处理的画,图不会绘制出,并会提示错误:
This application failed to start because it could not find or load the Qt platform plugin "windows" in "".
Reinstalling the application may fix this problem.
这个问题的解决是,通过拷贝C:\Anaconda3\Library\plugins\platforms
这个文件夹到当前执行文件所在目录来实现的。具体参考:网页
通过上述实践,完成了c程序调用python程序的工作,在进一步的应用中可以采用类似的方法调用python写的更多脚本,以得到更便捷的功能实现。
[1]. python362.chm,Extending and Embedding the Python Interpreter
[2]. 如何实现 C/C++ 与 Python 的通信?
[3]. 使用c语言调用python小结
[4]. Py_Initialize fails - unable to load the file system codec
[5]. http://tieba.baidu.com/p/3090727690
参考资料3中给出了对类的方法的调用,同时也展示了数据类型转换的不同方法,区别于官方例子中使用元组数据,而使用Py_BuildValue和PyArg_Parse。当然这些信息,参考官方文档中的内容也可以看到。
v1.0 20171025 完成基本内容