Category Archives: Strangeness

Qt——QThread

事件驱动与事件循环

1、QT是事件驱动。在建立工程时,main.cpp如下部分代码,QAplication::exec()即启动了事件循环,每一个信号被当做事件来排队处理。

    QApplication a(argc, argv);
    /*...*/
    return a.exec();

2、模态对话框和事件循环参考这里
1)QWidget有非模态和模态两种展示方式
即QWidget::show() & QWidget::exec()。
2)实际上,QWidget::exec() 就是 setAttribute+show()+eventloop
3)QDialog继承自QWidget,对于模态对话框,会启用自己的事件循环。此时,主线程的事件循环阻塞,等待模态对话框的事件循环结束后,解除阻塞。
4)模态对话框的事件循环不会在单独的子线程执行,因此,事件循环跟线程没有必然联系。

QThread

1、QThread子类成员函数的运行线程
继承自QThread的类,在使用过程中,对于不在run()函数内执行的成员函数,比如一些槽函数,一般是在主线程运行(因为QThread子类本身一般是在主线程)。如何使其在次线程执行?
首先,明白一下几点
1)QThread负责管理线程,管理的线程(子线程)和依附的线程不同。
2)QThread::run()如同main(),运行run则开启线程、结束run则结束线程。
3)对于信号与槽,connect函数强调发送信号的线程和接受者依附的线程。
connect函数的最后一个参数:队列连接(槽函数在接受者依附的线程执行)、直接连接(槽函数在信号发射的线程执行)…

然后,想要成员函数在次线程运行
1)正常创建QThread对象。
2)把需要的槽或者函数写成一个类(就叫Class吧),继承自QObject。
3)把这个Class类的实例化对象转移到次线程。

class Class: public QObject

QThread t
Class c
c.moveToThread(&t)

2、跨线程指针的new和delete
对于上述实例化的对象(上述的c)中,如果需要给指针new一个空间,不能在构造函数中new,同样也不能在析构函数中delete。
因为指针的使用是在次线程,而构造和析构是在主线程完成,当关闭程序时,会报错(Cannot send events to objects owned by a different thread)。
解决方法是将指针的new和delete放到类(上述的Class)的槽函数中,通过主线程发送信号实现构造和释放。看这里。
要注意的是要确保delete在线程quit之前,如果这两个动作一前一后执行,比如下面这样,建议connect方式用Qt::BlockingQueuedConnection

    emit toDelete();
    t.quit();

3、跨线程信号与槽问题1——次线程的槽不执行
上面的解决方法会涉及到跨线程的信号和槽,一般我们在connect时可以通过Qt::AutoConnection或者Qt::QueuedConnection的方式实现跨线程的连接。
理论上,这就足够了,但实际上,尽管使用Qt::QueuedConnection,仍可能出现主程序的信号无法触发从线程中QObject子类对象的槽。
原因参考这里。
1)只有当从线程的消息循环正在运行的时候,才能正常接收信号或者说事件。
2)QThread的exec会开消息循环,调用start接口默认会执行exec,因此也会开消息循环。
3)start启线程后是否执行exec取决于run接口的实现,默认是调exec;如果重写了run接口,修改了默认行为,不再执行exec,也就不会运行消息循环。

4、跨线程信号与槽问题2——如何在次线程connect
把主线程的对象的指针通过信号发送到次线程。

注意!!!次线程内部的信号槽的连接建议用Qt::DirectConnection。

参考

1、https://blog.csdn.net/lynfam/article/details/7081757
2、https://blog.csdn.net/dbzhang800/article/details/6300416
3、https://www.cnblogs.com/judes/p/12943716.html
4、https://blog.csdn.net/u011388696/article/details/107854759

Qt——信号与槽的原理

QT元对象系统

QT元对象系统的三个重要组成包括:
1)QObject类(所有使用元对象系统的基类)
2)Q_OBJECT(所在类可以使用元对象特性)
3)MOC(meta-object-compiler,元对象编译器,预处理包含Q_OBJECT宏的类)

整体的编译过程

首先进行MOC预处理,Q_OBJECT宏将展开和自动生成一些变量定义、函数声明等代码,同时生成moc_xxx.cpp,之后进行普通编译。

信号与槽的预处理:看成普通函数

1)在MOC预处理过程中,Q_OBJECT将展开,类中定义的信号和槽也被处理。
2)处理后会将信号与槽都作为可回调的函数,根据id调用,函数及其id的定义在Q_OBJECT展开的函数qt_static_metacall中,如实例1
3)其区别是在不同的关键字(signals,slots)声明下,信号的实现由moc完成,如实例2,而槽函数的实现需要自己完成。

信号与槽的关联:构建Connection对象

1)connect函数构造了一个Connection对象,将接发对象、函数(信号槽)的id、连接方式等保存在发送者内存,如实例3。
2)而发送者将内存中的Connection对象保存在QObjectConnectionListVector中,QObjectConnectionListVector是个数组,通过信号id索引。
3)一个信号可以连接多个槽,则信号id索引的内容是数组某个位置的ConnectionList。

信号的触发与槽的执行:执行函数回调

1)执行emit,则相当于调用QMetaObject::activate函数,此时通过信号id,获得ConnectionList。
2)对于直连的信号槽,会直接执行槽函数id对应的函数;
3)对于队列连接的槽函数,将通过QMetaObject::queued_activate,抛出QMetaCallEvent事件,把Connection扔到接受者所在线程,在接受者所在线程的事件循环中执行,如实例4。

参考

1、https://www.cnblogs.com/swarmbees/p/10816139.html

实例

实例1

void MasterConsole::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        auto *_t = static_cast<MasterConsole *>(_o);
        Q_UNUSED(_t)
        switch (_id) {
        case 0: _t->publishOrder((*reinterpret_cast< int(*)>(_a[1])),(*reinterpret_cast< QString(*)>(_a[2]))); break;
        case 1: _t->toBuilder(); break;
        case 2: _t->toDeleter(); break;
        case 3: _t->on_btnTestStartDistribution_clicked(); break;
        default: ;
        }
//...
}

实例2

// SIGNAL 1
void MasterConsole::toBuilder()
{
    QMetaObject::activate(this, &staticMetaObject, 1, nullptr);
}

实例3

QScopedPointer<QObjectPrivate::Connection> c(new QObjectPrivate::Connection);
c->sender = s;                              //发送者
c->signal_index = signal_index;             //信号索引
c->receiver = r;                            //接收者
c->method_relative = method_index;          //槽函数索引
c->method_offset = method_offset;           //槽函数偏移 主要是区别于多个信号
c->connectionType = type;                   //连接类型
c->isSlotObject = false;                    //是否是槽对象 默认是true
c->argumentTypes.store(types);              //参数类型
c->nextConnectionList = 0;                  //指向下个连接对象
c->callFunction = callFunction;             //静态回调函数,也就是qt_static_metacall

QObjectPrivate::get(s)->addConnection(signal_index, c.data());

实例4

void QMetaObject::activate(QObject *sender, int signalOffset ......)
{
    . . . . . .
    //发送方和接收方是否在同一个线程中
        const bool receiverInSameThread =  currentThreadId == receiver->d_func()->threadData->threadId;
         //决定连接立刻发送还是加入事件队列
            if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
                || (c->connectionType == Qt::QueuedConnection))
                {
                queued_activate(sender, signal_index, c, argv ? argv : empty_argv, locker);
                continue;
            } else if
//...
}

static void queued_activate(QObject *sender, int signal, QObjectPrivate::Connection *c, void **argv,  QMutexLocker &locker)
{
  ...
     QMetaCallEvent *ev = c->isSlotObject ?
     new QMetaCallEvent(c->slotObj, sender, signal, nargs, types, args) :
     new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction, 
                        sender, signal, nargs, types, args);

    //post事件到接收者所属线程的QThreadData::postEventList队列中
     QCoreApplication::postEvent(c->receiver, ev);
  ...
}

实例5:一个简单的moc_xxx.cpp文件

/****************************************************************************
** Meta object code from reading C++ file 'masterconsole.h'
**
** Created by: The Qt Meta Object Compiler version 67 (Qt 5.12.6)
**
** WARNING! All changes made in this file will be lost!
*****************************************************************************/

#include "../../SandTable_MasterConsole/masterconsole.h"
#include <QtCore/qbytearray.h>
#include <QtCore/qmetatype.h>
#if !defined(Q_MOC_OUTPUT_REVISION)
#error "The header file 'masterconsole.h' doesn't include <QObject>."
#elif Q_MOC_OUTPUT_REVISION != 67
#error "This file was generated using the moc from 5.12.6. It"
#error "cannot be used with the include files from this version of Qt."
#error "(The moc has changed too much.)"
#endif

QT_BEGIN_MOC_NAMESPACE
QT_WARNING_PUSH
QT_WARNING_DISABLE_DEPRECATED
struct qt_meta_stringdata_MasterConsole_t {
    QByteArrayData data[12];
    char stringdata0[235];
};
#define QT_MOC_LITERAL(idx, ofs, len) \
    Q_STATIC_BYTE_ARRAY_DATA_HEADER_INITIALIZER_WITH_OFFSET(len, \
    qptrdiff(offsetof(qt_meta_stringdata_MasterConsole_t, stringdata0) + ofs \
        - idx * sizeof(QByteArrayData)) \
    )
static const qt_meta_stringdata_MasterConsole_t qt_meta_stringdata_MasterConsole = {
    {
QT_MOC_LITERAL(0, 0, 13), // "MasterConsole"
QT_MOC_LITERAL(1, 14, 12), // "publishOrder"
QT_MOC_LITERAL(2, 27, 0), // ""
QT_MOC_LITERAL(3, 28, 2), // "id"
QT_MOC_LITERAL(4, 31, 5), // "order"
QT_MOC_LITERAL(5, 37, 9), // "toBuilder"
QT_MOC_LITERAL(6, 47, 9), // "toDeleter"
QT_MOC_LITERAL(7, 57, 35), // "on_btnTestStartDistribution_c..."
QT_MOC_LITERAL(8, 93, 35), // "on_btnTestCloseDistribution_c..."
QT_MOC_LITERAL(9, 129, 33), // "on_btnTestStartManagement_cli..."
QT_MOC_LITERAL(10, 163, 33), // "on_btnTestCloseManagement_cli..."
QT_MOC_LITERAL(11, 197, 37) // "on_btnTestFunction_singleCar1..."

    },
    "MasterConsole\0publishOrder\0\0id\0order\0"
    "toBuilder\0toDeleter\0"
    "on_btnTestStartDistribution_clicked\0"
    "on_btnTestCloseDistribution_clicked\0"
    "on_btnTestStartManagement_clicked\0"
    "on_btnTestCloseManagement_clicked\0"
    "on_btnTestFunction_singleCar1_clicked"
};
#undef QT_MOC_LITERAL

static const uint qt_meta_data_MasterConsole[] = {

 // content:
       8,       // revision
       0,       // classname
       0,    0, // classinfo
       8,   14, // methods
       0,    0, // properties
       0,    0, // enums/sets
       0,    0, // constructors
       0,       // flags
       3,       // signalCount

 // signals: name, argc, parameters, tag, flags
       1,    2,   54,    2, 0x06 /* Public */,
       5,    0,   59,    2, 0x06 /* Public */,
       6,    0,   60,    2, 0x06 /* Public */,

 // slots: name, argc, parameters, tag, flags
       7,    0,   61,    2, 0x08 /* Private */,
       8,    0,   62,    2, 0x08 /* Private */,
       9,    0,   63,    2, 0x08 /* Private */,
      10,    0,   64,    2, 0x08 /* Private */,
      11,    0,   65,    2, 0x08 /* Private */,

 // signals: parameters
    QMetaType::Void, QMetaType::Int, QMetaType::QString,    3,    4,
    QMetaType::Void,
    QMetaType::Void,

 // slots: parameters
    QMetaType::Void,
    QMetaType::Void,
    QMetaType::Void,
    QMetaType::Void,
    QMetaType::Void,

       0        // eod
};

void MasterConsole::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        auto *_t = static_cast<MasterConsole *>(_o);
        Q_UNUSED(_t)
        switch (_id) {
        case 0: _t->publishOrder((*reinterpret_cast< int(*)>(_a[1])),(*reinterpret_cast< QString(*)>(_a[2]))); break;
        case 1: _t->toBuilder(); break;
        case 2: _t->toDeleter(); break;
        case 3: _t->on_btnTestStartDistribution_clicked(); break;
        case 4: _t->on_btnTestCloseDistribution_clicked(); break;
        case 5: _t->on_btnTestStartManagement_clicked(); break;
        case 6: _t->on_btnTestCloseManagement_clicked(); break;
        case 7: _t->on_btnTestFunction_singleCar1_clicked(); break;
        default: ;
        }
    } else if (_c == QMetaObject::IndexOfMethod) {
        int *result = reinterpret_cast<int *>(_a[0]);
        {
            using _t = void (MasterConsole::*)(int , QString );
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MasterConsole::publishOrder)) {
                *result = 0;
                return;
            }
        }
        {
            using _t = void (MasterConsole::*)();
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MasterConsole::toBuilder)) {
                *result = 1;
                return;
            }
        }
        {
            using _t = void (MasterConsole::*)();
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&MasterConsole::toDeleter)) {
                *result = 2;
                return;
            }
        }
    }
}

QT_INIT_METAOBJECT const QMetaObject MasterConsole::staticMetaObject = { {
    &QMainWindow::staticMetaObject,
    qt_meta_stringdata_MasterConsole.data,
    qt_meta_data_MasterConsole,
    qt_static_metacall,
    nullptr,
    nullptr
} };

const QMetaObject *MasterConsole::metaObject() const
{
    return QObject::d_ptr->metaObject ? QObject::d_ptr->dynamicMetaObject() : &staticMetaObject;
}

void *MasterConsole::qt_metacast(const char *_clname)
{
    if (!_clname) return nullptr;
    if (!strcmp(_clname, qt_meta_stringdata_MasterConsole.stringdata0))
        return static_cast<void*>(this);
    return QMainWindow::qt_metacast(_clname);
}

int MasterConsole::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
    _id = QMainWindow::qt_metacall(_c, _id, _a);
    if (_id < 0)
        return _id;
    if (_c == QMetaObject::InvokeMetaMethod) {
        if (_id < 8)
            qt_static_metacall(this, _c, _id, _a);
        _id -= 8;
    } else if (_c == QMetaObject::RegisterMethodArgumentMetaType) {
        if (_id < 8)
            *reinterpret_cast<int*>(_a[0]) = -1;
        _id -= 8;
    }
    return _id;
}

// SIGNAL 0
void MasterConsole::publishOrder(int _t1, QString _t2)
{
    void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)), const_cast<void*>(reinterpret_cast<const void*>(&_t2)) };
    QMetaObject::activate(this, &staticMetaObject, 0, _a);
}

// SIGNAL 1
void MasterConsole::toBuilder()
{
    QMetaObject::activate(this, &staticMetaObject, 1, nullptr);
}

// SIGNAL 2
void MasterConsole::toDeleter()
{
    QMetaObject::activate(this, &staticMetaObject, 2, nullptr);
}
QT_WARNING_POP
QT_END_MOC_NAMESPACE

Qt——win10下程序打包发布

写这篇博客是因为终于成功打包出去一个程序,之前放到其他设备上总是缺这少那…

ok,开始记录….

Step 1 release exe文件

这一步比较简单,把编译选项从debug模式改成release模式,然后,重新编译运行一遍。会在编译文件夹下的release文件夹内生成那个exe文件。

Step 2 使用windeployqt.exe打包到文件夹

  1. 将第一步生成的exe文件,新建个文件夹,把它放进去,文件夹的位置似乎没有特别的要求,就像这样↓
    在这里插入图片描述

  2. 打开终端(Windows powershell,就用管理员那个),然后把目录定位到刚刚建立的文件夹
    在这里插入图片描述
  3. 运行windeployqt.exe进行打包,这里要知道自己的windeployqt.exe在哪,一般在自己的Qt安装目录
    在这里插入图片描述
  4. 执行,然后在那个新建的文件夹里就多了一堆文件和夹
    在这里插入图片描述
  5. 这种事情怎么能没有一键操作呢,编写一个bat文件,双击执行即可(bat文件似乎不能编辑,可以先建立一个txt文件,然后修改文件类型就ok)(喔,好像也没有很一键)
    cmd /k "cd /d [windeployqt路径] && windeployqt.exe [自己exe路径]\xxx.exe

    cmd /k "cd /d D:\Qt5.12.6\5.12.6\msvc2017_64\bin\ && windeployqt.exe C:\Users\25834\Desktop\blog_pkg\xxx.exe

    Step 3 打包成一个文件

  6. 下载打包工具 Enigma Virtual Box
    在这里插入图片描述

  7. 选择自己的exe文件,并把整个文件添加到目录树,移除目录树中的exe
    在这里插入图片描述
    在这里插入图片描述

  8. 可以选择是否压缩,最后执行封包,结束后,可以运行看看
    在这里插入图片描述
    在这里插入图片描述
    (选择压缩,则生成的程序启动的时候要先解包,因此时间慢,不压缩,程序就会比较大,可以权衡。)

(我也加一个,亲测可用…)

参考

  1. 【win】【qt5打包】【qt程序打包成一个可执行文件(带图标任何win都可以运行哦)】