如何在引擎盖下实现信号和插槽?

时间:2009-09-10 18:31:15

标签: qt qt4 signals-slots

这个问题已在本论坛中提出,但我不明白这个概念。

我正在阅读,似乎信号和插槽是使用函数指针实现的,即信号是一个很大的函数,它在里面调用所有连接的插槽(函数指针)。它是否正确?在整个故事中生成的moc文件的作用是什么?我不明白信号功能如何知道哪个插槽要调用,即哪个插槽连接到该信号。

感谢您的时间

2 个答案:

答案 0 :(得分:16)

Qt以类似解释语言的方式实现这些东西。即它构造符号表,将信号名称映射到函数指针,维护它们并在需要时按函数名称查找函数指针。

每次发出信号,即写

emit something();

实际上你调用something()函数,它由元对象编译器自动生成并放入*.moc文件中。在此函数中,此时检查此信号连接到哪些插槽,并通过符号表(以上述方式)顺序调用适当的插槽功能(在您自己的源中实现)。与其他Qt特定关键字一样,emit在生成*.moc后被C ++预处理器丢弃。实际上,在其中一个Qt标题(qobjectdefs.h)中,存在这样的行:

#define slots 
#define signals protected
#define emit

连接函数(connect)只修改*.moc个文件中维护的符号表,并且传递给它的参数(带有SIGNAL()和`SLOT宏)也会被预处理以匹配表。

这是一般的想法。在他或她的另一个答案中,ジョージ为我们提供了有关此主题的to trolltech mailing listanother SO question链接。

答案 1 :(得分:5)

我想我应该添加以下内容。

another linked question - 并且a very good article可以被视为对answer的非常详细的扩展。 here is this article again,改进了(但仍然不完美)代码语法突出显示。

这是我对它的简短重述,可能容易出错)

基本上,当我们在类定义中插入Q_OBJECT宏时,预处理器会将它扩展为静态QMetaObject实例声明,该声明将由同一个类的所有实例共享:

class ClassName : public QObject // our class definition
{
    static const QMetaObject staticMetaObject; // <--= Q_OBJECT results to this

    // ... signal and slots definitions, other stuff ...

}

此实例反过来在初始化时将存储信号和插槽的签名"methodname(argtype1,argtype2)"),这将允许实现indexOfMethod()调用,通过它的签名字符串返回,以及方法的索引:

struct Q_CORE_EXPORT QMetaObject
{    
    // ... skip ...
    int indexOfMethod(const char *method) const;
    // ... skip ...
    static void activate(QObject *sender, int signal_index, void **argv);
    // ... skip ...
    struct { // private data
        const QMetaObject *superdata; // links to the parent class, I guess
        const char *stringdata; // basically, "string1\0string2\0..." that contains signatures and other names 
        const uint *data; // the indices for the strings in stringdata and other stuff (e.g. flags)
        // skip
    } d;
};

现在,当moc为Qt类标头moc_headername.cpp创建headername.h文件时,它会在其中放置正确初始化{{1}所需的签名字符串和其他数据结构,然后使用此数据写入d单例的初始化代码。

它做的另一件重要的事情是为对象的staticMetaObject方法生成代码,该方法接受对象的方法id和参数指针数组,并通过long调用该方法qt_metacall()喜欢这样:

switch

最后,对于每个信号int ClassName::qt_metacall(..., int _id, void **_args) { // ... skip ... switch (_id) { case 0: signalOrSlotMethod1(_args[1], _args[2]); break; // for a method with two args case 1: signalOrSlotMethod2(_args[1]); break; // for a method with a single argument // ... etc ... } // ... skip ... } 生成一个包含moc调用的实现:

QMetaObject::activate()

最后,void ClassName::signalName(argtype1 arg1, argtype2 arg2, /* ... */) { void *_args[] = { 0, // this entry stands for the return value &arg1, // actually, there's a (void*) type conversion &arg2, // in the C++ style // ... }; QMetaObject::activate( this, &staticMetaObject, 0, /* this is the signal index in the qt_metacall() map, I suppose */ _args ); } 调用将字符串方法签名转换为它们的整数id(connect()使用的那些)并维护信号到槽连接的列表;当发出信号时,qt_metacall()代码通过该列表并调用适当的对象&#34; slot&#34;通过他们的activate()方法。

总而言之,静态qt_metacall()实例存储了&#34;元信息&#34; (方法签名字符串等),生成的QMetaObject方法提供&#34;方法表&#34;允许索引调用任何信号/槽,qt_metacall()生成的信号实现通过moc使用这些索引,最后activate()完成维护信号列表的工作-slot index maps。

*注意:当我们想要在不同线程之间传递信号时(我怀疑必须查看connect()代码),这种方案的复杂性用于此情况,但我希望总体思路保持不变)

这是我对链接文章的粗略理解,这很容易出错,所以我建议直接去阅读它。

PS。由于我想提高对Qt实施的理解,请告诉我复述中的任何不一致之处!


由于我的其他(早期)答案被一些热心的编辑删除了,我会在这里附上文字(我遗漏了一些并入Pavel Shved&post文章中的细节,以及我怀疑删除答案的人是否在乎。)

@Pavel Shved:

  

我很确定在Qt​​标题中的某处存在一行:

     

blocking_activate()

只是为了确认:通过Google Code Search在旧的Qt代码中找到它。它很可能仍然存在);找到的位置路径是:

ftp://ftp.slackware-brasil.com.br> Slackware的-7.1> 的contrib> KDE-1.90> QT-2.1.1.tgz> USR> LIB> QT-2.1.1> SRC> 核心> 的 qobjectdefs.h


另一个补充链接:http://lists.trolltech.com/qt-interest/2007-05/thread00691-0.html - 请参阅Andreas Pakulat的回答


以下是答案的另一部分:Qt question: How do signals and slots work?