windows下c语言程序调用python实践

windows下c语言程序调用python实践


要点概览

  1. 目的
    • c程序直接调用python程序
    • 方法一:直接运行python语句
    • 方法二:向python传递参数并调用函数
  2. 运行环境
    • windows系统:32位win7
    • c/c++编译器:mingw gcc
    • python 环境:anaconda3.0
  3. 注意点:
    • PATH中的python.exe路径设置
    • PYTHONPATH路径设置
    • qt库的plugin的拷贝

在实际的工作中,为了方便利用python写的程序(因为python中有很多功能强大的函数库),有时需要进行c、c++与python的混合编程,特别是需要在c程序中调用python脚本。关于c程序调用python代码,除了官方文档,网上有很多好的文章可以参考,下面开展实践,实验的主要目的是利用c程序调用python函数画一幅图。

简单的python调用

关于示例,最方便的是使用python官方文档(python362.chm)的例子。参考Embedding Python in Another Application一节的第一个例子,这是一个在c程序中直接运行python语句的例子。

#include <Python.h>

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 <Python.h>

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的任意代码片段。但这种方法不存在数据的交互,如果需要数据交互,那么就需要采用下面一种方法:

向python传递参数并调用函数

这种实现数据交互的方法需要写更多的代码,主要需要完成3件事情:

  1. 将数据值从c的类型转换到python类型,
  2. 执行函数使用转换后的数据调用python接口过程,
  3. 将调用返回的数据从python类型转换为c类型的数据

我们仍然看官方文档提供的例子,这里直接做了简化,代码eg4.c为:

#include <Python.h>
#include <stdio.h>

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

ps:

参考资料3中给出了对类的方法的调用,同时也展示了数据类型转换的不同方法,区别于官方例子中使用元组数据,而使用Py_BuildValue和PyArg_Parse。当然这些信息,参考官方文档中的内容也可以看到。

history:

v1.0 20171025 完成基本内容

猜你喜欢

转载自blog.csdn.net/xenonhu/article/details/78336425