简体   繁体   English

Qt日志记录工具多线程,使用可变数量的参数调用信号和插槽形成另一个线程,混合了C和C ++

[英]Qt Logging tool multithreading, calling signal and slot with variable number of arguments form another thread, mixing C and C++

This is my Logging class: 这是我的日志记录类:

#include "log.h"
#include "ui_log.h"

#include <QDebug>
#include <QMutex>


bool Log::logOpen = false;

Log::Log(QWidget *parent) : QDialog(parent), ui(new Ui::Log)
{
    ui->setupUi(this);
    text = new QString;
    this->bar = this->ui->logText->verticalScrollBar();
    connect(this, SIGNAL(call_write(char*)), this, SLOT(write(char*)));
}

Log::~Log()
{
    delete ui;
}

void Log::on_buttonClose_clicked()
{
    Log::logOpen = false;
    close();
}

void Log::on_buttonClear_clicked()
{
    this->text->clear();
    this->ui->logText->clear();
}

void Log::write(char *msg)
{
    this->text->append(msg);
    this->ui->logText->setPlainText(*this->text);
    this->bar->setValue(bar->maximum());
    free(msg);
}

void write_c(Log *ptr, char *msg) {
    emit ptr->call_write(msg);
}

void r_printf(char *format, ...) {
    char *buf = (char*) malloc(4096);
    va_list argList;
    va_start(argList, format);
    vsnprintf(buf, 4096,format, argList);
    va_end(argList);
    write_c(logptr, buf);
}

This is, by far, THE most complicated piece of code I have ever written. 到目前为止,这是我编写过的最复杂的代码。 This is how it works: r_printf(char *format, ...) can be called from either C or C++, it takes the format and va_list arguments, and formats them. 它是这样工作的: r_printf(char *format, ...)可以从C或C ++调用,它接受formatva_list参数,并对它们进行格式化。 Then, it calls write_c , which sends a signal the Logger, which is connected to itself, so the Qt scheduler schedules an update to the window by calling write() , so that Qt does not freak out. 然后,它调用write_c ,该信号发送一个与自身连接的Logger信号,因此Qt调度程序通过调用write()来调度对窗口的更新,以使Qt不会发疯。 Crazy, right? 疯狂吧?

Here is the log header, log.h 这是日志标题log.h

#ifndef LOG_H
#define LOG_H

#include <stdarg.h>

#ifdef __cplusplus

#include <QDialog>
#include <QString>
#include <QScrollBar>

namespace Ui {
class Log;
}

class Log : public QDialog
{
    Q_OBJECT

public:
    static bool logOpen;
    explicit Log(QWidget *parent = 0);

    ~Log();
    Ui::Log *ui;
    QString *text;
    QScrollBar *bar;

private slots:
    void on_buttonClose_clicked();
    void on_buttonClear_clicked();
    void write(char *msg);

signals:
    void call_write(char *msg);

};

#else

typedef struct Log Log;

#endif // __cplusplus

#ifdef __cplusplus
#define EXPORT_C extern "C"
#else
#define EXPORT_C
#endif

extern Log *logptr;

EXPORT_C void write_c(Log *ptr, char *msg);
EXPORT_C void r_printf(char *format, ...);

#endif // LOG_H

logptr is the pointer to a Log object inside the Main window. logptr是“主”窗口中指向Log对象的指针。

While this does work, and it does not corrupt memory, is there a better solution to this huge mess? 尽管这确实有效,并且不会破坏内存,但是对于这种混乱情况,是否有更好的解决方案? Is this a good solution? 这是一个好的解决方案吗? Can it be improved? 可以改善吗?

The goal is to have something like printf(char *format, ...) which can be called from either thread, from either C or C++. 目的是要有类似printf(char *format, ...) ,可以从C或C ++的任一线程中调用。 It should always work. 它应该总是有效。

Here are my nitpicks, arranged in the order of how I thought about it. 这是我的nitpicks,按照我的想法排列。

  1. The declaration and definition of r_printf differ. r_printf的声明和定义不同。 Both must be EXPORT_C . 两者都必须是EXPORT_C

  2. EXPORT_C is likely to clash with some errant library code. EXPORT_C可能与某些错误的库代码冲突。 Prefer a more unique name, like LOG_H_EXPORT_C . 建议使用更唯一的名称,例如LOG_H_EXPORT_C

  3. A signal can have zero or more recipients. 一个信号可以有零个或多个接收者。 The message allocated in r_printf can in principle leak or be multiply deleted. r_printf中分配的消息原则上可以泄漏或被r_printf删除。 There's no reason to do manual memory management here, use a shared pointer or an implicitly shared data structure like QByteArray instead. 这里没有理由进行手动内存管理,而是使用共享指针或隐式共享数据结构(例如QByteArray

  4. For portability, you could use qvsnprintf . 为了便于移植,可以使用qvsnprintf

  5. You're allocating a huge buffer. 您正在分配一个巨大的缓冲区。 This is a tradeoff between allocation size and performance. 这是分配大小和性能之间的权衡。 You have an option of calling qvsnprintf with zero size to get the needed buffer size, then allocate a correctly-sized buffer, and call qvsnprintf again. 您可以选择以零大小调用qvsnprintf以获取所需的缓冲区大小,然后分配正确大小的缓冲区,然后再次调用qvsnprintf You'd need to profile this to make an informed choice. 您需要对此进行概要分析,以做出明智的选择。 At the very least, don't allocate a page-sized buffer since on some platforms this pessimizes and allocates more than a page, at a 100% overhead. 至少, 不要分配页面大小的缓冲区,因为在某些平台上,这会造成100%的开销,而不是一个页面。 A 0xFE0 size would be a safer bet. 大小为0xFE0将是更安全的选择。

  6. Prefer QString::asprintf , and simply pass a QString through the slots. 最好使用QString::asprintf ,只需将QString通过插槽即可。 This guarantees that the string will be converted from 8-bit to UTF-16 encoding only once. 这样可以保证将字符串从8位编码转换为UTF-16编码一次。

  7. Since you're now emitting a container like QString or QByteArray , you could factor out the log message source into a separate object. 由于现在要发出QStringQByteArray类的容器,因此可以将日志消息源分解为一个单独的对象。 It could be connected to zero or more views, then. 然后,它可以连接到零个或多个视图。

  8. Do not reset the log text. 不要重置日志文本。 Instead, use QPlainText::appendPlainText . 而是使用QPlainText::appendPlainText This will avoid the need to re-parse the entire log every time you add to it. 这样可以避免每次添加日志时都需要重新解析整个日志。

  9. The QPlainTextEdit is abysmally slow and unsuitable for logging. QPlainTextEdit一败涂地缓慢,不适合用于记录。 You should use a QListView or a custom widget instead. 您应该改用QListView或自定义小部件。

  10. You may wish to keep the log scrolled to the bottom if it already is so. 您可能希望将日志滚动到底部(如果已经这样做的话)。 See this question for details. 有关详细信息,请参见此问题

Here's an example: 这是一个例子:

该示例的屏幕截图

Log.h Log.h

#ifndef LOG_H
#define LOG_H

#ifdef __cplusplus
#include <QObject>
class Log : public QObject {
    Q_OBJECT
public:
    /// Indicates that a new message is available to be logged.
    Q_SIGNAL void newMessage(const QString &);
    /// Sends a new message signal from the global singleton. This method is thread-safe.
    static void sendMessage(const QString &);
    /// Returns a global singleton. This method is thread-safe.
    static Log * instance();
};
#define LOG_H_EXPORT_C extern "C"
#else
#define LOG_H_EXPORT_C
#endif

LOG_H_EXPORT_C void r_printf(const char * format, ...);

#endif // LOG_H

Log.cpp Log.cpp

#include "Log.h"
#include <cstdarg>

Q_GLOBAL_STATIC(Log, log)

Log * Log::instance() { return log; }

void Log::sendMessage(const QString & msg) {
    emit log->newMessage(msg);
}

LOG_H_EXPORT_C void r_printf(const char * format, ...) {
    va_list argList;
    va_start(argList, format);
    auto msg = QString::vasprintf(format, argList);
    va_end(argList);
    Log::sendMessage(msg);
}

main.cpp main.cpp中

// https://github.com/KubaO/stackoverflown/tree/master/questions/simplelog-38793887
#include <QtWidgets>
#include <QtConcurrent>
#include "Log.h"

int main(int argc, char ** argv) {
   using Q = QObject;
   QApplication app{argc, argv};
   QStringListModel model;
   Q::connect(Log::instance(), &Log::newMessage, &model, [&](const QString & msg) {
      auto row = model.rowCount();
      model.insertRow(row);
      model.setData(model.index(row), msg);
   });
   QWidget w;
   QVBoxLayout layout{&w};
   QListView view;
   bool viewAtBottom = false;
   QPushButton clear{"Clear"};
   layout.addWidget(&view);
   layout.addWidget(&clear);
   Q::connect(&clear, &QPushButton::clicked,
              &model, [&]{ model.setStringList(QStringList{}); });
   view.setModel(&model);
   view.setUniformItemSizes(true);
   Q::connect(view.model(), &QAbstractItemModel::rowsAboutToBeInserted, &view, [&] {
      auto bar = view.verticalScrollBar();
      viewAtBottom = bar ? (bar->value() == bar->maximum()) : false;
   });
   Q::connect(view.model(), &QAbstractItemModel::rowsInserted,
              &view, [&]{ if (viewAtBottom) view.scrollToBottom(); });

   QtConcurrent::run([]{
      auto delay = 10;
      for (int ms = 0; ms <= 500; ms += delay) {
         r_printf("%d ms", ms);
         QThread::msleep(ms);
      }
   });
   w.show();
   return app.exec();
}

Kuba's answer didn't compile for me, probably due to a name collision with the mathematical function log() . 库巴的答案没有为我编译,可能是由于与数学函数log()的名称冲突。 I got it to compile and run by replacing log in Log.cpp with eg global_log_instance . 我通过将Log.cpp log Log.cpp为例如global_log_instance来进行编译和运行。

Note I tested using Qt 5.6.1 on Ubuntu 16.04, with this .pro-file: 注意我在Ubuntu 16.04上使用Qt 5.6.1通过以下.pro文件进行了测试:

QT += widgets concurrent
SOURCES += main.cpp Log.cpp
HEADERS += Log.h

I mention this because as perhaps Kuba was intending to use some library setting or flag to avoid conflict with the log() -function. 我之所以这样说,是因为也许Kuba打算使用某些库设置或标志来避免与log()函数冲突。

Note: Something is strange with the solution though. 注意:解决方案有些奇怪。 Eg, if exiting the window early, it still takes about 12 seconds in total for the prompt to return. 例如,如果提早退出窗口,则返回提示仍然总共需要12秒钟。 Further, log messages are visibly being added at a slower and slower rate. 此外,显然以越来越慢的速率添加日志消息。 On my system I do get a warning about "IBUS", maybe that's related somehow. 在我的系统上,我确实收到有关“ IBUS”的警告,也许这与某种原因有关。

$ time ./main

(main:11094): IBUS-WARNING **: Unable to connect to ibus: Could not connect: Connection refused

real    0m12.825s
user    0m0.168s
sys     0m0.012s

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM