Windows下Python3调用C++函数

  • 本博客运行环境为Windows 10 + VS2015 + python3.6;
  • 主要流程为将.cpp文件封装成.dll文件,改名为.pyd后可以直接在python下import
  • 更多详细信息可以参考python的doc:Extending Python with C or C++

1. 配置VS环境

A. 此处选择Release和x64,Debug亲测会有问题,win32没有做过测试。
在这里插入图片描述
B. 添加项目的包含目录。
在这里插入图片描述
C. 添加项目的库目录
在这里插入图片描述
D. 添加附加包含目录
在这里插入图片描述
E. 添加附加库目录
在这里插入图片描述

F. 修改VS中文件的配置类型,修改为动态库
在这里插入图片描述

2. C++示例代码

module文件名为Py2Cpp.cpp

2.1. 有输入输出参数

2.1.1. 常规变量类型

第4节的图中有python变量类型与c++变量类型的对应关系,对于共有的基础类型而言,无需手动进行变量的转换就可以在python中对c++函数进行调用。

/*
导入python API,
由于Python可能会定义一些会影响某些系统上标准标头的预处理器定义,
因此必须在包含任何标准标头文件之前包含Python.h。
*/
#include <Python.h> 

/*
cpp中的原始函数
*/
int Add(int x, int y)
{
	return x + y;
}

/*
Python调用Add函数时的接口。
该函数输入参数为python变量类型,
PyArg_ParseTuple检查输入的参数类型并转换为C变量类型,
最后Py_BuildValue又将C变量类型转换为python类型。
*/
static PyObject *
WrappAdd(PyObject* self, PyObject* args)
{
	int x, y;
	if (!PyArg_ParseTuple(args, "ii", &x, &y))
	{
		return NULL;
	}
	return Py_BuildValue("i", Add(x, y));
}

/*
为使得函数能够被python调用,需要先定义一个方发表“method table”。 
第三个参数(METH_VARARGS),这个标志指定会使用C的调用惯例。
可选值有METH_VARARGS、METH_VARARGS|METH_KEYWORDS。值0代表使用PyArg_ParseTuple()的过时的变量。
如果单独使用METH_VARARGS,函数会等待Python传来tuple格式的参数,并最终使用PyArg_ParseTuple()进行解析。
METH_KEYWORDS值表示接受关键字参数。这种情况下C函数需要接受第三个PyObject*对象,表示字典参数,
使用 PyArg_ParseTupleAndKeywords()来解析出参数。
*/
static PyMethodDef test_methods[] = {
	{ "Add", WrappAdd, METH_VARARGS, "something" },
	{ NULL, NULL , 0, NULL }
};

/*
上一个方法表必须被模块定义结构所引用。
这个结构体必须传递给解释器的模块初始化函数。
*/
static struct PyModuleDef PCmodule = {
	PyModuleDef_HEAD_INIT,
	"Py2Cpp",   /* name of module */
	NULL, /* module documentation, may be NULL */
	-1,       /* size of per-interpreter state of the module,
			  or -1 if the module keeps state in global variables. */
	test_methods
};

/*
初始化函数必须命名为PyInit_name(),其中name是module的名字,并应该定义为非static,且在模块文件里。
*/
PyMODINIT_FUNC
PyInit_Py2Cpp(void)
{
	return PyModule_Create(&PCmodule);
}

2.1.2. 非常规变量类型

若c++函数的输入是vector等类型,则在传入python变量之后需要将其转换为c++的vector类型,才能正常地调用函数。
注意,python中在调用该c++函数时,传入的变量类型应为list(可以是多维),最后获取的输出变量也是list,可以自行再将其转换为numpy类型。

#include <Python.h> 
#include <vector>
#include <iostream>
using namespace std;

/*
将c++中的vector<double>转换为python的list
*/
PyObject *
c2p_oneDData(vector<double> *c_data)
{
	int labelSize = c_data->size();
	printf("c++ data size:%d\n", labelSize);
	PyObject *py_data = PyList_New(labelSize);

	for (int i = 0; i < labelSize; ++i) {
		PyList_SetItem(py_data, i, Py_BuildValue("d", (*c_data)[i]));
	}
	return py_data;
}

/*
将c++中的vector<vector<double>>转换为python的二维list
*/
PyObject *
c2p_twoDData(vector<vector<double>> *c_data)
{
	int argSize = c_data->size();
	printf("c++ data size:%d\n", argSize);
	PyObject *py_data = PyList_New(argSize);

	for (int i = 0; i < c_data->size(); ++i) {
		PyObject *pObj = PyList_New((*c_data)[i].size());
		for (int j = 0; j < (*c_data)[i].size(); ++j) {
			PyList_SetItem(pObj, j, Py_BuildValue("d", (*c_data)[i][j]));
		}
		PyList_SetItem(py_data, i, Py_BuildValue("O", pObj));
	}
	return py_data;
}

/*
将二维python数据类型转换为c++中的vector<vector<double>>类型
*/
vector<vector<double>> *
p2C_twoDData(PyObject *py_data)
{
	vector<vector<double>> *c_data = new vector<vector<double>>;
	int retSize = PyList_Size(py_data);
	printf("python data size:%d\n", retSize);
	for (int i = 0; i < retSize; ++i) {
		PyObject *pItem = PyList_GetItem(py_data, i);
		int itemLen = PyList_Size(pItem);
		vector<double> tmp;
		for (int j = 0; j < itemLen; ++j) {
			PyObject *pItem_j = PyList_GetItem(pItem, j);
			double item_val = PyFloat_AsDouble(pItem_j);
			tmp.push_back(item_val);
		}
		c_data->push_back(tmp);
	}
	return c_data;
}

/*
将一维python数据类型转换为c++中的vector<vector<double>>类型
*/
vector<double> *
p2C_oneDData(PyObject *py_data)
{
	vector<double> *c_data = new vector<double>;
	int retSize = PyList_Size(py_data);
	printf("python data size:%d\n", retSize);
	for (int i = 0; i < retSize; ++i) {
		PyObject *pItem = PyList_GetItem(py_data, i);
		double item_val = PyFloat_AsDouble(pItem);
		c_data->push_back(item_val);
	}
	return c_data;
}

vector<vector<double>> 
Add(vector<double> a) // python中需调用的c++函数主体
{
	vector<vector<double>> sum;
	for (int i = 0; i < a.size(); i++) {
		sum.push_back(a);
	}
	return sum;
}

static PyObject *
WrappAdd(PyObject* self, PyObject* args)
{
	PyObject *py_data; // python中传入的输入参数
	if (!PyArg_ParseTuple(args, "O", &py_data)) // 获取到python文件中的输入参数(python 2 c++)
	{
		return NULL;
	}
	vector<double> a = *p2C_oneDData(py_data); // python 2 c++
	vector<vector<double>> sum = Add(a);
	PyObject * tmp = c2p_twoDData(&sum); // c++ 2 python
	return Py_BuildValue("O", tmp);  // (c++ 2 python)
}

static PyMethodDef test_methods[] = {
	{ "Add", WrappAdd, METH_VARARGS, "something" },
	{ NULL, NULL , 0, NULL }
};

static struct PyModuleDef PCmodule = {
	PyModuleDef_HEAD_INIT,
	"Py2Cpp",   /* name of module */
	NULL, /* module documentation, may be NULL */
	-1,       /* size of per-interpreter state of the module,
			  or -1 if the module keeps state in global variables. */
	test_methods
};

PyMODINIT_FUNC
PyInit_Py2Cpp(void)
{
	return PyModule_Create(&PCmodule);
}

2.2. 无输入输出参数

#include <Python.h> 
#include <iostream>

void Add()
{
	std::cout<<"~~"<<std::endl;
}

/*
如果C函数没有返回值(返回 void 的函数),则必须返回None(可以用Py_RETUN_NONE宏来完成):
*/
static PyObject *
WrappAdd(PyObject* self, PyObject* args)
{
	Add();
	Py_INCREF(Py_None);
	return Py_None;
}

static PyMethodDef test_methods[] = {
	{ "Add", WrappAdd, METH_VARARGS, "something" },
	{ NULL, NULL , 0, NULL }
};

static struct PyModuleDef PCmodule = {
	PyModuleDef_HEAD_INIT,
	"Py2Cpp",   /* name of module */
	NULL, /* module documentation, may be NULL */
	-1,       /* size of per-interpreter state of the module,
			  or -1 if the module keeps state in global variables. */
	test_methods
};

PyMODINIT_FUNC
PyInit_Py2Cpp(void)
{
	return PyModule_Create(&PCmodule);
}

为每个模块增加一个型如PyMethodDef ModuleMethods[]的数组,以便于Python解释器能够导入并调用它们,每一个数组都包含了函数在Python中的名字,相应的包装函数的名字以及一个METH_VARARGS常量,METH_VARARGS表示参数以tuple形式传入。 若需要使用PyArg_ParseTupleAndKeywords()函数来分析命名参数的话,还需要让这个标志常量与METH_KEYWORDS常量进行逻辑与运算常量 。数组最后用两个NULL来表示函数信息列表的结束 。 [ 1 ] 。^{[1]} [1]

所有工作的最后一部分就是模块的初始化函数,调用Py_InitModule()函数,并把模块名和ModuleMethods[]数组的名字传递进去,以便于解释器能正确的调用模块中的函数 。 [ 1 ] 。^{[1]} [1]

3. 生成.pyd文件

A. 右键点击项目选择“生成”:
在这里插入图片描述
B. 找到生成的.dll文件,将其后缀更改为.pyd
在这里插入图片描述
C. 将该.pyd文件移动到.py目录中,可以在.py中直接import该文件
在这里插入图片描述
在这里插入图片描述

4. 变量类型关系

从Python到C的转换用PyArg_Parse*系列函数,int PyArg_ParseTuple():把Python传过来的参数转为C;int PyArg_ParseTupleAndKeywords()与PyArg_ParseTuple()作用相同,但是同时解析关键字参数;它们的用法跟C的sscanf函数很像,都接受一个字符串流,并根据一个指定的格式字符串进行解析,把结果放入到相应的指针所指的变量中去,它们的返回值为1表示解析成功,返回值为0表示失败。从C到Python的转换函数是PyObject* Py_BuildValue():把C的数据转为Python的一个对象或一组对象,然后返回之;Py_BuildValue的用法跟sprintf很像,把所有的参数按格式字符串所指定的格式转换成一个Python的对象 。 [ 1 ] 。^{[1]} [1]

C与Python之间数据转换的转换代码 : [ 1 ] :^{[1]} [1]
在这里插入图片描述

5. 注意事项

  • .cpp文件运行时需要别的.dll,则也应将这些.dll.pyd一同复制到.py文件目录中。否则会报ImportError: DLL load failed: 找不到指定的模块

Conferences

[1] Python与C/C++的混合调用

  • 7
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值