文章目录
- 本博客运行环境为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: 找不到指定的模块
。