QT与Python混合编程经验记录
1、如何embedding python,Python文档中有专门一章阐述https://docs.python.org/3.5/extending/embedding.htm;
1、库文件:在vs--c/c++--附加包含文件中添加目;链接中也要添加,:将python中的include,libs二个目录添加进来
2、对于Python,没有调试库,可直接将运行库复制一份,名称后面加上_d,就可用,可不能源代码调试而已,同时修改
pyconfig.h
#ifdef _DEBUG
//# define Py_DEBUG //将这行注释掉
#endif
和object.h
#if defined(Py_DEBUG) && !defined(Py_TRACE_REFS)
// #define Py_TRACE_REFS //将这行注释掉
#endif
在windows中如果使用msvs作为开发工具,在pyconfig.h中大约300行左左,有一段代码明确指定了在debug模式下链接到python{311}_d.lib库,因此如果使用msvs作为开发工具,无法指向其它的库,除非修改代码。
另:使用代码中的宏方法来避免链接到Python的Debug库可以采用以下方式:. 在包含任何Python头文件`Python.h`之前,首先检查是否定义了`_DEBUG`宏,` 这个方法的关键在于,通过这种手段我们绕开了`pyconfig.h`中对`_DEBUG`宏的检查,该检查会引导链接器添加调试库。由于我们在包含Python头文件之前取消定义`_DEBUG`,`pyconfig.h`会认为我们是在非调试模式下编译,而选择链接到非调试库。 这种方法可能有助于在一个调试构建中链接到Python的release库,但这样做可能导致某些潜在风险,如之前所述,比如堆不兼容的问题,因为调试和非调试构建通常使用不同的内存管理方式,以及可能的调试符号的不匹配等。因此,这种方法虽然技术上可行,但并不保证总是安全或者适合所有的场合。它应该谨慎使用,并且最好只在你充分理解可能的后果的情况下使用。
// Check whether _DEBUG is defined and remember its state
#ifdef _DEBUG
#define _DEBUG_WAS_DEFINED
#undef _DEBUG
#endif
// Include Python header
#include <Python.h>
// Re-define _DEBUG if it was defined before
#ifdef _DEBUG_WAS_DEFINED
#define _DEBUG
#undef _DEBUG_WAS_DEFINED
#endif
这个方式一定要注意,第一个包含python.h标头的地方就要使用上面的方式。
3、在QT中嵌入python时,编译object.obj时出错。
这个问题是由于Python的object.h
文件中的“slots”关键字和Qt的slots
宏冲突导致的。在Qt中,“slots”是用于实现信号和槽的一个宏。在Python的头文件中,"slots"是一个用于PyObject的宏。
由于QT中定义了slots作为关键了,而python3中有使用slot作为变量,所以有冲突,办法是:
A、在vs中-属性-c/c++ -预处理器-预处理器定义-中添加:QT_NO_KEYWORDS指令,
在Dt的项目文件中.pro文件中添加 config +=_no_keywords指令,再生成vs的项目文件,也就自动添加了上面的指令
这种方法是全局的,同时也取消 forever,foreach的宏定义,因为很多代码使用foreach,所以副作用大;
B、在使用第三方代码时暂时取消冲突的宏定义,然后重新定义相关宏,如QT调用第三方python时,只是在python的object.h中 slots冲突,因此修改object.h:
typedef struct{
const char* name;
int basicsize;
int itemsize;
unsigned int flags;
#undef slots //这里取消slots宏定义
PyType_Slot *slots; /* terminated by slot==0. */
#define slots Q_SLOTS //这里恢复slots宏定义与QT中QObjectDefs.h中一致
} PyType_Spec;
C、要解决这个问题,你需要在包含任何Python头文件(比如Python.h
)之前,#undef slots
。然后在包含Python头文件之后,再#define slots Q_SLOTS
。
4、在主程序中执行一开始Py_Initialize(); 时就出现错误
解决办法是:Py_SetPythonHome((wchar_t*)(L"C:\\Python3"));
是因为我安装python的时候没有添加PYTHONHOME这个环境变量
在Py_Initialize()之前调用下Py_SetPythonHome("C:\\Python3");就可以了
5、用于导入sys模块,以供程序使用,这是基本模块
PyRun_SimpleString("import sys");
6、在执行pModule = PyImport_ImportModule("jptest"); 语句导入自定义模块时,返回对pModule为NULL,是由于自定义模块的位置不在python系统搜索的系统目录列表内,因此解决办法是在系统搜索目录列表中添加目录:
PyRun_SimpleString("sys.path.append('D:\\develop\\qtpythontest\\Win32\\Debug')");
注意字符中中"\"必须前面加上“\\",也就是单个"\"是转意符;而且目录尾部不能添加多余的\符;如果不能确认可将它们显示出来用下面这个语句:PyRun_SimpleString("print(sys.path)");
7、数据转换,在c/c++与python交互时,都是通过PyObject来传入和传出数据的,Python提供相关函数对PyObject数据进行转换,转换时使用格式字符串来控制生成的对象类型,具体可参见https://docs.python.org/3.5/c-api/arg.html官方文档:
A) 将c/c++数据转换成PyObject:
PyObject *pInt=Py_BuildValue("i",2003);
PyObject *pStr=Py_BuildValue("s","This is a string");
PyObject *pTuples=Py_BuildValue("()"); //生成空元组,可作为调用不包含任何参数的函数时,传递空参数
PyObject *pTuples=Py_BuildValue("(s)","This is a string"); //生成一个元素的元组,可作为调用只包含一个字符参数的函数时,传递一个字符参数
B) 将PyObject数据转换成c/c++数据:
1) int bb=0; PyArg_Parse(pObjcet,"i",&bb); //这里pObject是包含整数数据的Python对象,第二个字符串参数"i"指定转换类型,第三个参数将结果值存入bb变量;
2) char * cc=NULL; PyArg_parse(pObject,"s",&cc); //这是字符串转换
3) char * cc=NULL; PyArg_parse(pObject,"(s)",&cc); //这是包含一个字符串元素元组转换
8、调用Python模块函数时,传入参数时,要构造一个参数元组,如:presult = PyObject_CallObject(pfunction, args);这里args就是一个元组,作为被调用函数的参数列表;
A、如参数为空,则这样构造:args=Py_BuildValues("(si)","abc",10); 表示构造二个参数的元组,一个是字符型,另一个是整;多个参数,可参照处;
B、如果参数为空,则需构造一个包含0个元素元组:args=Py_BuildValues("()");
注意以上二种都在格式字符串中包含"()",这是指示构造元;作为函数调用参数必须传递元组,也必须这样构;
下例是通过可变参数来构造调用函数参数元组:
int PythonHandler::PyModuleRunFunction(const char *module, const char *function,
const char *result_format, void *result, const char *args_format, ...)
{
......
//这里构造调用函数所使用的参数元组
va_list args_list;
va_start(args_list, args_format);
args = Py_VaBuildValue(const_cast<char *>(args_format), args_list);
va_end(args_list);
...
if (!args)
{
//args为空,则元组构造失败
Py_DECREF(pfunction);
return -3;
}
...
presult = PyObject_CallObject(pfunction,args); //调用函数