分享

深入浅析 C 调用 Python 模块

 拽拽的橡皮擦 2017-06-11

一般开发过游戏的都知道Lua和C 可以很好的结合在一起,取长补短,把Lua脚本当成类似动态链接库来使用,很好的利用了脚本开发的灵活性。而作为一门流行的通用型脚本语言Python,也是可以做到的。在一个C 应用程序中,我们可以用一组插件来实现一些具有统一接口的功能,一般插件都是使用动态链接库实现,如果插件的变化比较频繁,我们可以使用Python来代替动态链接库形式的插件(堪称文本形式的动态链接库),这样可以方便地根据需求的变化改写脚本代码,而不是必须重新编译链接二进制的动态链接库。灵活性大大的提高了。

作为一种胶水语言,Python 能够很容易地调用 C 、 C 等语言,也能够通过其他语言调用 Python 的模块。

Python 提供了 C 库,使得开发者能很方便地从 C 程序中调用 Python 模块。

具体的文档参考官方指南:

Embedding Python in Another Application

调用方法

1 链接到 Python 调用库

Python 安装目录下已经包含头文件( include 目录)和库文件 ( Windows 下为 python27.lib)。

使用之前需要链接到此库。

2 直接调用 Python 语句

<code class='language-cpp hljs '>#include 'python/Python.h' int main() { Py_Initialize(); ## 初始化 PyRun_SimpleString('print 'hello''); Py_Finalize(); ## 释放资源 } </code>

3 加载 Python 模块并调用函数

~/test 目录下含有 test.py :

<code class='language-python hljs '>def test_add(a, b): print 'add ', a, ' and ', b return a b</code>

则可以通过以下代码调用 test_add 函数 :

<code class='language-cpp hljs '>#include 'python/Python.h' #include <iostream> using namespace std; int main() { Py_Initialize(); // 初始化 // 将Python工作路径切换到待调用模块所在目录,一定要保证路径名的正确性 string path = '~/test'; string chdir_cmd = string('sys.path.append(\'') path '\')'; const char* cstr_cmd = chdir_cmd.c_str(); PyRun_SimpleString('import sys'); PyRun_SimpleString(cstr_cmd); // 加载模块 PyObject* moduleName = PyString_FromString('test'); //模块名,不是文件名 PyObject* pModule = PyImport_Import(moduleName); if (!pModule) // 加载模块失败 { cout << '[ERROR] Python get module failed.' << endl; return 0; } cout << '[INFO] Python get module succeed.' << endl; // 加载函数 PyObject* pv = PyObject_GetAttrString(pModule, 'test_add'); if (!pv || !PyCallable_Check(pv)) // 验证是否加载成功 { cout << '[ERROR] Can't find funftion (test_add)' << endl; return 0; } cout << '[INFO] Get function (test_add) succeed.' << endl; // 设置参数 PyObject* args = PyTuple_New(2); // 2个参数 PyObject* arg1 = PyInt_FromLong(4); // 参数一设为4 PyObject* arg2 = PyInt_FromLong(3); // 参数二设为3 PyTuple_SetItem(args, 0, arg1); PyTuple_SetItem(args, 1, arg2); // 调用函数 PyObject* pRet = PyObject_CallObject(pv, args); // 获取参数 if (pRet) // 验证是否调用成功 { long result = PyInt_AsLong(pRet); cout << 'result:' << result; } Py_Finalize(); ## 释放资源 return 0; } </iostream></code>

参数传递

1 C 向 Python 传递参数

Python 的参数实际上是元组,因此传参实际上就是构造一个合适的元组。

常用的有两种方法:

使用 PyTuple_New 创建元组, PyTuple_SetItem 设置元组值

<code class='language-cpp hljs '>PyObject* args = PyTuple_New(3); PyObject* arg1 = Py_BuildValue('i', 100); // 整数参数 PyObject* arg2 = Py_BuildValue('f', 3.14); // 浮点数参数 PyObject* arg3 = Py_BuildValue('s', 'hello'); // 字符串参数 PyTuple_SetItem(args, 0, arg1); PyTuple_SetItem(args, 1, arg2); PyTuple_SetItem(args, 2, arg3);</code>

直接使用Py_BuildValue构造元组

<code class='language-cpp hljs '>PyObject* args = Py_BuildValue('ifs', 100, 3.14, 'hello'); PyObject* args = Py_BuildValue('()'); // 无参函数</code>

i, s, f之类的格式字符串可以参考 格式字符串

2 转换 Python 返回值

调用 Python 得到的都是PyObject对象,因此需要使用 Python 提供的库里面的一些函数将返回值转换为 C , 例如 PyInt_AsLong,PyFloat_AsDouble, PyString_AsString 等。

还可以使用 PyArg_ParseTuple 函数来将返回值作为元组解析。

PyArg_Parse 也是一个使用很方便的转换函数。

PyArg_ParseTuple 和 PyArg_Parse 都使用 格式字符串

注意事项

需要将 Python 的工作目录切换到模块所在路径 按照模块名加载而不是文件名 模块加载或者函数加载需要验证是否成功,否则可能会引起堆栈错误导致程序崩溃 需要使用 Py_DECREF(PyObject*) 来解除对象的引用(以便Python垃圾回收)

以上所述是小编给大家介绍的C 调用 Python 模块的相关知识,希望对大家有所帮助!

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多