Qt: 信号与槽机制

2023-11-16

一、信号和槽机制是什么?


        1 Qt信号槽机制:是Qt的核心机制,它是Qt定义的一种对象间的通讯机制,且独立于标准C/C++语言。

        2 信号(signals):当某个类对象发生内部改变时,发射“信号”随后与关联的“槽函数”被立即执行,“信号槽机制”完全独立于任何GUI事件循环机制

                (1)信号可以链接普通函数

                (2)如果存在一个信号对应多个槽函数,则槽函数调用顺序与connect顺序相同

     3 槽函数(slots):等同于类普通成员函数

     4 信号槽数量:单信号可以对应多个槽函数,单个槽函数也可以对应多个信号,互为一对多,多对一的关系

     5 信号槽连接(connect):注册信号与槽函数对其进行绑定,实现存储和调度

     6 应用场景

        (1)单线程:相当于函数指针调用

        (2)多线程:信号触发时,发送者线程将槽函数的调用转化为一次“调用事件”放入事件循环队列,接收者线程执行到下一次事件处理时,处理“调用事件”,调用相应的函数。

      7 使用注意:

                (1)使用方式灵活但是存在效率上的开销(损耗相对较少),不能像真正的“回调函数”高效

                (2)参数列表中不能使用宏定义和函数指针

                (3)参数列表中不能使用缺省值

                (4)参数列表中不能使用模版类

                (5)signals前不能设置访问权限(public private protected)且返回执行必须是void ,slots可以设置访问权限 返回值类型void

                 (6) signals和slots区域内不能含有:构造函数,内部类(嵌套类)、友元声明(类或函数)

                (7)signals 的参数类型和参数个数 == 对应的slots的参数类型和参数个数

        8 优缺点

                优点

                        1 类型安全,相关联的信号槽签名等同(signals 的参数类型和参数个数 == 对应的slots的参数类型和参数个数)

                        2 松耦合,削弱QT对象之间的耦合度(两个对象都互相不知道对方的存在,依然可以建立联系,关联运行时删除,程序不崩溃)

                缺点

                        1 效率:信号触发到槽函数调用比直接调用非虚函数速度慢10倍

        9 特征

                1 信号槽是观察者模式的一种实现

                2 一个信号就是一个能够被观察的事件,或者至少是事件已经发送的一种通知

                3 一个槽就是一个观察者,通常就是在被观察的对象发生变化的时候,被调用的函数

       10 信号槽本质上是两个对象的函数地址映射

 观察者设计模式 

        

耦合度:

        1 模块和模块之间信息或者参数的的依赖程度

        2 一般而言,低耦合性程序可读性可维护性比较好

详细介绍:设计模式:观察者模式(Observer)_~怎么回事啊~的博客-CSDN博客 

连接方式和类型

标准连接方式

1 语法

	connect(this, SIGNAL(signal_test1()) , this, SLOT(slot_test1()));

2 原理:将信号槽的函数名称按照相应规则组合成字符串,通过connect解析该字符串,分别获取信号槽的绝对索引,然后将信号槽的链接关系,添加到信号槽容器中

3 注意事项:所有检查都在运行时,通过解析字符串进行,如果字符串拼写错误却编译成功,错误则创建的是空链接,在运行时报错

4 特性

        1 不可以调用普通成员函数

        2 支持使用重载信号&&槽函数

5 链接类型

        1 默认 Qt::AutoConnection 直接链接

        2 设置:可手动设置

6 内部实现:QObject::connnect

函数指针连接方式(qt5及以上)

1语法

	connect(this,&qttest::signal_test1,this,&qttest::slot_test1);

2 原理:编译时检查,使用函数指针作为信号槽函数

3 注意事项编译时如果信号槽出现拼写错误则无法通过编译

4 特性

        1 调用逻辑层次分明,在编译时检查避免程序运行时错

        2 可以调用普通成员函数,槽函数

        3 不能定义重载信号和槽函数,否则无法识别

5 解决信号重载的方式,不能解决槽函数和类普通成员函数重载

        使用QOverload

6 链接类型

        1 默认 Qt::AutoConnection 直接链接

        2 设置:可手动设置

7 内部实现

        template<typename Func1,typename Func2>

        static inline QMeataObjectConnection connnet

lambda表达式连接方式

1 语法:

	connect(this, &qttest::signal_test1, [this]() {slot_test1(); });

2 原理:编译时检查,使用函数指针作为信号槽函数,编译时如果信号槽出现拼写错误则无法通过编译,lambda表达式发挥的是槽函数的函数指针

3 注意事项:同函数指针

4 特性

        1 支持槽函数重载

        2 不支持信号重载

5 解决

          使用QOverload

6 链接类型

        1 默认 Qt::AutoConnection 直接链接

        2 设置:可手动设置

连接类型

enum ConnectionType {
	AutoConnection,//默认
	DirectConnection,//立即调用
	QueuedConnection,//放在接收者队列
	BlockingQueuedConnection,//与放在接收者队列,同时阻塞发送者
	UniqueConnection =  0x80//标志位,避免重复链接
};

1、Qt::AutoConnection
        一般信号槽不会写第五个参数,其实使用的默认值,使用这个值则连接类型会在信号发送时决定。
        如果接收者和发送者在同一个线程,则自动使用Qt::DirectConnection类型。
        如果接收者和发送者不在一个线程,则自动使用Qt::QueuedConnection类型。
2、Qt::DirectConnection
        槽函数会在信号发送的时候直接被调用,槽函数运行于信号发送者所在线程,有点类似于回调函数。
        效果看上去就像是直接在信号发送位置调用了槽函数。这个在多线程环境下比较危险,可能会造成奔溃。

3、Qt::QueuedConnection:
        槽函数在控制回到接收者所在线程的事件循环时被调用,槽函数运行于信号接收者所在线程。发送信号之后,槽函数不会立刻被调用,等到接收者的当前函数执行完,进入事件循环之后,槽函数才会被调用。多线程环境下一般用这个。

4、Qt::BlockingQueuedConnection
        槽函数的调用时机与Qt::QueuedConnection一致,不过发送完信号后发送者所在线程会阻塞,直到槽函数运行完。使用该连接方式,接收者和发送者绝对不能在一个线程,否则程序会死锁!!!在多线程间需要同步的场合可能需要这个。

5、Qt::UniqueConnection
        这个flag可以通过按位或(|)与以上四个结合在一起使用。当这个flag设置时,当某个信号和槽已经连接时,再进行重复的连接就会失败。也就是避免了重复连接。


二、怎样使用信号和槽机制?


        在使用信号与槽的类中,必须在类的定义中加入宏 Q_OBJECT;
        当一个信号被发射时,与其关联的槽函数通常被立即执行,就像正常调用一个函数一样。只有当信号关联的所有槽函数执行完毕后,才会执行发射信号处后面的代码。

信号和槽是什么?

在类中定义两个信号和两个槽

#pragma once

#include <QtWidgets/QWidget>
#include "ui_qttest.h"

class qttest : public QWidget
{
	Q_OBJECT

public:
	qttest(QWidget *parent = Q_NULLPTR);
signals:
	void signal_test1();
	void signal_test2(int index);
public slots:
	void slot_test1() {};
	void slot_test2(int index) {};
private:
	Ui::qttestClass ui;
};

构造函数中写个connnect:

#include "qttest.h"

qttest::qttest(QWidget *parent)
	: QWidget(parent)
{
	ui.setupUi(this);

	connect(this,&qttest::signal_test1,this,&qttest::slot_test1);//qt5
	connect(this, SIGNAL(signal_test1()) , this, SLOT(slot_test1()));//qt4
}

在这里,我使用qt5的信号与槽连接方式和qt4的信号与槽连接方式

通过SLOT 和SIGNAL的宏定义可以看出,qt4中信号是 :字符串1连接信号的函数名;槽是:字符串2连接函数名。

# define SLOT(a)     qFlagLocation("1"#a QLOCATION)
# define SIGNAL(a)   qFlagLocation("2"#a QLOCATION)

打开vs的预编译到文件:

重新编译后:

之后进入debug目录(我是debug编译)

打开qtttest.i文件,可以看到如下内容

qttest::qttest(QWidget *parent)
	: QWidget(parent)
{
	ui.setupUi(this);

	connect(this,&qttest::signal_test1,this,&qttest::slot_test1);
	connect(this, qFlagLocation("2""signal_test1()" "\0" "c:\\users\\admin\\source\\repos\\qttest\\qttest\\qttest.cpp" ":" "9") , this, qFlagLocation("1""slot_test1()" "\0" "c:\\users\\admin\\source\\repos\\qttest\\qttest\\qttest.cpp" ":" "9"));
}

      使用qt4方式的 信号与槽宏定义被展开成: 1/2 + 函数名 + 文件名  + 行号

连接方式1 SIGNAL SLOT

C:\Qt\5.9.8\Src\qtbase\src\corelib\kernel\qobject.h

    static QMetaObject::Connection connect(const QObject *sender, const char *signal,
                        const QObject *receiver, const char *member, Qt::ConnectionType = Qt::AutoConnection);//连接方式1 SIGNAL SLOT

    static QMetaObject::Connection connect(const QObject *sender, const QMetaMethod &signal,
                        const QObject *receiver, const QMetaMethod &method,
                        Qt::ConnectionType type = Qt::AutoConnection);//qt5

    inline QMetaObject::Connection connect(const QObject *sender, const char *signal,
                        const char *member, Qt::ConnectionType type = Qt::AutoConnection) const;//lamda

分析第一种连接方式的源码:

//sender 和receiver 必须是Qobject的对象,signal 和 medthod 是字符串  连接类型默认Auto
QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal,
                                     const QObject *receiver, const char *method,
                                     Qt::ConnectionType type)
{
    // 1 检查指针是否为空
    if (sender == 0 || receiver == 0 || signal == 0 || method == 0) {
        qWarning("QObject::connect: Cannot connect %s::%s to %s::%s",
                 sender ? sender->metaObject()->className() : "(null)",
                 (signal && *signal) ? signal+1 : "(null)",
                 receiver ? receiver->metaObject()->className() : "(null)",
                 (method && *method) ? method+1 : "(null)");
        return QMetaObject::Connection(0);
    }
    QByteArray tmp_signal_name;

    // 2 检查宏指令  : 信号字符串中是否含有 "2"
    if (!check_signal_macro(sender, signal, "connect", "bind"))
        return QMetaObject::Connection(0);
    const QMetaObject *smeta = sender->metaObject();//调用信号对象的 metaObject
    const char *signal_arg = signal;
    ++signal; //skip code  跳过 字符串2 获取信号名称和参数类型
    QArgumentTypeArray signalTypes;//qt 的可变长数组
    Q_ASSERT(QMetaObjectPrivate::get(smeta)->revision >= 7);//检测moc 版本
    QByteArray signalName = QMetaObjectPrivate::decodeMethodSignature(signal, signalTypes);
    //在信号所在的静态元对象中找到信号的相对索引
    int signal_index = QMetaObjectPrivate::indexOfSignalRelative(
            &smeta, signalName, signalTypes.size(), signalTypes.constData());
    //进行容错检查
    if (signal_index < 0) {
        // check for normalized signatures
        tmp_signal_name = QMetaObject::normalizedSignature(signal - 1);
        signal = tmp_signal_name.constData() + 1;

        signalTypes.clear();
        signalName = QMetaObjectPrivate::decodeMethodSignature(signal, signalTypes);
        smeta = sender->metaObject();
        signal_index = QMetaObjectPrivate::indexOfSignalRelative(
                &smeta, signalName, signalTypes.size(), signalTypes.constData());
    }
    if (signal_index < 0) {
        err_method_notfound(sender, signal_arg, "connect");
        err_info_about_objects("connect", sender, receiver);
        return QMetaObject::Connection(0);
    }
    signal_index = QMetaObjectPrivate::originalClone(smeta, signal_index);
    signal_index += QMetaObjectPrivate::signalOffset(smeta);
   

   //3 对槽函数执行和信号相同的检测逻辑
    QByteArray tmp_method_name;
    int membcode = extract_code(method);

    if (!check_method_code(membcode, receiver, method, "connect"))
        return QMetaObject::Connection(0);
    const char *method_arg = method;
    ++method; // skip code

    QArgumentTypeArray methodTypes;
    QByteArray methodName = QMetaObjectPrivate::decodeMethodSignature(method, methodTypes);
    const QMetaObject *rmeta = receiver->metaObject();
    int method_index_relative = -1;
    Q_ASSERT(QMetaObjectPrivate::get(rmeta)->revision >= 7);
    switch (membcode) {
    case QSLOT_CODE:
        method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(
                &rmeta, methodName, methodTypes.size(), methodTypes.constData());
        break;
    case QSIGNAL_CODE:
        method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(
                &rmeta, methodName, methodTypes.size(), methodTypes.constData());
        break;
    }
    if (method_index_relative < 0) {
        // check for normalized methods
        tmp_method_name = QMetaObject::normalizedSignature(method);
        method = tmp_method_name.constData();

        methodTypes.clear();
        methodName = QMetaObjectPrivate::decodeMethodSignature(method, methodTypes);
        // rmeta may have been modified above
        rmeta = receiver->metaObject();
        switch (membcode) {
        case QSLOT_CODE:
            method_index_relative = QMetaObjectPrivate::indexOfSlotRelative(
                    &rmeta, methodName, methodTypes.size(), methodTypes.constData());
            break;
        case QSIGNAL_CODE:
            method_index_relative = QMetaObjectPrivate::indexOfSignalRelative(
                    &rmeta, methodName, methodTypes.size(), methodTypes.constData());
            break;
        }
    }

    if (method_index_relative < 0) {
        err_method_notfound(receiver, method_arg, "connect");
        err_info_about_objects("connect", sender, receiver);
        return QMetaObject::Connection(0);
    }

    //4 检查信号和槽的参数列表是否匹配
    if (!QMetaObjectPrivate::checkConnectArgs(signalTypes.size(), signalTypes.constData(),
                                              methodTypes.size(), methodTypes.constData())) {
        qWarning("QObject::connect: Incompatible sender/receiver arguments"
                 "\n        %s::%s --> %s::%s",
                 sender->metaObject()->className(), signal,
                 receiver->metaObject()->className(), method);
        return QMetaObject::Connection(0);
    }

   //5 检查信号与槽的链接方式
    int *types = 0;
    if ((type == Qt::QueuedConnection)
            && !(types = queuedConnectionTypes(signalTypes.constData(), signalTypes.size()))) {
        return QMetaObject::Connection(0);
    }

#ifndef QT_NO_DEBUG
    QMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index);
    QMetaMethod rmethod = rmeta->method(method_index_relative + rmeta->methodOffset());
    check_and_warn_compat(smeta, smethod, rmeta, rmethod);
#endif
    // 6 将信号与槽进行链接
    QMetaObject::Connection handle = QMetaObject::Connection(QMetaObjectPrivate::connect(
        sender, signal_index, smeta, receiver, method_index_relative, rmeta ,type, types));
    return handle;
}

1 断点调试,参数中的信号和槽的内容

在第一种链接方式中,信号和槽函数被处理成字符串

                   

2  check_signal_macro(sender, signal, "connect", "bind")  用于判断singal是不是以2开头:

static bool check_signal_macro(const QObject *sender, const char *signal,
                                const char *func, const char *op)
{
    int sigcode = extract_code(signal);
    if (sigcode != QSIGNAL_CODE) {
        if (sigcode == QSLOT_CODE)
            qWarning("QObject::%s: Attempt to %s non-signal %s::%s",
                     func, op, sender->metaObject()->className(), signal+1);
        else
            qWarning("QObject::%s: Use the SIGNAL macro to %s %s::%s",
                     func, op, sender->metaObject()->className(), signal);
        return false;
    }
    return true;
}

static int extract_code(const char *member)
{
    // extract code, ensure QMETHOD_CODE <= code <= QSIGNAL_CODE
    return (((int)(*member) - '0') & 0x3);
}

3 获取信号的静态元对象   const QMetaObject *smeta = sender->metaObject();

会获得对象生成的moc_qttest.cpp 文件中生成元对象

 静态元对象是通过定义Q_OBJECT宏生成的

staticMetaObject中的具体内容参见:qt 元对象 和moc原理_~怎么回事啊~的博客-CSDN博客 

 获取信号的名称和在元对象中的偏移:

     // 6 将信号与槽进行链接

/*!
    \internal
   Same as the QMetaObject::connect, but \a signal_index must be the result of QObjectPrivate::signalIndex

    method_index is relative to the rmeta metaobject, if rmeta is null, then it is absolute index

    the QObjectPrivate::Connection* has a refcount of 2, so it must be passed to a QMetaObject::Connection
 */

// sender 发送者  signal_index 信号偏移量  smeta 信号元对象
// receiver 接送者  method_index 槽函数偏移量  rmeta 槽函数元对象
QObjectPrivate::Connection *QMetaObjectPrivate::connect(const QObject *sender,
                                 int signal_index, const QMetaObject *smeta,
                                 const QObject *receiver, int method_index,
                                 const QMetaObject *rmeta, int type, int *types)
{
   //1 去除 sender 和receiver 的const 限制 使其可以读写
    QObject *s = const_cast<QObject *>(sender);
    QObject *r = const_cast<QObject *>(receiver);

   //2 在槽函数的元对象中提取槽方法的偏移量:32
    int method_offset = rmeta ? rmeta->methodOffset() : 0;
    Q_ASSERT(!rmeta || QMetaObjectPrivate::get(rmeta)->revision >= 6);

    //3 获取槽 元对象中的回调  
    QObjectPrivate::StaticMetaCallFunction callFunction =
        rmeta ? rmeta->d.static_metacall : 0;


   //4 在对信号和槽的数据进行操作之前,首先上锁保证线程安全
    QOrderedMutexLocker locker(signalSlotLock(sender),
                               signalSlotLock(receiver));

   // 5 如果连接类型是只连接一次
    if (type & Qt::UniqueConnection) {
        QObjectConnectionListVector *connectionLists = QObjectPrivate::get(s)->connectionLists;//获取发送者的信号存储容器
        //判断当前信号是否有效
        if (connectionLists && connectionLists->count() > signal_index) {
            const QObjectPrivate::Connection *c2 =
                (*connectionLists)[signal_index].first;//如果有效,获取到该信号的二级存储容器

            int method_index_absolute = method_index + method_offset;//获取槽方法的绝对索引

           //将槽方法的绝对索引在当前信号中排除是否重复已有该槽函数
            while (c2) {
                if (!c2->isSlotObject && c2->receiver == receiver && c2->method() == method_index_absolute)
                    return 0;
                c2 = c2->nextConnectionList;
            }
        }
        type &= Qt::UniqueConnection - 1;
    }

    //6 新建信号二级容器对象 并填充数据
    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;
    c->argumentTypes.store(types);
    c->nextConnectionList = 0;
    c->callFunction = callFunction;

    //7 将此容器存储到发送者的当前信号的槽函数存储列表中
    QObjectPrivate::get(s)->addConnection(signal_index, c.data());

    locker.unlock();
    QMetaMethod smethod = QMetaObjectPrivate::signal(smeta, signal_index);
    if (smethod.isValid())
        s->connectNotify(smethod);

    return c.take();
}

    //3 获取槽 元对象中的回调  

即:

 里面存有信号和槽的方法调用:

 

 函数指针和lambda表达式链接方式

//Connect a signal to a pointer to qobject member function
//1 发送者和接收者的 类型 const typename QtPrivate::FunctionPointer<Func1>::Object 
     信号和槽的类型  
    template <typename Func1, typename Func2>
    static inline QMetaObject::Connection connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal,
                                     const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot,
                                     Qt::ConnectionType type = Qt::AutoConnection)
    {

       
        typedef QtPrivate::FunctionPointer<Func1> SignalType;
        typedef QtPrivate::FunctionPointer<Func2> SlotType;

        Q_STATIC_ASSERT_X(QtPrivate::HasQ_OBJECT_Macro<typename SignalType::Object>::Value,
                          "No Q_OBJECT in the class with the signal");


        //2 对提取的信号和槽的返回值类型和参数列表进行检查
        //compilation error if the arguments does not match.
        Q_STATIC_ASSERT_X(int(SignalType::ArgumentCount) >= int(SlotType::ArgumentCount),
                          "The slot requires more arguments than the signal provides.");
        Q_STATIC_ASSERT_X((QtPrivate::CheckCompatibleArguments<typename SignalType::Arguments, typename SlotType::Arguments>::value),
                          "Signal and slot arguments are not compatible.");
        Q_STATIC_ASSERT_X((QtPrivate::AreArgumentsCompatible<typename SlotType::ReturnType, typename SignalType::ReturnType>::value),
                          "Return type of the slot is not compatible with the return type of the signal.");


       // 3 判断连接类型
        const int *types = Q_NULLPTR;
        if (type == Qt::QueuedConnection || type == Qt::BlockingQueuedConnection)
            types = QtPrivate::ConnectionTypes<typename SignalType::Arguments>::types();


       //4 调用connectImpl
        return connectImpl(sender, reinterpret_cast<void **>(&signal),
                           receiver, reinterpret_cast<void **>(&slot),
                           new QtPrivate::QSlotObject<Func2, typename QtPrivate::List_Left<typename SignalType::Arguments, SlotType::ArgumentCount>::Value,
                                           typename SignalType::ReturnType>(slot),
                            type, types, &SignalType::Object::staticMetaObject);
    }

 1 发送者和接收者的 类型 const typename QtPrivate::FunctionPointer<Func1>::Object 
     信号和槽的类型 

FunctionPointer作用:提取和保存信号和槽函数的数据信息

    template<class Obj, typename Ret, typename... Args> struct FunctionPointer<Ret (Obj::*) (Args...)>
    {
        typedef Obj Object;//函数地址
        typedef List<Args...>  Arguments;//形参列表
        typedef Ret ReturnType;//返回值类型
        typedef Ret (Obj::*Function) (Args...);//函数指针
        //计算形参个数,是否是指针类型
        enum {ArgumentCount = sizeof...(Args), IsPointerToMemberFunction = true};
        template <typename SignalArgs, typename R>
        static void call(Function f, Obj *o, void **arg) {
            FunctorCall<typename Indexes<ArgumentCount>::Value, SignalArgs, R, Function>::call(f, o, arg);
        }
    };

 sender的类型const typename QtPrivate::FunctionPointer<Func1>::Object *sender,Func1 是信号的函数地址;如    connect(this,&qttest::signal_test1,this,&qttest::slot_test1);

与方式1的区别:

1 connect的传递参数区别:是否在编译阶段就对信号和槽的数据进行检查,方式1没有检查(运行时期检查信号和槽的拼写,如果拼写错误则报错),方式二会进行类型检查,在获取到信号和槽的数据后立即进行形参和返回值类型的检查

  二级调度 调用 connectImpl

QMetaObject::Connection QObject::connectImpl(const QObject *sender, void **signal,
                                             const QObject *receiver, void **slot,
                                             QtPrivate::QSlotObjectBase *slotObj, Qt::ConnectionType type,
                                             const int *types, const QMetaObject *senderMetaObject)
{

    //1 检查信号
    if (!signal) {
        qWarning("QObject::connect: invalid null parameter");
        if (slotObj)
            slotObj->destroyIfLastRef();
        return QMetaObject::Connection();
    }

    //2 信号索引检查其有效性
    int signal_index = -1;
    void *args[] = { &signal_index, signal };
    for (; senderMetaObject && signal_index < 0; senderMetaObject = senderMetaObject->superClass()) {
        senderMetaObject->static_metacall(QMetaObject::IndexOfMethod, 0, args);
        if (signal_index >= 0 && signal_index < QMetaObjectPrivate::get(senderMetaObject)->signalCount)
            break;
    }

    //3 检查发送者元对象
    if (!senderMetaObject) {
        qWarning("QObject::connect: signal not found in %s", sender->metaObject()->className());
        slotObj->destroyIfLastRef();
        return QMetaObject::Connection(0);
    }

   //4 计算信号的绝对索引
    signal_index += QMetaObjectPrivate::signalOffset(senderMetaObject);

   //5 调用接口
    return QObjectPrivate::connectImpl(sender, signal_index, receiver, slot, slotObj, type, types, senderMetaObject);
}

QSlotObjectBase  内部基类(接口),包含调用管理槽所需的函数

// internal base class (interface) containing functions required to call a slot managed by a pointer to function.
    class QSlotObjectBase {
        QAtomicInt m_ref;//引用计数
        // don't use virtual functions here; we don't want the
        // compiler to create tons of per-polymorphic-class stuff that
        // we'll never need. We just use one function pointer.
        typedef void (*ImplFn)(int which, QSlotObjectBase* this_, QObject *receiver, void **args, bool *ret);//函数指针 也就是void qttest::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
        const ImplFn m_impl;
    protected:
        enum Operation {
            Destroy,
            Call,
            Compare,

            NumOperations
        };
    public:
        //构造函数对引用结束和回调函数进行初始化
        explicit QSlotObjectBase(ImplFn fn) : m_ref(1), m_impl(fn) {}

        inline int ref() Q_DECL_NOTHROW { return m_ref.ref(); }
        inline void destroyIfLastRef() Q_DECL_NOTHROW
        { if (!m_ref.deref()) m_impl(Destroy, this, Q_NULLPTR, Q_NULLPTR, Q_NULLPTR); }

        inline bool compare(void **a) { bool ret = false; m_impl(Compare, this, Q_NULLPTR, a, &ret); return ret; }
        inline void call(QObject *r, void **a)  { m_impl(Call,    this, r, a, Q_NULLPTR); }
    protected:
        ~QSlotObjectBase() {}
    private:
        Q_DISABLE_COPY(QSlotObjectBase)
    };


//子类:
    // implementation of QSlotObjectBase for which the slot is a pointer to member function of a QObject
    // Args and R are the List of arguments and the returntype of the signal to which the slot is connected.
    template<typename Func, typename Args, typename R> class QSlotObject : public QSlotObjectBase
    {
        typedef QtPrivate::FunctionPointer<Func> FuncType;
        Func function;
        static void impl(int which, QSlotObjectBase *this_, QObject *r, void **a, bool *ret)
        {
            switch (which) {
            case Destroy:
                delete static_cast<QSlotObject*>(this_);
                break;
            case Call:
                FuncType::template call<Args, R>(static_cast<QSlotObject*>(this_)->function, static_cast<typename FuncType::Object *>(r), a);
                break;
            case Compare:
                *ret = *reinterpret_cast<Func *>(a) == static_cast<QSlotObject*>(this_)->function;
                break;
            case NumOperations: ;
            }
        }
    public:
        explicit QSlotObject(Func f) : QSlotObjectBase(&impl), function(f) {}
    };

第一项:Func2 槽函数函数指针

第二项 :typename QtPrivate::List_Left<typename SignalType::Arguments, SlotType::ArgumentCount>::Value    信号的参数列表  ,槽函数的参数数量

第三项:typename SignalType::ReturnType  信号的返回类型和槽函数的返回类型

 QSlotObjectBase 是一个普通基类,而不是一个虚基类,在信号与槽连接调用connect 过程中,会new 出来一个QSlotOject对象,继承于QSlotObjectBase;在实际使用中,必然存在大量QSlotOject对象,如果重写QSlotObjectBase的方法,则会有大量的虚方法表,浪费内存。

三级调度

QMetaObject::Connection QObjectPrivate::connectImpl(const QObject *sender, int signal_index,
                                             const QObject *receiver, void **slot,
                                             QtPrivate::QSlotObjectBase *slotObj, Qt::ConnectionType type,
                                             const int *types, const QMetaObject *senderMetaObject)
{
    //1 检查
    if (!sender || !receiver || !slotObj || !senderMetaObject) {
        qWarning("QObject::connect: invalid null parameter");
        if (slotObj)
            slotObj->destroyIfLastRef();
        return QMetaObject::Connection();
    }

    //2 去除const 属性
    QObject *s = const_cast<QObject *>(sender);
    QObject *r = const_cast<QObject *>(receiver);

    QOrderedMutexLocker locker(signalSlotLock(sender),
                               signalSlotLock(receiver));

    if (type & Qt::UniqueConnection && slot) {
        QObjectConnectionListVector *connectionLists = QObjectPrivate::get(s)->connectionLists;
        if (connectionLists && connectionLists->count() > signal_index) {
            const QObjectPrivate::Connection *c2 =
                (*connectionLists)[signal_index].first;

            while (c2) {
                if (c2->receiver == receiver && c2->isSlotObject && c2->slotObj->compare(slot)) {
                    slotObj->destroyIfLastRef();
                    return QMetaObject::Connection();
                }
                c2 = c2->nextConnectionList;
            }
        }
        type = static_cast<Qt::ConnectionType>(type ^ Qt::UniqueConnection);
    }

    QScopedPointer<QObjectPrivate::Connection> c(new QObjectPrivate::Connection);
    c->sender = s;
    c->signal_index = signal_index;
    c->receiver = r;
    c->slotObj = slotObj;
    c->connectionType = type;
    c->isSlotObject = true;
    if (types) {
        c->argumentTypes.store(types);
        c->ownArgumentTypes = false;
    }

    QObjectPrivate::get(s)->addConnection(signal_index, c.data());
    QMetaObject::Connection ret(c.take());
    locker.unlock();

    QMetaMethod method = QMetaObjectPrivate::signal(senderMetaObject, signal_index);
    Q_ASSERT(method.isValid());
    s->connectNotify(method);

    return ret;
}

与方式1基本相同,唯一的区别:

    c->slotObj = slotObj;

方式1 使用的是callFunction;现在使用的是slotObj 

lambda表达式的实现方式

    static inline typename std::enable_if<QtPrivate::FunctionPointer<Func2>::ArgumentCount == -1, QMetaObject::Connection>::type
            connect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal, Func2 slot)
    {
        return connect(sender, signal, sender, slot, Qt::DirectConnection);
    }

调用了connect(sender, signal, sender, slot, Qt::DirectConnection);注意是 Qt::DirectConnection

之后的过程与函数指针基本相同

信号槽中的数据存储

问题1 :每个QObject及其派生类对象是否在创建之初就自带一个信号与槽的存储容器?它在哪里?具体是什么

C:\Qt\5.9.8\Src\qtbase\src\corelib\kernel\qobject.cpp

/*!
    \internal

    Internal version of connect used by the template version of QObject::connect (called via connectImpl) and
    also used by the QObjectPrivate::connect version used by QML. The signal_index is expected to be relative
    to the number of signals.
 */
QMetaObject::Connection QObjectPrivate::connectImpl(const QObject *sender, int signal_index,
                                             const QObject *receiver, void **slot,
                                             QtPrivate::QSlotObjectBase *slotObj, Qt::ConnectionType type,
                                             const int *types, const QMetaObject *senderMetaObject)
{
    if (!sender || !receiver || !slotObj || !senderMetaObject) {
        qWarning("QObject::connect: invalid null parameter");
        if (slotObj)
            slotObj->destroyIfLastRef();
        return QMetaObject::Connection();
    }

    QObject *s = const_cast<QObject *>(sender);
    QObject *r = const_cast<QObject *>(receiver);

    QOrderedMutexLocker locker(signalSlotLock(sender),
                               signalSlotLock(receiver));

    if (type & Qt::UniqueConnection && slot) {
        QObjectConnectionListVector *connectionLists = QObjectPrivate::get(s)->connectionLists;
        if (connectionLists && connectionLists->count() > signal_index) {
            const QObjectPrivate::Connection *c2 =
                (*connectionLists)[signal_index].first;

            while (c2) {
                if (c2->receiver == receiver && c2->isSlotObject && c2->slotObj->compare(slot)) {
                    slotObj->destroyIfLastRef();
                    return QMetaObject::Connection();
                }
                c2 = c2->nextConnectionList;
            }
        }
        type = static_cast<Qt::ConnectionType>(type ^ Qt::UniqueConnection);
    }

    QScopedPointer<QObjectPrivate::Connection> c(new QObjectPrivate::Connection);
    c->sender = s;
    c->signal_index = signal_index;
    c->receiver = r;
    c->slotObj = slotObj;
    c->connectionType = type;
    c->isSlotObject = true;
    if (types) {
        c->argumentTypes.store(types);
        c->ownArgumentTypes = false;
    }

    QObjectPrivate::get(s)->addConnection(signal_index, c.data());
    QMetaObject::Connection ret(c.take());
    locker.unlock();

    QMetaMethod method = QMetaObjectPrivate::signal(senderMetaObject, signal_index);
    Q_ASSERT(method.isValid());
    s->connectNotify(method);

    return ret;
}

信号存储容器的获取:       

QObjectConnectionListVector *connectionLists = QObjectPrivate::get(s)->connectionLists;

QObjectPrivate继承QObjectData

任何继承于QObject 的类天生就有个QObjectPrivate成员对象

    static QObjectPrivate *get(QObject *o) {
        return o->d_func();
    }

 QObjectPrivate的get方法调用的是对象的d_func();即获取对象的d指针(关于d指针的介绍:qt d指针和对象树_~怎么回事啊~的博客-CSDN博客);

        因此:每个QObject对象及其派生类对象在创建之初就自带一个信号与槽的存储容器,它存在当前对象的d指针所指向的QObjectConnectionListVector的实例对象中,它是一个vector类型的存储容器。

问题2:对象关系,上至QObject是怎么关联的?

        QObjectPrivate继承QObjectData

        QObject存储有    QScopedPointer<QObjectData> d_ptr;并在构造函数中指向派生类QObjectPrivate的实例对象,在QObjectPrivate中存储有    QObjectConnectionListVector *connectionLists;为实际的数据存储容器

        QObjectPrivate和QObject属于一种impl 模式,QObjectPrivate中保存这QObject的基本数据,只有QObject可以访问从而实现对数据的保护。QObject实现了大量的public方法,这两个类可以在内部相互访问

       

问题3:是怎么进行存储的?具体存储容器是什么?

C:\Qt\5.9.8\Src\qtbase\src\corelib\kernel\qobject.cpp

        class QObjectConnectionListVector : public QVector<QObjectPrivate::ConnectionList>

实质上QObjectConnectionListVector是一个QVector的容器,存储的元素是ConnectionList类型。

C:\Qt\5.9.8\Src\qtbase\src\corelib\kernel\qobject_p.h

    // ConnectionList is a singly-linked list
    struct ConnectionList {
        ConnectionList() : first(0), last(0) {}
        Connection *first;
		Connection *last;
    };

ConnectionList  是一个链表头,指向一个双向链表,数据节点是Connection,Connection是一个双向链表,

  struct Connection
    {
        QObject *sender;//发送者对象
        QObject *receiver;//接收者对象

        //回调函数,qt4是  calFunction qt5是slotObj
        union {
            StaticMetaCallFunction callFunction;
            QtPrivate::QSlotObjectBase *slotObj;
        };
        // The next pointer for the singly-linked ConnectionList
        Connection *nextConnectionList;//下一个节点的指针
        //senders linked list
        Connection *next;
        Connection **prev;
        QAtomicPointer<const int> argumentTypes;//参数值数组指针
        QAtomicInt ref_;
        ushort method_offset;//方法偏移量
        ushort method_relative;//方法的相对索引
        //信号的绝对索引
        uint signal_index : 27; // In signal range (see QObjectPrivate::signalIndex())
        //信号与槽的链接方式
        ushort connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking
        ushort isSlotObject : 1;
        ushort ownArgumentTypes : 1;
        Connection() : nextConnectionList(0), ref_(2), ownArgumentTypes(true) {
            //ref_ is 2 for the use in the internal lists, and for the use in QMetaObject::Connection
        }
        ~Connection();
        int method() const { Q_ASSERT(!isSlotObject); return method_offset + method_relative; }
        void ref() { ref_.ref(); }
        void deref() {
            if (!ref_.deref()) {
                Q_ASSERT(!receiver);
                delete this;
            }
        }
    };

回调函数StaticMetaCallFunction(类型:    typedef void (*StaticMetaCallFunction)(QObject *, QMetaObject::Call, int, void **);)即静态元对象中第四个参数:

里面存储了对信号和槽的具体调用

 


/*
    This vector contains the all connections from an object.

    Each object may have one vector containing the lists of
    connections for a given signal. The index in the vector correspond
    to the signal index. The signal index is the one returned by
    QObjectPrivate::signalIndex (not QMetaObject::indexOfSignal).
    Negative index means connections to all signals.

    This vector is protected by the object mutex (signalSlotMutexes())

    Each Connection is also part of a 'senders' linked list. The mutex
    of the receiver must be locked when touching the pointers of this
    linked list.
*/
class QObjectConnectionListVector : public QVector<QObjectPrivate::ConnectionList>
{
public:
    //当前对象的使用状态
    bool orphaned; //the QObject owner of this vector has been destroyed while the vector was inUse
    //断开的链接对象是否已经从容器中移除
    bool dirty; //some Connection have been disconnected (their receiver is 0) but not removed from the list yet
    int inUse; //number of functions that are currently accessing this object or its connections
    QObjectPrivate::ConnectionList allsignals;

    QObjectConnectionListVector()
        : QVector<QObjectPrivate::ConnectionList>(), orphaned(false), dirty(false), inUse(0)
    { }

    QObjectPrivate::ConnectionList &operator[](int at)
    {
        if (at < 0)
            return allsignals;
        return QVector<QObjectPrivate::ConnectionList>::operator[](at);
    }
};

创建一个Connection并添加到信号索引为signalindex的双向链表中

/*!
  \internal
  Add the connection \a c to the list of connections of the sender's object
  for the specified \a signal

  The signalSlotLock() of the sender and receiver must be locked while calling
  this function

  Will also add the connection in the sender's list of the receiver.
 */
void QObjectPrivate::addConnection(int signal, Connection *c)
{
    //1 检查connectionLists 是否存在,不存在则创建
    Q_ASSERT(c->sender == q_ptr);
    if (!connectionLists)
        connectionLists = new QObjectConnectionListVector();

    //2 检查信号的绝对索引位置是否存在,不存在则扩充vector
    if (signal >= connectionLists->count())
        connectionLists->resize(signal + 1);


    //3 找到该信号所对应的双向链表,并且将新的Connection追加至尾部
    ConnectionList &connectionList = (*connectionLists)[signal];
    if (connectionList.last) {
        connectionList.last->nextConnectionList = c;//
    } else {
        connectionList.first = c;
    }
    connectionList.last = c;

   //
    cleanConnectionLists();

    //4  将Connection挂载到双向链表中,并重设指针指向位置
    c->prev = &(QObjectPrivate::get(c->receiver)->senders);
    c->next = *c->prev;
    *c->prev = c;
    if (c->next)
        c->next->prev = &c->next;

    if (signal < 0) {
        connectedSignals[0] = connectedSignals[1] = ~0;
    } else if (signal < (int)sizeof(connectedSignals) * 8) {
        connectedSignals[signal >> 5] |= (1 << (signal & 0x1f));
    }
}

QMetaObject的调用时机和使用方式

 

        元对象主要包括QMetaObject和   QMetaObjectPrivate,同时包含一个数据结合体Connection;

        元对象的主要作用:存储当前类对象的元对象数据以及对外提供获取接口,主要对类信息和类信息索引进行操作

        QMetaObject的功能

1 对元对象信息进行操作:[偏移量]、[数量]、[索引]、通过索引获取对应数据对象、调用对象成员函数,信号激活,(指针,lambda)方法检测

2 存储元对象信息

        QMetaObject::Connection  是一个包含所有数据的私有数据结构体

        QMetaObjectPrivate:

1 存储类信息

2 获取静态元对象

3 分别获取索引/偏移量/ 绝对 /顺序索引、检查信号的参数信息、参数类型签名

4 执行链接

 connect 函数的返回值是一个QMetaObject::Connection类型

QMetaObject:

struct Q_CORE_EXPORT QMetaObject
{
    class Connection;//内嵌类
    const char *className() const;//当前类的名称
    const QMetaObject *superClass() const;//父类的QMetaObject 指针

    bool inherits(const QMetaObject *metaObject) const Q_DECL_NOEXCEPT;
    QObject *cast(QObject *obj) const;
    const QObject *cast(const QObject *obj) const;

#ifndef QT_NO_TRANSLATION
    QString tr(const char *s, const char *c, int n = -1) const;
#endif // QT_NO_TRANSLATION

    int methodOffset() const;//方法偏移量获取
    int enumeratorOffset() const;//枚举偏移量获取
    int propertyOffset() const;//属性偏移量获取
    int classInfoOffset() const;//类信息偏移量获取

    //获取个数
    int constructorCount() const;
    int methodCount() const;
    int enumeratorCount() const;
    int propertyCount() const;
    int classInfoCount() const;

    //获取索引
    int indexOfConstructor(const char *constructor) const;
    int indexOfMethod(const char *method) const;
    int indexOfSignal(const char *signal) const;
    int indexOfSlot(const char *slot) const;
    int indexOfEnumerator(const char *name) const;
    int indexOfProperty(const char *name) const;
    int indexOfClassInfo(const char *name) const;

    //通过索引获取对应的数据对象
    QMetaMethod constructor(int index) const;
    QMetaMethod method(int index) const;
    QMetaEnum enumerator(int index) const;
    QMetaProperty property(int index) const;
    QMetaClassInfo classInfo(int index) const;
    QMetaProperty userProperty() const;


    //检查信号和槽函数的字符串
    static bool checkConnectArgs(const char *signal, const char *method);
    //检查信号与槽的对象
    static bool checkConnectArgs(const QMetaMethod &signal,
                                 const QMetaMethod &method);
    static QByteArray normalizedSignature(const char *method);
    static QByteArray normalizedType(const char *type);

    // internal index-based connect
    static Connection connect(const QObject *sender, int signal_index,
                        const QObject *receiver, int method_index,
                        int type = 0, int *types = Q_NULLPTR);
    // internal index-based disconnect
    static bool disconnect(const QObject *sender, int signal_index,
                           const QObject *receiver, int method_index);
    static bool disconnectOne(const QObject *sender, int signal_index,
                              const QObject *receiver, int method_index);
    // internal slot-name based connect  通过槽函数名称链接
    static void connectSlotsByName(QObject *o);

    // internal index-based signal activation  激活
    static void activate(QObject *sender, int signal_index, void **argv);
    static void activate(QObject *sender, const QMetaObject *, int local_signal_index, void **argv);
    static void activate(QObject *sender, int signal_offset, int local_signal_index, void **argv);

    static bool invokeMethod(QObject *obj, const char *member,
                             Qt::ConnectionType,
                             QGenericReturnArgument ret,
                             QGenericArgument val0 = QGenericArgument(Q_NULLPTR),
                             QGenericArgument val1 = QGenericArgument(),
                             QGenericArgument val2 = QGenericArgument(),
                             QGenericArgument val3 = QGenericArgument(),
                             QGenericArgument val4 = QGenericArgument(),
                             QGenericArgument val5 = QGenericArgument(),
                             QGenericArgument val6 = QGenericArgument(),
                             QGenericArgument val7 = QGenericArgument(),
                             QGenericArgument val8 = QGenericArgument(),
                             QGenericArgument val9 = QGenericArgument());

    static inline bool invokeMethod(QObject *obj, const char *member,
                             QGenericReturnArgument ret,
                             QGenericArgument val0 = QGenericArgument(Q_NULLPTR),
                             QGenericArgument val1 = QGenericArgument(),
                             QGenericArgument val2 = QGenericArgument(),
                             QGenericArgument val3 = QGenericArgument(),
                             QGenericArgument val4 = QGenericArgument(),
                             QGenericArgument val5 = QGenericArgument(),
                             QGenericArgument val6 = QGenericArgument(),
                             QGenericArgument val7 = QGenericArgument(),
                             QGenericArgument val8 = QGenericArgument(),
                             QGenericArgument val9 = QGenericArgument())
    {
        return invokeMethod(obj, member, Qt::AutoConnection, ret, val0, val1, val2, val3,
                val4, val5, val6, val7, val8, val9);
    }

    static inline bool invokeMethod(QObject *obj, const char *member,
                             Qt::ConnectionType type,
                             QGenericArgument val0 = QGenericArgument(Q_NULLPTR),
                             QGenericArgument val1 = QGenericArgument(),
                             QGenericArgument val2 = QGenericArgument(),
                             QGenericArgument val3 = QGenericArgument(),
                             QGenericArgument val4 = QGenericArgument(),
                             QGenericArgument val5 = QGenericArgument(),
                             QGenericArgument val6 = QGenericArgument(),
                             QGenericArgument val7 = QGenericArgument(),
                             QGenericArgument val8 = QGenericArgument(),
                             QGenericArgument val9 = QGenericArgument())
    {
        return invokeMethod(obj, member, type, QGenericReturnArgument(), val0, val1, val2,
                                 val3, val4, val5, val6, val7, val8, val9);
    }

    static inline bool invokeMethod(QObject *obj, const char *member,
                             QGenericArgument val0 = QGenericArgument(Q_NULLPTR),
                             QGenericArgument val1 = QGenericArgument(),
                             QGenericArgument val2 = QGenericArgument(),
                             QGenericArgument val3 = QGenericArgument(),
                             QGenericArgument val4 = QGenericArgument(),
                             QGenericArgument val5 = QGenericArgument(),
                             QGenericArgument val6 = QGenericArgument(),
                             QGenericArgument val7 = QGenericArgument(),
                             QGenericArgument val8 = QGenericArgument(),
                             QGenericArgument val9 = QGenericArgument())
    {
        return invokeMethod(obj, member, Qt::AutoConnection, QGenericReturnArgument(), val0,
                val1, val2, val3, val4, val5, val6, val7, val8, val9);
    }

    QObject *newInstance(QGenericArgument val0 = QGenericArgument(Q_NULLPTR),
                         QGenericArgument val1 = QGenericArgument(),
                         QGenericArgument val2 = QGenericArgument(),
                         QGenericArgument val3 = QGenericArgument(),
                         QGenericArgument val4 = QGenericArgument(),
                         QGenericArgument val5 = QGenericArgument(),
                         QGenericArgument val6 = QGenericArgument(),
                         QGenericArgument val7 = QGenericArgument(),
                         QGenericArgument val8 = QGenericArgument(),
                         QGenericArgument val9 = QGenericArgument()) const;
    ///当前qobject对象的操作功能 在qt_static_metacall中使用
    enum Call {
        InvokeMetaMethod,
        ReadProperty,
        WriteProperty,
        ResetProperty,
        QueryPropertyDesignable,
        QueryPropertyScriptable,
        QueryPropertyStored,
        QueryPropertyEditable,
        QueryPropertyUser,
        CreateInstance,
        IndexOfMethod,
        RegisterPropertyMetaType,
        RegisterMethodArgumentMetaType
    };

    int static_metacall(Call, int, void **) const;
    static int metacall(QObject *, Call, int, void **);

    struct { // private data
        const QMetaObject *superdata;//父类的qmetaobject的实例指针
        const QByteArrayData *stringdata;//当前类信息的string字符串
        const uint *data;//类信息的索引表
        typedef void (*StaticMetacallFunction)(QObject *, QMetaObject::Call, int, void **);//具体信号槽的调用
        StaticMetacallFunction static_metacall;
        const QMetaObject * const *relatedMetaObjects;
        void *extradata; //reserved for future use
    } d;
};

QMetaObject::Connection

class Q_CORE_EXPORT QMetaObject::Connection {
    void *d_ptr; //QObjectPrivate::Connection*
    explicit Connection(void *data) : d_ptr(data) {  }

    //友元类
    friend class QObject;
    friend class QObjectPrivate;
    friend struct QMetaObject;
    bool isConnected_helper() const;
public:
    ~Connection();
    Connection();
    Connection(const Connection &other);
    Connection &operator=(const Connection &other);
#ifdef Q_QDOC
    operator bool() const;
#else
    typedef void *Connection::*RestrictedBool;
    operator RestrictedBool() const { return d_ptr && isConnected_helper() ? &Connection::d_ptr : Q_NULLPTR; }
#endif

#ifdef Q_COMPILER_RVALUE_REFS
    inline Connection(Connection &&o) : d_ptr(o.d_ptr) { o.d_ptr = Q_NULLPTR; }
    inline Connection &operator=(Connection &&other)
    { qSwap(d_ptr, other.d_ptr); return *this; }
#endif
};

QMetaObject::Connection 中 void *d_ptr;是QMetaObjectPrivate类型,用于存储数据


struct QMetaObjectPrivate
{
    // revision 7 is Qt 5.0 everything lower is not supported
    enum { OutputRevision = 7 }; // Used by moc, qmetaobjectbuilder and qdbus


    //存储类对象的元对象信息
    int revision;
    int className;
    int classInfoCount, classInfoData;
    int methodCount, methodData;
    int propertyCount, propertyData;
    int enumeratorCount, enumeratorData;
    int constructorCount, constructorData;
    int flags;
    int signalCount;

    //对外提供获取类对象的元对象信息接口
    static inline const QMetaObjectPrivate *get(const QMetaObject *metaobject)
    { return reinterpret_cast<const QMetaObjectPrivate*>(metaobject->d.data); }

    static int originalClone(const QMetaObject *obj, int local_method_index);

    static QByteArray decodeMethodSignature(const char *signature,
                                            QArgumentTypeArray &types);

    //对象信息的索引获取和偏移量获取
    static int indexOfSignalRelative(const QMetaObject **baseObject,
                                     const QByteArray &name, int argc,
                                     const QArgumentType *types);
    static int indexOfSlotRelative(const QMetaObject **m,
                                   const QByteArray &name, int argc,
                                   const QArgumentType *types);
    static int indexOfSignal(const QMetaObject *m, const QByteArray &name,
                             int argc, const QArgumentType *types);
    static int indexOfSlot(const QMetaObject *m, const QByteArray &name,
                           int argc, const QArgumentType *types);
    static int indexOfMethod(const QMetaObject *m, const QByteArray &name,
                             int argc, const QArgumentType *types);
    static int indexOfConstructor(const QMetaObject *m, const QByteArray &name,
                                  int argc, const QArgumentType *types);
    Q_CORE_EXPORT static QMetaMethod signal(const QMetaObject *m, int signal_index);
    Q_CORE_EXPORT static int signalOffset(const QMetaObject *m);
    Q_CORE_EXPORT static int absoluteSignalCount(const QMetaObject *m);
    Q_CORE_EXPORT static int signalIndex(const QMetaMethod &m);
    static bool checkConnectArgs(int signalArgc, const QArgumentType *signalTypes,
                                 int methodArgc, const QArgumentType *methodTypes);
    static bool checkConnectArgs(const QMetaMethodPrivate *signal,
                                 const QMetaMethodPrivate *method);

    static QList<QByteArray> parameterTypeNamesFromSignature(const char *signature);

#ifndef QT_NO_QOBJECT
    //defined in qobject.cpp
    enum DisconnectType { DisconnectAll, DisconnectOne };
    static void memberIndexes(const QObject *obj, const QMetaMethod &member,
                              int *signalIndex, int *methodIndex);


    //将新创建的链接添加到信号索引对应的双向链表中
    static QObjectPrivate::Connection *connect(const QObject *sender, int signal_index,
                        const QMetaObject *smeta,
                        const QObject *receiver, int method_index_relative,
                        const QMetaObject *rmeta = 0,
                        int type = 0, int *types = 0);
    static bool disconnect(const QObject *sender, int signal_index,
                           const QMetaObject *smeta,
                           const QObject *receiver, int method_index, void **slot,
                           DisconnectType = DisconnectAll);
    static inline bool disconnectHelper(QObjectPrivate::Connection *c,
                                        const QObject *receiver, int method_index, void **slot,
                                        QMutex *senderMutex, DisconnectType = DisconnectAll);
#endif
};

 信号的触发流程:

        如调用一个信号  :一般的写法是  emit signal_test2(2);翻看emit 宏定义 是0,则实质是调用moc文件中信号实现:

// SIGNAL 1
void qttest::signal_test2(int _t1)
{
    void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
    QMetaObject::activate(this, &staticMetaObject, 1, _a);
}

        调用了QMetaObject::activate方法。

C:\Qt\5.9.8\Src\qtbase\src\corelib\kernel\qobject.cpp

/*!
    \internal
 */
void QMetaObject::activate(QObject *sender, const QMetaObject *m, int local_signal_index,
                           void **argv)
{
    activate(sender, QMetaObjectPrivate::signalOffset(m), local_signal_index, argv);
}

其中 :local_singal_index:表示信号的下标;也就是当前信号是moc文件中的第几个信号
QMetaObjectPrivate::signalOffset(m) 表示第一个信号在类信息中的偏移地址:

/*!
    \internal
    \since 5.0

    Returns the signal offset for the class \a m; i.e., the index position
    of the class's first signal.

    Similar to QMetaObject::methodOffset(), but non-signal methods are
    excluded.
*/
int QMetaObjectPrivate::signalOffset(const QMetaObject *m)
{
    Q_ASSERT(m != 0);
    int offset = 0;
    for (m = m->d.superdata; m; m = m->d.superdata)
        offset += priv(m->d.data)->signalCount;
    return offset;
}

真正的激活过程: QMetaObject::activate


/*!
    \internal
 */
void QMetaObject::activate(QObject *sender, int signalOffset, int local_signal_index, void **argv)
{
    //1 计算当前信号绝对索引 = 信号偏移量 + 本地索引
    int signal_index = signalOffset + local_signal_index;

    // 检查信号是否处于阻塞状态
    if (sender->d_func()->blockSig)
        return;

    if (sender->d_func()->isDeclarativeSignalConnected(signal_index)
            && QAbstractDeclarativeData::signalEmitted) {
        QAbstractDeclarativeData::signalEmitted(sender->d_func()->declarativeData, sender,
                                                signal_index, argv);
    }

  
    //2 检查此信号是否存储信号槽链接
    if (!sender->d_func()->isSignalConnected(signal_index, /*checkDeclarative =*/ false)
        && !qt_signal_spy_callback_set.signal_begin_callback
        && !qt_signal_spy_callback_set.signal_end_callback) {
        // The possible declarative connection is done, and nothing else is connected, so:
        return;
    }

    void *empty_argv[] = { 0 };
    if (qt_signal_spy_callback_set.signal_begin_callback != 0) {
        qt_signal_spy_callback_set.signal_begin_callback(sender, signal_index,
                                                         argv ? argv : empty_argv);
    }


 
    {
    QMutexLocker locker(signalSlotLock(sender));
    struct ConnectionListsRef {
        QObjectConnectionListVector *connectionLists;
        ConnectionListsRef(QObjectConnectionListVector *connectionLists) : connectionLists(connectionLists)
        {
            if (connectionLists)
                ++connectionLists->inUse;
        }
        ~ConnectionListsRef()
        {
            if (!connectionLists)
                return;

            --connectionLists->inUse;
            Q_ASSERT(connectionLists->inUse >= 0);
            if (connectionLists->orphaned) {
                if (!connectionLists->inUse)
                    delete connectionLists;
            }
        }

        QObjectConnectionListVector *operator->() const { return connectionLists; }
    };
    ConnectionListsRef connectionLists = sender->d_func()->connectionLists;
    if (!connectionLists.connectionLists) {
        locker.unlock();
        if (qt_signal_spy_callback_set.signal_end_callback != 0)
            qt_signal_spy_callback_set.signal_end_callback(sender, signal_index);
        return;
    }

     //3 获取当前信号的绝对索引在connectionLists中所对应槽函数双向链表的链表头
    const QObjectPrivate::ConnectionList *list;
    if (signal_index < connectionLists->count())
        list = &connectionLists->at(signal_index);//获取链表头
    else
        list = &connectionLists->allsignals;

    Qt::HANDLE currentThreadId = QThread::currentThreadId();//获取当前信号的当前线程id


    // 4 遍历双向链表
    do {
        
        QObjectPrivate::Connection *c = list->first;
        if (!c) continue;
        // We need to check against last here to ensure that signals added
        // during the signal emission are not emitted in this emission.
        QObjectPrivate::Connection *last = list->last;

        do {
            if (!c->receiver)
                continue;

            QObject * const receiver = c->receiver;
         
            //检查接收者和发送者是否处于同一线程
            const bool receiverInSameThread = currentThreadId == receiver->d_func()->threadData->threadId.load();

            // determine if this connection should be sent immediately or
            // put into the event queue
            //判断信号槽的链接类型
            //4.1 如果是自动链接或队列链接  且不在同一个线程
            if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
                || (c->connectionType == Qt::QueuedConnection)) {
                queued_activate(sender, signal_index, c, argv ? argv : empty_argv, locker);
                continue;

           //4.2  如果是堵塞队列链接
#ifndef QT_NO_THREAD
            } else if (c->connectionType == Qt::BlockingQueuedConnection) {
                if (receiverInSameThread) {
                    qWarning("Qt: Dead lock detected while activating a BlockingQueuedConnection: "
                    "Sender is %s(%p), receiver is %s(%p)",
                    sender->metaObject()->className(), sender,
                    receiver->metaObject()->className(), receiver);
                }
                QSemaphore semaphore;
                QMetaCallEvent *ev = c->isSlotObject ?
                    new QMetaCallEvent(c->slotObj, sender, signal_index, 0, 0, argv ? argv : empty_argv, &semaphore) :
                    new QMetaCallEvent(c->method_offset, c->method_relative, c->callFunction, sender, signal_index, 0, 0, argv ? argv : empty_argv, &semaphore);
                QCoreApplication::postEvent(receiver, ev);
                locker.unlock();
                semaphore.acquire();
                locker.relock();
                continue;
#endif
            }

            QConnectionSenderSwitcher sw;
            
            //如果在同一个线程
            if (receiverInSameThread) {
                sw.switchSender(receiver, sender, signal_index);
            }
            
            //4.3 如果是函数地址链接方式
            if (c->isSlotObject) {
                c->slotObj->ref();
                QScopedPointer<QtPrivate::QSlotObjectBase, QSlotObjectBaseDeleter> obj(c->slotObj);
                locker.unlock();
                obj->call(receiver, argv ? argv : empty_argv);

                // Make sure the slot object gets destroyed before the mutex is locked again, as the
                // destructor of the slot object might also lock a mutex from the signalSlotLock() mutex pool,
                // and that would deadlock if the pool happens to return the same mutex.
                obj.reset();

                locker.relock();
            //4.4 如果是 SINGAL  SLOT链接方式   判断槽偏移量是否小于接收者元对象中的方法偏移量
            } else if (c->callFunction && c->method_offset <= receiver->metaObject()->methodOffset()) {
                //we compare the vtable to make sure we are not in the destructor of the object.
                const int methodIndex = c->method();//方法索引
                const int method_relative = c->method_relative; //相对索引
                const auto callFunction = c->callFunction;//回调函数
                locker.unlock();
                if (qt_signal_spy_callback_set.slot_begin_callback != 0)
                    qt_signal_spy_callback_set.slot_begin_callback(receiver, methodIndex, argv ? argv : empty_argv);
                //传入参数 ,调用回调函数 从而实现对信号绑定槽函数的具体调用 就是  staticMetaObject 中的  qt_static_metacall
                callFunction(receiver, QMetaObject::InvokeMetaMethod, method_relative, argv ? argv : empty_argv);

                if (qt_signal_spy_callback_set.slot_end_callback != 0)
                    qt_signal_spy_callback_set.slot_end_callback(receiver, methodIndex);
                locker.relock();
            } else {
                const int method = c->method_relative + c->method_offset;
                locker.unlock();

                if (qt_signal_spy_callback_set.slot_begin_callback != 0) {
                    qt_signal_spy_callback_set.slot_begin_callback(receiver,
                                                                method,
                                                                argv ? argv : empty_argv);
                }

                metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);

                if (qt_signal_spy_callback_set.slot_end_callback != 0)
                    qt_signal_spy_callback_set.slot_end_callback(receiver, method);

                locker.relock();
            }

            if (connectionLists->orphaned)
                break;
        } while (c != last && (c = c->nextConnectionList) != 0);

        if (connectionLists->orphaned)
            break;
    } while (list != &connectionLists->allsignals &&
        //start over for all signals;
        ((list = &connectionLists->allsignals), true));

    }

    if (qt_signal_spy_callback_set.signal_end_callback != 0)
        qt_signal_spy_callback_set.signal_end_callback(sender, signal_index);

}

下面分情况讨论每种连接方式的具体槽函数的调用方式:

  如果是函数地址链接方式

            //4.3 如果是函数地址链接方式
            if (c->isSlotObject) {
                c->slotObj->ref();
                QScopedPointer<QtPrivate::QSlotObjectBase, QSlotObjectBaseDeleter> obj(c->slotObj);
                locker.unlock();
                //执行槽函数
                obj->call(receiver, argv ? argv : empty_argv);

                // Make sure the slot object gets destroyed before the mutex is locked again, as the
                // destructor of the slot object might also lock a mutex from the signalSlotLock() mutex pool,
                // and that would deadlock if the pool happens to return the same mutex.
                obj.reset();

                locker.relock();
         
            } 

调用obj->call函数  C:\Qt\5.9.8\msvc2015\include\QtCore\qobject_impl.h

inline void call(QObject *r, void **a)  { m_impl(Call,    this, r, a, Q_NULLPTR); }

// 调用impl 函数

        typedef QtPrivate::FunctionPointer<Func> FuncType;
        Func function;
        static void impl(int which, QSlotObjectBase *this_, QObject *r, void **a, bool *ret)
        {
            switch (which) {
            case Destroy:
                delete static_cast<QSlotObject*>(this_);
                break;
            case Call:
                FuncType::template call<Args, R>(static_cast<QSlotObject*>(this_)->function, static_cast<typename FuncType::Object *>(r), a);
                break;
            case Compare:
                *ret = *reinterpret_cast<Func *>(a) == static_cast<QSlotObject*>(this_)->function;
                break;
            case NumOperations: ;
            }
        }

//走到case Call  一层层的模版!!!

        static void call(Function f, Obj *o, void **arg) {
            FunctorCall<typename Indexes<ArgumentCount>::Value, SignalArgs, R, Function>::call(f, o, arg);
        }

    template <int... II, typename... SignalArgs, typename R, typename... SlotArgs, typename SlotRet, class Obj>
    struct FunctorCall<IndexesList<II...>, List<SignalArgs...>, R, SlotRet (Obj::*)(SlotArgs...)> {
        static void call(SlotRet (Obj::*f)(SlotArgs...), Obj *o, void **arg) {
            (o->*f)((*reinterpret_cast<typename RemoveRef<SignalArgs>::Type *>(arg[II+1]))...), ApplyReturnValue<R>(arg[0]);
        }
    };

调用到槽函数



 

如果是 SINGAL  SLOT链接方式

 

            //4.4 如果是 SINGAL  SLOT链接方式   判断槽偏移量是否小于接收者元对象中的方法偏移量
            } else if (c->callFunction && c->method_offset <= receiver->metaObject()->methodOffset()) {
                //we compare the vtable to make sure we are not in the destructor of the object.
                const int methodIndex = c->method();//方法索引
                const int method_relative = c->method_relative; //相对索引
                const auto callFunction = c->callFunction;//回调函数
                locker.unlock();
                if (qt_signal_spy_callback_set.slot_begin_callback != 0)
                    qt_signal_spy_callback_set.slot_begin_callback(receiver, methodIndex, argv ? argv : empty_argv);
                //传入参数 ,调用回调函数 从而实现对信号绑定槽函数的具体调用 就是  staticMetaObject 中的  qt_static_metacall
                callFunction(receiver, QMetaObject::InvokeMetaMethod, method_relative, argv ? argv : empty_argv);

                if (qt_signal_spy_callback_set.slot_end_callback != 0)
                    qt_signal_spy_callback_set.slot_end_callback(receiver, methodIndex);
                locker.relock();
            }

调用callFunction ,callFunction 是一个函数指针。调用接收者对象的:


void qttest::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        qttest *_t = static_cast<qttest *>(_o);
        Q_UNUSED(_t)
        switch (_id) {
        case 0: _t->signal_test1(); break;
        case 1: _t->signal_test2((*reinterpret_cast< int(*)>(_a[1]))); break;
        case 2: _t->slot_test1(); break;
        case 3: _t->slot_test2((*reinterpret_cast< int(*)>(_a[1]))); break;
        default: ;
        }
    } else if (_c == QMetaObject::IndexOfMethod) {
        int *result = reinterpret_cast<int *>(_a[0]);
        {
            typedef void (qttest::*_t)();
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&qttest::signal_test1)) {
                *result = 0;
                return;
            }
        }
        {
            typedef void (qttest::*_t)(int );
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&qttest::signal_test2)) {
                *result = 1;
                return;
            }
        }
    }
}

找到槽函数的 索引并且调用:

 

  如果是自动链接或队列链接  且不在同一个线程

            if ((c->connectionType == Qt::AutoConnection && !receiverInSameThread)
                || (c->connectionType == Qt::QueuedConnection)) {
                queued_activate(sender, signal_index, c, argv ? argv : empty_argv, locker);
                continue;
            }

调用queued_activate函数,创建QMetaCallEvent 并且使用postEvent发送到接收者线程中


/*!
    \internal

    \a signal must be in the signal index range (see QObjectPrivate::signalIndex()).
*/
static void queued_activate(QObject *sender, int signal, QObjectPrivate::Connection *c, void **argv,
                            QMutexLocker &locker)
{
......

    //创建MetaCallEvent 并且使用postEvent发送到接收者线程中
    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);
    QCoreApplication::postEvent(c->receiver, ev);
}

QCoreApplication::postEvent 的相关使用参考qt-事件循环系统_~怎么回事啊~的博客-CSDN博客

postEvent将该事件发送到接受者的postEventList中并调用接收者的事件分发器dispatcher->wakeUp();唤醒接收者的事件循环,接受者的隐藏窗口过程函数qt_internal_proc 进行事件处理:,堆栈如下:

 最终调用到bool QObject::event(QEvent *e),执行

bool QObject::event(QEvent *e)
{
    switch (e->type()) {
......

    case QEvent::MetaCall:
        {
            QMetaCallEvent *mce = static_cast<QMetaCallEvent*>(e);

            QConnectionSenderSwitcher sw(this, const_cast<QObject*>(mce->sender()), mce->signalId());

            mce->placeMetaCall(this);
            break;
        }
......

接下来调用   slotObj_->call(object, args_); call是模版编程,接下来的模版编程我也没怎么看懂,最终调用到槽函数。

void QMetaCallEvent::placeMetaCall(QObject *object)
{
    if (slotObj_) {
        slotObj_->call(object, args_);
    } else if (callFunction_ && method_offset_ <= object->metaObject()->methodOffset()) {
        callFunction_(object, QMetaObject::InvokeMetaMethod, method_relative_, args_);
    } else {
        QMetaObject::metacall(object, QMetaObject::InvokeMetaMethod, method_offset_ + method_relative_, args_);
    }
}

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Qt: 信号与槽机制 的相关文章

  • 删除 QComboBox“下拉”动画

    我正在使用 Qt 4 8 并且想在单击 QComboBox 时摆脱 下拉 动画 我也想稍微移动一下 到目前为止 我一直在考虑重新实现 showPopup 和 hidePopup 但不知道如何使其工作 此外 每次我尝试使用 CSS 进行移动或
  • 如何在 QTableWidget 的行和列中自动换行文本?

    I tried QTableWidget j new QTableWidget 10000 5 centralWidget j gt setColumnWidth 0 500 j gt setColumnWidth 1 30 j gt se
  • QT 应用程序运行时错误?

    我已经在 Windows 7 的 Qt5 1 上开发了一个应用程序 现在我想分发它 由于我使用了 MINGW 编译器 因此无法静态构建它 我使用dependency walker找出所有dll并打包 当我在未安装 QT 开发环境的计算机上运
  • 检查目录是否为空

    我正在尝试检查目录是否为空 MainWindow MainWindow QWidget parent QMainWindow parent ui new Ui MainWindow ui gt setupUi this QDir Dir h
  • QLineEdit:显示处理后的文本,而不是输入的文本,但保留它(自定义回显模式)

    我想要一个QLineEdit不显示输入的文本 而是显示经过处理的版本 同时保留原始文本并在通过请求时返回它text 就像密码回显模式一样 但我不希望每个字符都被屏蔽 我想虚拟化空间 例如什么时候some text with spaces i
  • 如何将图标放置到 QLineEdit 上?

    stackoverflow com 网站的右上角有一个带有放大镜头的搜索字段和一个灰色的 搜索 关键字 我想知道是否有可能实现相同的外观QLineEdit 如果是这样那怎么办 QLineEdit lineEdit new QLineEdit
  • QT 中只获取文件而不获取目录?

    当我这样做时 QDir myDir home some location QStringList filesList myDir entryList 它返回该位置内的文件和目录 但我只想要文件 并且这些文件可以具有任意扩展名 有任何想法吗
  • 从 QML 实例化 C++ 对象会产生巨大的内存使用开销

    实例化一个QObjectC 堆中的派生类为每个对象提供了大约 160 个字节 通过注册相同的对象qmlRegisterType 用于从 QML 创建并通过以下方式动态创建对象createObject 每个对象给我 2000 多个字节 这是完
  • 使用 cmake 将两种解决方案合二为一

    我有两个单独的 Visual Studio 2013 解决方案 我想将它们迁移到一个解决方案中 因为第一个解决方案 使用 Qt 充当第二个解决方案的 GUI 最后 我希望有一个结构如下的单一解决方案 Solution All Build P
  • 程序意外完成 - QT Creator

    我正在尝试使用 QT Creator 使用 QT 框架开发 GUI 控制台应用程序 我使用的是Windows XP 我安装了QT 4 8 3和mingw 两者均已安装 没有任何错误 然后我安装了QT Creator QT 版本 路径中的 Q
  • 如何创建QWidget的屏幕截图?

    我在 Qt Creator 中做作业 在其中绘制 QWidget 并且需要保存此 QWdiget 的某些部分 我试图解决这个问题 QPixmap pixmap pixmap copy rectangle rectangle is part
  • QT/QML Android App,点击通知栏时打开应用程序

    我为 Android 应用程序制作了一个 QT 当我单击平板电脑中上面看到的按钮通知栏时 但是 如果单击通知 我的应用程序将打开 聚焦 不知道 并显示黑色窗口 简单来说怎么做呢 我的代码是 package org ays operation
  • Qt:将拖放委托给子级的最佳方式

    我在 QWidget 上使用拖放 我重新实现了 DragEnterEvent dragLeaveEvent dragMoveEvent 和 dropEvent 效果很好 在我的 QWidget 中 我有其他 QWidget 子级 我希望它们
  • GoQt 致命错误:QAbstractAnimation:没有这样的文件或目录

    我尝试编译 Qt 来开发桌面应用程序 我按照 Qt 网站上的官方 wiki 指南的说明进行操作 当我尝试go run示例文件夹中的示例 我收到错误 去运行 home pinkya rabbit workspace go1programs s
  • PyQt:如何设置组合框项目可检查?

    为了将 GUI 小部件数量保持在最低限度 我需要找到一种方法来为用户提供下拉菜单项的选择 这些菜单项可用于过滤掉 listWidget 项中显示的内容 假设 listWidget 列出了 5 个不同类别的项目 Cat A Cat B Cat
  • 如何在带有预编译头的项目中使用google protobuf

    我有一个包含多个项目的解决方案 我的项目 但不是全部 使用预编译头 我决定使用 protobuf 但遇到了一个问题 在 protoc exe 从 proto 生成 pb h 后 我尝试包含标头并收到错误 预编译标头未包含在 pb h 中 我
  • 禁用 QML Slider 的鼠标滚轮

    我希望能够滚动Flickable使用鼠标滚轮 或触摸板上的两根手指 不改变Sliders它可能包含 示例代码及结果应用 import QtQuick 2 7 import QtQuick Window 2 2 import QtQuick
  • 在 Qt5 中,是否需要 Q_INVOKABLE 来从 QML 调用公共 QObject 函数?

    我刚刚意识到我可以调用暴露于 QML 的对象的几乎任何函数 现在我对 Q INVOKABLE 很好奇 Qt5docs http doc qt io qt 5 qtqml cppintegration exposecppattributes
  • 如何在 Qt 中以编程方式制作一条水平线

    我想弄清楚如何在 Qt 中制作一条水平线 这很容易在设计器中创建 但我想以编程方式创建一个 我已经做了一些谷歌搜索并查看了 ui 文件中的 xml 但无法弄清楚任何内容 ui 文件中的 xml 如下所示
  • 与 Qt 项目的静态链接

    我有一个在 Visual Studio 2010 Professional 中构建的 Qt 项目 但是 当我运行它 在调试或发布模式下 时 它会要求一些 Qt dll 如果我提供 dll 并将它们放入 System32 中 它就可以工作 但

随机推荐

  • TCP的keep-alive机制分析

    TCP中的keep alive机制 问题和解决思路 详细内容 缺陷分析 问题和解决思路 建立tcp连接后 双方互相发送信息 但是可能存在的情况是双方在处理数据 暂时并不会互相发送数据 那么这个时候如何判断双方连接是否依然正常 而没有意外断开
  • Python Using EXE

    OS This method has its disadvantage if we want to stop this process We need do it by ourselves except seize the terminal
  • 启动nacos时出现“nacos is starting with cluster”问题

    使用startup cmd命令启动是以集群方式启动nacos 可以看见命令行中有 nacos is starting with cluster 我们可以以单机方式启动nacos 执行以下命令 startup cmd m standalone
  • MySql导入导出数据库(含远程导入导出)

    一 导入导出本地数据库 导出 1 先运行cmd cd 到mysql安装目录中的bin文件夹 2 mysqldump u root p 数据库名 gt 导出文件名 sql 其他情况下 1 导出整个数据库 mysqldump u 用户名 p 数
  • 2023华为od机试 Java 实现【德州扑克】

    前言 本题使用Java解答 如果需要Python代码 请参考以下链接 链接 题目 我们可以选择五张牌 它们的范围是 每张牌的大小在2 10之间 或者字母J Q K A 牌花色为红桃 黑桃 梅花 方块四种花色之一 现在一共有6种牌型 牌型1
  • 最新的ARM-GCC下载安装指南

    GCC下载网址 https developer arm com tools and software open source software developer tools gnu toolchain gnu a downloads 我使
  • 搭建一个属于自己的个人网站怎样选择服务器

    不管是想搭建一个属于自己的个人网站 还是想开发一个企业项目上云 都必须拥有一台云服务器 云服务器是公有云的支柱之一 我用过很多大厂的云服务器 还记得读研期间我刚开始入门的时候 自己尝试搭建了一台云服务器建网站 后来在一个导师项目中刚好需要用
  • 双指针笔记

    双指针是在一次for循环中使用两个指针完成需要两个for循环的工作 其主要有两种方法 快慢双指针 从一端开始 设置两个速度不一样的指针进行遍历 public int removeElement int nums int val int fa
  • 关于C++中void*形参兼容问题,血的教训!

    关于C 中void 形参兼容问题 血的教训 C语言中void 作为形参的用法 C 中void 作为形参的用法 C语言中void 作为形参的用法 最近在实操一本算法书上的代码的时候 碰到了形如int comp void void 这样的形参列
  • 一种通用代码模型的构建与实现

    开发思路 总体开发思路 代码解析 需要考虑的问题是 目前已经实现了70 左右的功能 代码片段的内在结构 识别代码片段中所存在的对象元素 系统根据不同的对象元素的分类能快速执行相应的处理程序 将处理的结果进行传递 将处理过程进行缓存 保留代码
  • 程设课终章:c++使用socket实现bmp图片的传输

    特别鸣谢 44条消息 C UDP发送接收文件 BMP 我不在你不在的博客 CSDN博客 里面一些概念 ip地址 电脑门牌号 端口号 做这件事的行动代号 协议 接收方和发送方都必须遵守的一种规则 socket是基于tcp ip协议发送数据技术
  • 【vue其他相关】欢迎讨论!异步请求放在生命周期中的created还是mounted,已填坑

    文章目录 前言 两个生命周期 场景1 场景2 场景3 结论 如何处理场景一和场景二的问题 最后 前言 曾经在百度上搜这个问题 看到的答案都是相互复制粘贴的 而且个人觉得并没具体场景举例说明 并没有好的答案 于是自己立贴 留下这个坑 等日后遇
  • vue循环生成div

    遇到了一个需求 需要展示一年12个月的日历 然后一行为一个div 需要循环生成4个div 刚开始我查了一下 不知道怎么入手 然后问了我一个做前端的同学 一分钟就解决了 代码如下 div div div style width 500px b
  • pb 判断timer是否在执行_FreeRTOS 之 Timer

    在 FreeRTOS 之任务调度 一文中提到 硬件定时器是和硬件设计相关的 不同的芯片有不同的配置方法 通过中断方式触发执行 精确度高 相对于硬件定时器 FreeRTOS 中还提供了软件定时器 本文就来聊聊软件定时器是如何实现的 以及它的精
  • 类加载器的父委托机制

    类加载器 类加载器用来把类加载到Java虚拟机中 类加载器的类型 有两种类型的类加载器 1 JVM自带的加载器 根类加载器 Bootstrap 扩展类加载器 Extension 系统类加载器 System 2 用户自定义的类加载器 java
  • spring异常Unsatisfied dependency expressed through constructor parameter 0

    异常信息 org springframework beans factory UnsatisfiedDependencyException Error creating bean with name xxx defined in file
  • 程序员如何在浏览器上面去做p图软件的事

    在浏览器中更改内容除了箭头点击元素进行更改还有另一种更快捷的方法 首先随便打开一个浏览器 按f12 打开控制台 在控制台内输入一下代码回车 document designMode on 开启这个网站修改模式之后你就可以随心所欲地去更改网站的
  • 深度、广度优先搜索

    文章目录 二 图的遍历 2 1 深度优先搜索 DFS DFS森林 应用 2 2 广度优先搜索 BFS 基本操作 应用 二 图的遍历 2 1 深度优先搜索 DFS DFS森林 Vertextype GetVex ALGraph G int v
  • ewiews面板回归模型操作_【视频教程】Eviews系列25

    点击上方关注我们 本期我们学习Eviews统计建模最后一部分 面板数据回归分析Hausman检验及本章常见问题解答 实操 Hausman检验判断是固定效应模型还是随机效应模型 上期我们讲到模型判断若选择模型2 需进一步通过Hausman检验
  • Qt: 信号与槽机制

    一 信号和槽机制是什么 1 Qt信号槽机制 是Qt的核心机制 它是Qt定义的一种对象间的通讯机制 且独立于标准C C 语言 2 信号 signals 当某个类对象发生内部改变时 发射 信号 随后与关联的 槽函数 被立即执行 信号槽机制 完全