2022年 11月 5日

C程序调用python程序

最近跟同学一起做后台的项目,用的c语言。由于要用到python下的算法库,因此学习了在c程序中如何调用python程序。

由于后台的环境用到了多线程,因此需要考虑多线程环境下的c调用python。

在多线程环境下调用python库,需要按照如下步骤:

在主线程中:

1、初始化python,允许支持多线程。

2、python扩展初始化

3、释放线程全局锁

在子线程中:

4、设置GIL(python特有的全局解释锁)的状态,确保为当前线程控制GIL

5、调用ython代码

6、释放GIL,将GIL的控制权交还给系统

功能描述:我们需要在c代码中调用python函数,并且,要将c的参数传到python中,并解析python的返回值存入c的变量中。

示例:

#include<Python.h>

PyObject * pFunc = NULL;    //全局变量,可使process.c文件访问该变量

int pythonInit()    //自定义python初始化,包括python初始化以及相关模块参数的导入。
{
Py_Initialize();    //python初始化,库函数
if (!Py_IsInitialized())
{

    printf(“Python init failed!\n”);

            PyErr_Print();//打印错误信息,库函数

    return -1;

}

PyObject *pDict = NULL;           //python模块字典

PyObject *pModule = NULL;    //python模块

PyRun_SimpleString(“import sys”);    //调用python代码

PyRun_SimpleString(“sys.path.append(‘./’)”);    //将当前目录添加到python路径

        //导入模块,模块名为query_python。在query_python.py文件中。

pModule = PyImport_Import(PyString_FromString(“query_python”));    

if(!pModule)

{

printf(“Load query_python.py failed!\n”);

PyErr_Print();

return -1;

}

pDict = PyModule_GetDict(pModule);    //调用库函数获取模块字典,模块字典包含该模块下的所有函数

        if(!pDict)

{

printf(“Can’t find dict in query_python!\n”);

                PyErr_Print();

return -1;

}

       pFunc = PyDict_GetItemString(pDict, “query_k”);//获取模块字典中query_k函数

       if(!pFunc || !PyCallable_Check(pFunc))    //检查函数的有效性

       {

           printf(“Can’t find function!\n”);

           PyErr_Print();

           return -1;

       }

       if(pModule)    //释放模块申请的内存

{


Py_DECREF(pModule);

}

if(pDict)     //释放模块字典申请的内存

{


Py_DECREF(pDict);

}

return 0;

}

//主调函数

void serverStart()

{

        PyEval_InitThreads();//初始化多线程,使python支持多线程

        int res;

res = pythonInit(); //python扩展初始化

        if(res==-1)
{
PyErr_Print();
return;

}

        //开启多线程之前,必须先释放锁,才能获得线程全局锁
         PyEval_ReleaseLock();

        … //此处开启了多线程

        process();//process函数会调用process.c中的get_query_result函数

        PyGILState_Ensure();    //主线程结束,释放GIL
Py_Finalize();                //对应于python初始化的释放

}

process.c:

extern PyObject *pFunc;    //获取另一个文件中的全局变量

///下面的函数功能是获取query_python.py文件中的query_k函数的返回值,并传给c程序的query_res指向的内存。

int get_query_result(const char *buf, const char *query, int k, char *query_res)

{

    PyGILState_STATE gstate = PyGILState_Ensure();//确保当前线程获得GIL的控制权

    if (!PyCallable_Check(pFunc))

    {

        PyErr_Print();

        return -1;

    }

    PyObject *pArgs = NULL;     //传给PyObject_Call函数的第二个参数

    PyObject *pkwargs = NULL; //传给PyObject_Call函数的第三个参数



    pArgs = PyTuple_New(3);    //函数参数以元组形式传入,参数个数为3



    PyTuple_SetItem(pArgs, 0, Py_BuildValue(“s”, query));    //传给python的参数,s代表字符串,i代表整数

    PyTuple_SetItem(pArgs, 1, Py_BuildValue(“s”, buf));

    PyTuple_SetItem(pArgs, 2, Py_BuildValue(“i”, k));

    PyObject *pyValue = PyObject_Call(pFunc, pArgs, pkwargs);//传入pFunc指向的函数,参数。返回值存入pyValue中。

    if (pyValue == NULL)

    {

        printf(“PyObject_CallObject error!\n”);

        PyErr_Print();

        PyGILState_Release(gstate);    //返回值为NULL,需要释放GIL

        return -1;

    }

    char *ret_str;    //无需申请内存

    int len;

    PyArg_ParseTuple(pyValue, “si”, &ret_str, &len);    //解析返回值,将返回值存入ret_str字符数组和len变量中。

    strncpy(query_res, ret_str, len);    //执行字符串拷贝。

    if(pArgs)

    {

        Py_DECREF(pArgs);    //释放参数内存

    }

    if(pkwargs)                    //释放参数内存

    {

        Py_XDECREF(pkwargs);

    }

    PyGILState_Release(gstate);    //释放GIL

    return 0;

}

总结:

第一次学习c调用python,查阅了大量资料,花了不少时间,收获还是不少。

调试过程中遇到了很多问题,暴露了c语言的一些不足,同时,尽管python学了一段时间了,但对pyhton的GIL理解不深。