零、测试环境与前提条件
- 1、公司的Windows 电脑一台
- 2、Python 任意版本一套(本例使用 python 3.8.5 32位)
- 3、C++的开发软件一套(本例使用 VS2019)
一、准备阶段
要想实现C调用python需要用到Python库的帮忙,如Python.h;
由于依赖文件们在安装Python的时候已经随之安装了,如此便只需找到他们并将库文件的路径配置到项目路径中去。
二、测试环境是否成功搭建
C++初始化python环境示例函数:
// test.cpp 中的初始化函数
int init_env(char* mode)
{
printf("init python env\n");
// 设置python路径 替换为你自己的python环境/虚拟环境的路径
Py_SetPath(L"E:\\Desktop\\workspace\\my_projects\\python\\iMateInterface\\venv32\\Lib\\site-packages;" // 缺失后,导入模块中的所有用到自己安装的python模块 都会报错: ModuleNotFoundError
"D:\\Python\\Python38-32\\DLLs;" // 缺失报错: ModuleNotFoundError: No module named '_ctypes'
"D:\\Python\\Python38-32\\Lib;" // 缺失报错: ModuleNotFoundError: No module named 'encodings'
"./;"
);
Py_Initialize();
if (!Py_IsInitialized()) {
printf("init py env failed\n", err_message);
return -1;
}
printf("initialize env success\n");
PyRun_SimpleString("import os");
PyRun_SimpleString("print('current work path', os.getcwd())");
return 0;
}
三、实现调用函数/类方法(含传参)
0、导入要调用的python模块
//cpp
PyObject* py_module = PyImport_ImportModule("test123.py");
if (py_module == NULL) {
printf("import module failed\n");
// 打印模块导入错误的原因
PyErr_Print();
return -1;
}
printf("import module success\n");
// 获取模块内部函数和类列表, 以备后用
PyObject* module_dict = PyModule_GetDict(py_module);
1、调用函数(含传参)
PyObject* ret_from_python;
// 获取python模块中的 test_1 函数, 如需调用类,则可以将test_1更换为类名
PyObject* func_py= PyDict_GetItemString(mdule_dict, "test_1");
// 执行下列函数时,若为函数名,则返回函数中的返回值对象;
// 若为类名,则返回类的实例。
/* ***** 1个参数的情况***** */
// 调用不含参数的python函数
// ret_from_python = PyObject_CallFunction(fuc_py, NULL);
// 调用含字符串str参数的python函数
// ret_from_python = PyObject_CallFunction(func_py, "s", "hello python");
// 调用含整形int参数的python函数
// ret_from_python = PyObject_CallFunction(func_py, "i", "hello python");
// 其他参数见第五小节,常用的代号总结
/* ***** 2个参数的情况***** */
// 调用参数模型为(int, str)时
// ret_from_python = PyObject_CallFunction(func_py, "(is)", 21, "hello python");
// 调用参数模型为(int, int)时
// ret_from_python = PyObject_CallFunction(func_py, "(ii)", 20, 21);
注:使用PyInstance_New函数实现类实例化在python3中已经是过往烟云了。
2、调用类方法(含传参)
// cpp
// 使用上述调用函数的方法获得类实例, 以无初始化参数为例
PyObject* func_py= PyDict_GetItemString(mdule_dict, "ClassA");
PyObject* instance_classa = PyObject_CallFunction(fuc_py, NULL);
// 调用上述python类中的方法(以无参数输入为例, 带参可参考调用带参函数方法),并获得返回值
PyObject* ret_method = PyObject_CallMethod(instance_classa, "test_m", NULL);
上述调用类的方法在python模块中的实现:
# python
class ClassA:
def test_m(self):
print("classa.test_m")
return 0
四、实现返回值解析
解析返回值主要用到的函数为PyArg_Parse() 函数; 解析返回值和发送参数值有异曲同工之妙。
假设已经获得接收值PyObject* ret_py. (即上方的ret_from_python 或 ret_method),下面以解析该值为例。
//cpp
// 用于接收解析函数的返回值;返回值为1时,解析参数成功。下面就不进行判断了,自行添加
int ret_parse;
// 用于接收参数返回值为int类型
int ret_int1;
int ret_int2;
// 用于接收返回值类型为str的
char* ret_str1;
char* ret_str2;
/****** 当只有一个返回值时 *******/
// 返回值为int型
// ret_parse = PyArg_Parse(ret_py, "i", &ret_int1);
// 返回值为char*型
// ret_parse = PyArg_Parse(ret_py, "s", &ret_str1);
/****** 当有两个返回值时 *********/
// 返回值为 (int, int) 型
// ret_parse = PyArg_Parse(ret_py, "(ii)", &ret_int1, &ret_int2);
// 返回值为 (int, str) 型
// ret_parse = PyArg_Parse(ret_py, "(is)", &ret_int1, &ret_str1);
// 其他组合类型,可自己进行相应的替换。
五、常用的代号总结
- i int
- s char*
- B unsigned char
- O PyObject*
六、结束语
如果有需要进行int列表传输的,肯定是不能使用(iiiiiiiiiiiiiiiiiiiiiiii) 进行传入和传出的,如此麻烦。
本博客就不在这里进行转换了,有需要的可以关注我的微信公众号,在相关的菜单进行查看, 还有相关的代码示例,时刻欢迎你的到来。