记录一下尝试使用C/C++调用python文件的方法与期间遇到的一些基础问题

零、测试环境与前提条件

  • 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) 进行传入和传出的,如此麻烦。
本博客就不在这里进行转换了,有需要的可以关注我的微信公众号,在相关的菜单进行查看, 还有相关的代码示例,时刻欢迎你的到来。
微代码人生

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值