【发布时间】:2010-11-27 06:36:42
【问题描述】:
这个问题已经在这个论坛上问过了,但我不明白这个概念。
我正在阅读,似乎信号和槽是使用函数指针实现的,即信号是一个大函数,它在它内部调用所有连接的槽(函数指针)。它是否正确?而生成的moc文件在整个故事中的作用是什么?我不明白信号函数如何知道要调用哪些插槽,即哪些插槽连接到该信号。
感谢您的宝贵时间
【问题讨论】:
标签: qt qt4 signals-slots
这个问题已经在这个论坛上问过了,但我不明白这个概念。
我正在阅读,似乎信号和槽是使用函数指针实现的,即信号是一个大函数,它在它内部调用所有连接的槽(函数指针)。它是否正确?而生成的moc文件在整个故事中的作用是什么?我不明白信号函数如何知道要调用哪些插槽,即哪些插槽连接到该信号。
感谢您的宝贵时间
【问题讨论】:
标签: qt qt4 signals-slots
Qt 以类似于解释语言的方式实现这些功能。 IE。它构建将信号名称映射到函数指针的符号表,维护它们并在需要时按函数名称查找函数指针。
每次你发出一个信号,即写
emit something();
您实际上调用了something() 函数,它由元对象编译器自动生成并放入*.moc 文件中。在这个函数中,它会检查这个信号当前连接到哪些槽,并且通过符号表(以上述方式)顺序调用适当的槽函数(您在自己的源代码中实现)。并且emit 与其他特定于Qt 的关键字一样,在生成*.moc 之后被C++ 预处理器丢弃。实际上,在 Qt 标头之一 (qobjectdefs.h) 中,存在这样的行:
#define slots
#define signals protected
#define emit
连接函数 (connect) 只是修改维护在 *.moc 文件中的符号表,并且传递给它的参数(使用 SIGNAL() 和 `SLOT 宏)也被预处理以匹配表。
这是一般的想法。在他或她的另一个答案中,ジョージ 为我们提供了有关此主题的链接 to trolltech mailing list 和 another SO question。
【讨论】:
我想我应该添加以下内容。
有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 类头 headername.h 创建 moc_headername.cpp 文件时,它会将签名字符串和其他正确初始化 d 结构所需的数据放在那里,然后写入使用此数据的 staticMetaObject 单例的初始化代码。
它所做的另一件重要的事情是为对象的qt_metacall() 方法生成代码,该代码采用对象的方法ID 和参数指针数组,并通过长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 生成的信号实现通过activate() 使用这些索引,最后connect() 负责维护信号到插槽索引映射列表。
*注意:当我们想要在不同线程之间传递信号时,这种方案有一个复杂的情况(我怀疑必须查看blocking_activate() 代码),但我希望总体思路保持不变)
这是我对链接文章的非常粗略的理解,很容易出错,所以我建议你直接去阅读)
PS。因为我想提高我对 Qt 实现的理解——请让我知道我复述中的任何不一致之处!
由于一些热心的编辑删除了我的其他(较早的)答案,我将在此处附加文本(我遗漏了一些未并入 Pavel Shved 帖子的细节,我怀疑这个人谁删除了答案在乎。)
@Pavel Shved:
我很确定在 Qt 标头的某处存在一行:
#define emit
只是为了确认:通过 Google 代码搜索在旧 Qt 代码中找到它。它很可能仍然存在);找到的位置路径是:
ftp://ftp.slackware-brasil.com.br› slackware-7.1› 贡献› kde-1.90› qt-2.1.1.tgz› 用户名› 库› qt-2.1.1› 来源› 核心> qobjectdefs.h
另一个补充链接:http://lists.trolltech.com/qt-interest/2007-05/thread00691-0.html -- 请参阅 Andreas Pakulat 的答案
【讨论】: