这个问题已经在这个论坛中被问过,但我不理解这个概念。
我读了一些资料,似乎信号和槽使用函数指针来实现,也就是说信号是一个大的函数,在其中调用所有连接的槽(函数指针)。这是正确的吗?生成的 moc 文件在整个过程中扮演着什么角色?我不明白信号函数如何知道要调用哪些槽,即哪些槽与此信号相连。
感谢您的时间
这个问题已经在这个论坛中被问过,但我不理解这个概念。
我读了一些资料,似乎信号和槽使用函数指针来实现,也就是说信号是一个大的函数,在其中调用所有连接的槽(函数指针)。这是正确的吗?生成的 moc 文件在整个过程中扮演着什么角色?我不明白信号函数如何知道要调用哪些槽,即哪些槽与此信号相连。
感谢您的时间
Qt以类似解释型语言的方式实现这些功能。也就是说,它构建符号表来映射信号名称到函数指针,并在需要时维护并通过函数名查找函数指针。
每次发出信号(即写入
emit something();
实际上,你调用了something()
函数,它是由元对象编译器自动生成并放置在一个*.moc
文件中。在这个函数内部,检查了此信号当前连接到哪些槽,并通过符号表(如上所述)顺序调用适当的槽函数(这些函数是你在自己的源代码中实现的)。而像emit
这样的Qt特定关键字,在生成*.moc
文件之后被C++预处理器丢弃。实际上,在Qt头文件之一qobjectdefs.h中存在这样的代码行:
#define slots
#define signals protected
#define emit
connect
函数只是修改*.moc
文件中维护的符号表,并且传递给它的参数(使用SIGNAL()
和SLOT
宏)也经过预处理以匹配这些表。
这是总体思路。在他或她的另一个回答中,ジョージ向我们提供了链接,指向trolltech邮件列表和关于这个主题的另一个SO问题。
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()
使用的 id),并维护信号与槽连接的列表;当发出信号时,activate()
代码会遍历此列表,并通过其 qt_metacall()
方法调用适当的对象“槽”。
总之,静态的 QMetaObject
实例存储“元信息”(方法签名字符串等),生成的 qt_metacall()
方法提供了“方法表”,允许通过索引调用任何信号/槽,由 moc
生成的信号实现使用这些索引通过 activate()
,最后 connect()
执行维护信号到槽索引映射列表的工作。
*注意:当我们想在不同线程之间传递信号时,这种方案会有一些复杂性(我怀疑必须查看 blocking_activate()
代码),但我希望总体思想保持不变)
这是我对链接文章的非常粗略的理解,可能是错误的,所以我建议直接去阅读它)
附注:由于我想提高自己对Qt实现的理解,请让我知道我叙述中任何不一致的地方!
由于我的另一个(早期的)答案被一些热心的编辑删除了,我将在此附上文本(我错过了一些细节,这些细节没有在Pavel Shved的帖子中包含,而我怀疑删除答案的人并不关心。)
@Pavel Shved:
我非常确定在Qt头文件中的某个地方存在一行:
#define emit
只是为了确认:通过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› kernel› qobjectdefs.h
另一个补充链接:http://lists.trolltech.com/qt-interest/2007-05/thread00691-0.html -- 参见 Andreas Pakulat 的回答。
以下是答案的另一部分: Qt问题:信号和槽如何工作?