23

这个问题已经在这个论坛上问过了,但我不明白这个概念。

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

谢谢你的时间

4

2 回答 2

16

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

每次你发出一个信号,即写

emit something();

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

#define slots 
#define signals protected
#define emit

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

这是一般的想法。在他或她的另一个答案中,ジョージ为我们提供了指向 trolltech 邮件列表的链接以及有关此主题的另一个 SO 问题。

于 2009-09-10T22:10:37.750 回答
5

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

还有另一个相关的问题——有一篇非常好的文章可以被认为是对其答案的非常详细的扩展;这又是这篇文章,改进了(尽管仍然不完美)代码语法突出显示。

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

基本上,当我们在类定义中插入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;
};

现在,当为 Qt 类头文件moc创建文件时,它会将签名字符串和其他正确初始化结构所需的数据放在那里,然后使用这些数据为单例编写初始化代码。moc_headername.cppheadername.hdstaticMetaObject

它所做的另一件重要的事情是生成对象方法的代码,该代码qt_metacall()采用对象的方法 id 和参数指针数组,并通过 long 调用该方法,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
                         );
}

最后,该connect()调用将字符串方法签名转换为它们的整数 id(由 使用的qt_metacall())并维护一个信号到插槽连接的列表;当信号发出时,代码会遍历这个列表并通过它们的方法activate()调用适当的对象“槽” 。qt_metacall()

总而言之,静态QMetaObject实例存储“元信息”(方法签名字符串等),生成的qt_metacall()方法提供“方法表”,允许通过索引调用任何信号/插槽,moc使用这些索引生成的信号实现via activate(),最后connect()完成维护信号到插槽索引映射列表的工作。

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

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

PS。因为我想提高我对 Qt 实现的理解——请让我知道我复述中的任何不一致之处!


由于一些热心的编辑删除了我的另一个(较早的)答案,我将在此处附加文本(我遗漏了一些包含在 Pavel Shved 的帖子中的细节,我怀疑删除答案的人是否关心。)

@Pavel Shved:

我很确定在 Qt 标头的某处存在一行:

#define emit

只是为了确认:通过 Google 代码搜索在旧 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› kernel› qobjectdefs.h


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


这是另一个答案:Qt 问题:信号和插槽如何工作?

于 2012-03-07T07:49:56.540 回答