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

【写在前面】

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

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

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

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


 【正文开始】

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

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

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

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

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

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

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

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

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

static void initializeDebugEnveriment()
{
    static bool initialized = false;
    static QTextEdit *edit = new QTextEdit;
    static int lineCount = 0;
    static const QString stdoutFileDir = qApp->applicationDirPath() + "/cache";

    static QTimer *watcher = new QTimer(qApp);
    static quint64 fileSize = 0;
    static QFile watchedStdoutFile(stdoutFileDir + "/stdout");

    if (!initialized) {
        qRegisterMetaType<QTextCursor>("QTextCursor");

        auto palette = edit->palette();
        palette.setBrush(QPalette::Highlight, QColor(0, 120, 215));
        edit->setPalette(palette);
        edit->setReadOnly(true);
        edit->setWindowTitle(QStringLiteral("调试窗口"));
        edit->setWindowFlag(Qt::WindowStaysOnTopHint);
        edit->resize(700, 500);
        edit->show();

        if (!QDir().exists(stdoutFileDir)) QDir().mkpath(stdoutFileDir);

        std::freopen((stdoutFileDir + "/stdout").toLocal8Bit().data(), "w", stdout);
        watchedStdoutFile.open(QIODevice::ReadOnly);

        watcher->start(100);
        QObject::connect(watcher, &QTimer::timeout, watcher, []{
            if (watchedStdoutFile.size() != fileSize) {
                fileSize = watchedStdoutFile.size();
                auto watchedMsg = QString::fromLocal8Bit(watchedStdoutFile.readAll());
                if (!watchedMsg.isEmpty()) {
                    auto list = watchedMsg.split('\n');
                    for (auto msg: qAsConst(list)) {
                        msg = msg.trimmed();
                        auto time = QDateTime::currentDateTime().toString("[yyyy-MM-dd-hh:mm:ss:zzz] ");
                        if (!msg.isEmpty()) msg = time + msg;
                        edit->append(msg);
                        if (!edit->textCursor().hasSelection()) edit->moveCursor(QTextCursor::End);
                        if (++lineCount > 50000) {
                            lineCount = 0;
                            edit->clear();
                        }
                    }
                }
            }
        });

        initialized = true;
    }

    static auto myMsgHandler = [](QtMsgType, const QMessageLogContext &, const QString &msg) -> void {
        auto time = QDateTime::currentDateTime().toString("[yyyy-MM-dd-hh:mm:ss:zzz] ");
        edit->append(time + msg);
        if (!edit->textCursor().hasSelection()) edit->moveCursor(QTextCursor::End);
        if (++lineCount > 50000) {
            lineCount = 0;
            edit->clear();
        }
    };

    qInstallMessageHandler(myMsgHandler);
}

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

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

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

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

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

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

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);

    initializeDebugEnveriment();

    QTimer timer;
    QObject::connect(&timer, &QTimer::timeout, &timer, []{
        static int count = 1;
        qDebug() << "This is Qt Debug message! Count:" << count++;
    });
    timer.start(1000);

    QTimer otherTimer;
    QObject::connect(&otherTimer, &QTimer::timeout, &otherTimer, []{
        static int count = 1;
        printf("This is printf stdout message! Count: %d", count++);
        fflush(stdout);
    });
    otherTimer.start(1000);

    return app.exec();
}

        效果图如下:


 【结语】

        现在有了这个函数,我们还可以动态控制打印窗口,即控制 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

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

梦起丶

您的鼓励和支持是我创作最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值