• Qt 中捕获三方库&自身标准打印方法


    【写在前面】

            很多时候,我们为了方便调试,常常需要加入一些打印。

            例如 Qt 中的 QDebug,C 和 C++ 中的 printf / cout 等等,又或者是三方库提供的标准打印接口。

            然而大部分时候,这些打印相当不统一(格式和位置),并且因为 Qt 作为 GUI 框架,调试信息实在不应该直接置于 UI 之上。

            因此,需要一种能统一和标准化所有标准打印的方法( 所谓标准打印即标准输出 stdout 等),并且能够动态配置。


     【正文开始】

    • 对于 Qt 自身的打印,捕获起来相当容易:

            我们使用 qInstallMessageHandler() 安装一个消息处理器,它指向一个函数,其函数签名如下第一行所示:

    1. typedef void (*QtMessageHandler)(QtMsgType, const QMessageLogContext &, const QString &);
    2. Q_CORE_EXPORT QtMessageHandler qInstallMessageHandler(QtMessageHandler);

             该函数能够捕获由 Qt Debug 产生的各种类型的打印消息,然后可以在此函数集中处理。

    • 对于三方库,只要他是标准输出,我们就可以使用一些技巧来捕获它:

            这里我们需要借助一个C库函数 freopen(),其声明如下:

    FILE *freopen(const char * restrict filename, const char * restrict mode, FILE * restrict stream);

            该函数用于重定向输入输出流。它可以在不改变代码原貌的情况下改变输入输出环境,但使用时应当保证流是可靠的

            有了这些函数,接下来我们整合一下,来实现一个完整的,能够处理所有情况的函数:

    1. static void initializeDebugEnveriment()
    2. {
    3. static bool initialized = false;
    4. static QTextEdit *edit = new QTextEdit;
    5. static int lineCount = 0;
    6. static const QString stdoutFileDir = qApp->applicationDirPath() + "/cache";
    7. static QTimer *watcher = new QTimer(qApp);
    8. static quint64 fileSize = 0;
    9. static QFile watchedStdoutFile(stdoutFileDir + "/stdout");
    10. if (!initialized) {
    11. qRegisterMetaType("QTextCursor");
    12. auto palette = edit->palette();
    13. palette.setBrush(QPalette::Highlight, QColor(0, 120, 215));
    14. edit->setPalette(palette);
    15. edit->setReadOnly(true);
    16. edit->setWindowTitle(QStringLiteral("调试窗口"));
    17. edit->setWindowFlag(Qt::WindowStaysOnTopHint);
    18. edit->resize(700, 500);
    19. edit->show();
    20. if (!QDir().exists(stdoutFileDir)) QDir().mkpath(stdoutFileDir);
    21. std::freopen((stdoutFileDir + "/stdout").toLocal8Bit().data(), "w", stdout);
    22. watchedStdoutFile.open(QIODevice::ReadOnly);
    23. watcher->start(100);
    24. QObject::connect(watcher, &QTimer::timeout, watcher, []{
    25. if (watchedStdoutFile.size() != fileSize) {
    26. fileSize = watchedStdoutFile.size();
    27. auto watchedMsg = QString::fromLocal8Bit(watchedStdoutFile.readAll());
    28. if (!watchedMsg.isEmpty()) {
    29. auto list = watchedMsg.split('\n');
    30. for (auto msg: qAsConst(list)) {
    31. msg = msg.trimmed();
    32. auto time = QDateTime::currentDateTime().toString("[yyyy-MM-dd-hh:mm:ss:zzz] ");
    33. if (!msg.isEmpty()) msg = time + msg;
    34. edit->append(msg);
    35. if (!edit->textCursor().hasSelection()) edit->moveCursor(QTextCursor::End);
    36. if (++lineCount > 50000) {
    37. lineCount = 0;
    38. edit->clear();
    39. }
    40. }
    41. }
    42. }
    43. });
    44. initialized = true;
    45. }
    46. static auto myMsgHandler = [](QtMsgType, const QMessageLogContext &, const QString &msg) -> void {
    47. auto time = QDateTime::currentDateTime().toString("[yyyy-MM-dd-hh:mm:ss:zzz] ");
    48. edit->append(time + msg);
    49. if (!edit->textCursor().hasSelection()) edit->moveCursor(QTextCursor::End);
    50. if (++lineCount > 50000) {
    51. lineCount = 0;
    52. edit->clear();
    53. }
    54. };
    55. qInstallMessageHandler(myMsgHandler);
    56. }

            这里我用一个 QTextEdit 来显示所有捕获的打印,但这不是必要的,可以根据自己需求来改造。

            首先,因为要将标准输出重定向到文件,所以我们要创建一个文件夹用于缓存其输出。

            接着,将 stdout 重定向至一个文件中( 实际上可以是任意输出流 ),然后创建一个定时器用于监视其文件大小变化

            如果监视文件大小改变,则将内容读入( 即打印内容 ),并添加到 textEdit 中显示出来,此时就完成了标准打印的捕获。

            并且,这种方法能够捕获三方库的内部打印消息。

            最后,我添加了一段模拟代码来测试一下:

    1. int main(int argc, char *argv[])
    2. {
    3. QApplication app(argc, argv);
    4. initializeDebugEnveriment();
    5. QTimer timer;
    6. QObject::connect(&timer, &QTimer::timeout, &timer, []{
    7. static int count = 1;
    8. qDebug() << "This is Qt Debug message! Count:" << count++;
    9. });
    10. timer.start(1000);
    11. QTimer otherTimer;
    12. QObject::connect(&otherTimer, &QTimer::timeout, &otherTimer, []{
    13. static int count = 1;
    14. printf("This is printf stdout message! Count: %d", count++);
    15. fflush(stdout);
    16. });
    17. otherTimer.start(1000);
    18. return app.exec();
    19. }

            效果图如下:


     【结语】

            现在有了这个函数,我们还可以动态控制打印窗口,即控制 textEdit 是否显示。

            另外提一点,如果嫌弃麻烦,可以直接 CONFIG += console,直接使用控制台打印。当然这个方法的缺点则是不能运行时配置,并且启动时会有一个控制台窗口。

            最后,源码地址:

            Github的:GitHub - mengps/QtExamples: 分享各种 Qt 示例,,说不定用得上呢~分享各种 Qt 示例,,说不定用得上呢~. Contribute to mengps/QtExamples development by creating an account on GitHub.https://github.com/mengps/QtExamples        CSDN的:

    https://download.csdn.net/download/u011283226/87097616https://download.csdn.net/download/u011283226/87097616

  • 相关阅读:
    Linux提权一
    Html_Css问答集(2)
    使用PM2部署goweb工程
    软件设计模式系列之四——简单工厂模式
    【设计模式】Java设计模式 - 装饰者模式
    网络原理——No.4 传输层_TCP协议中的延迟应答, 捎带应答, 面向字节流与TCP的异常处理
    35岁危机?内卷成程序员代名词了
    环信IM Android端实现华为推送详细步骤
    企业微信好友和微信好友的区别
    2024Python二级
  • 原文地址:https://blog.csdn.net/u011283226/article/details/127950587