我们希望在自己的C++程序中添加脚本支持,但是自己实现一套完整的脚本解释器会有许多额外的工作量,因此直接将python解释器嵌入到了我们的C++程序中,这样用户可以直接编写python脚本(类似Office的VBA脚本)操作我们的程序,大大方便了用户.
但是目前的问题是用户可能在python脚本中打印一些信息,我们需要获取python中打印的信息并将其显示在C++程序的log窗口中,经过笔者各种尝试终于可以获取到python脚本中使用print命令打印出来的信息了.由于python默认的print命令会将信息打印到sys.stdout,而这个对象实际上是一个TextIOWrapper类,因此理论上在这个类的write函数里就可以拿到具体的打印信息,我的思路是用C++实现一个自定义的write函数,借助python本身支持热替换类成员函数的特性,用它替换掉sys.stdout原本的write函数,然后我们就可以做任何我们想做的事了,比如拦截原本的输出信息,给输出信息添加时间戳等等,具体过程如下:
(我的开发环境为Windows10, Visual Studio 2019, Python 3.8)
1.首先在C++中实现一个write函数newWrite,该函数会将需要打印的信息传给原本的write函数,然后调用一个回调函数
2.然后将python环境中的sys.stdout自带的write()函数替换成我们自己编写的newWrite(),这样python每次print都会执行C++版本的newWrite()
#include "Python.h"
typedef void(*Callback)(const char*, size_t);
Callback gCb;
PyObject* gOriginalWriter;
PyObject* newWrite(PyObject* self, PyObject* args)
{
PyObject* unicode;
assert(!PyErr_Occurred());
if (!PyArg_ParseTuple(args, "U", &unicode)
{
return nullptr;
}
Py_ssize_t n = 0;
const char* str = PyUnicode_AsUTF8AndSize(unicode, &n);
if (str == nullptr)
{
return nullptr;
}
PyObject* result = PyObject_CallFunctionObjArgs(gOriginalWriter, unicode, NULL);
if (result == nullptr)
{
PyErr_Print();
Py_RETURN_NONE;
}
else
{
gCb(str, n); // do anything you want
return PyLong_FromSsize_t(n);
}
}
PyMethodDef writerFuncs[] =
{
{"__my_write", newWrite, METH_VARARGS, ""},
{NULL, NULL, 0, NULL} // sentinel
};
int main()
{
...
Py_Initialize();
gCb = ... // setup callback first
// replace the original write() with our customized one
PyObject* sys = PyImport_ImportModule("sys");
PyModule_AddFunctions(sys, writerFuncs);
PyObject* dict = PyModule_GetDict(sys);
PyObject* stdout = PyDict_GetItemString(dict, "stdout");
gOriginalWriter = PyObject_GetAttrString(stdout, "write");
PyObject* newWriter = PyDict_GetItemString(dict, "__my_write");
PyObject* key = PyUnicode_FromString("write");
PyObject_SetAttr(stdout, key, newWriter);
Py_DECREF(key);
...
}