Qt信号与槽的实现原理

本文档中出现的代码不一定是Qt的原生代码,可能是根据原来思维写的类似伪代码,如会将Qt原生的slots、signals关键字换成db_signals和db_slots,以表示这是伪代码,而不是Qt原生的代码,只用于表述信号槽的原理。

 

首先对象内部会议2个char*型的字符串,专门记录类中定义的信号与槽,如:

class Object

{

public:

    Object();

    virtual ~Object();

db_signals:

   void sig1();

   void sig2();

public db_slots:

    void slot1();

    void slot2();

};

然后有2个字符串为:

     static const char sig_names[] = "sig1/sig2";

     static const char slts_names[] = "slot1/slot2";

元对象系统中可能有一个类里面定义这2个字符串,如:

struct MetaObject

{

    const char * sig_names;

    const char * slts_names;

};

 

信号与槽是通过connect函数连接的,connect内部如下所示:

void Object::db_connect(Object* sender, const char* sig, Object* receiver, const char* slt)

{

    int sig_idx = find_string(sender->meta.sig_names, sig);

    int slt_idx = find_string(receiver->meta.slts_names, slt);

    if (sig_idx == -1 || slt_idx == -1) {

        perror("signal or slot not found!");

    } else {

        Connection c = {receiver, slt_idx};

        sender->connections.insert(std::pair<int, Connection>(sig_idx, c));

    }

}

可以看到connect内部是使用find_string,即字符串匹配,而一旦匹配到了,就会将信号索引sig_idx和对应的Connection对象合成一对,并且发送方有个专门的pair对象(connections)存储这些数据,而Connection对象存储着槽函数的对象receiver和槽函数的索引methodIndex

Connection对象定义如下所示:

 

struct Connection

{

    Object * receiver;

    int methodIndex;

};

 

接下来便是元对象编译器生成moc_xxx.cpp文件,给各个信号加上函数体,以下是我随便找的moc_mainview.cpp截取的部分代码,一个是信号testSignal的源码,另一个是qt_static_metacall函数的部分源码。

Qt信号与槽的实现原理

 

                                                                           信号testSignal源码

Qt信号与槽的实现原理

 

                                                                                   qt_static_metacall函数的部分源码

可以看到根据信号生成的函数体就是调用了QMetaObject::activate这一个函数而已,而这个函数的第3个参数是信号索引sig_idx,是专门用于识别是哪个信号的,图片中是0,代表是第0个信号。qt_static_metacall函数的源码我没有看到,但是很容易猜测出来里面发生了什么:

估计qt_static_metacall函数里面会遍历前面connect函数中记录信号与槽连接情况的pair对象(connections),找到对应对象连接的Connection的对象,然后使得Connection对象中的receiver调用其qt_static_metacall函数,而qt_static_metacall函数根据上图所示主要结构就是一个switch-case语句,根据信号或槽的索引值调用对应的信号或槽函数。

上诉原理都只讲述的是connect函数第5个参数是Qt::DirectConnection情况下的情况,若是其他情况,如Qt::QueuedConnection,肯定会在qt_static_metacall函数中做不同处理,但都大同小异,很容易猜测到发生了什么。

 

最后给出Qt信号与槽的使用条件以及需要这些条件的原因。

  1. 类继承自QObject或其子类。因为connect函数、承载信号与槽的字符串,记录信号与槽连接情况的pair对象等都是在QObject中定义的
  2. 类定义时声明有Q_OBJECT宏。前面提到,信号与槽的实现依托于moc_xxx.cpp文件,这个文件是Qt元对象编译器生成的,但是Qt元对象编译器总得知道哪些类或文件需要生成对应的moc文件吧,那么Q_OBJECT宏便是这个标志,声明了Q_OBJECT宏便是告诉Qt元对象编译器这个类需要生成对应的moc文件