[英]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 ++调用,它接受format
和va_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,按照我的想法排列。
The declaration and definition of r_printf
differ. r_printf
的声明和定义不同。 Both must be EXPORT_C
. 两者都必须是EXPORT_C
。
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
。
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
。
For portability, you could use qvsnprintf
. 为了便于移植,可以使用qvsnprintf
。
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将是更安全的选择。
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编码一次。
Since you're now emitting a container like QString
or QByteArray
, you could factor out the log message source into a separate object. 由于现在要发出QString
或QByteArray
类的容器,因此可以将日志消息源分解为一个单独的对象。 It could be connected to zero or more views, then. 然后,它可以连接到零个或多个视图。
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. 这样可以避免每次添加日志时都需要重新解析整个日志。
The QPlainTextEdit
is abysmally slow and unsuitable for logging. 该QPlainTextEdit
是一败涂地缓慢,不适合用于记录。 You should use a QListView
or a custom widget instead. 您应该改用QListView
或自定义小部件。
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: 这是一个例子:
#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
#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);
}
// 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.