use*_*508 23 qt qt4 signals-slots
这个问题已在本论坛中提出,但我不明白这个概念.
我正在四处阅读,似乎信号和插槽是使用函数指针实现的,即信号是一个很大的函数,它在里面调用所有连接的槽(函数指针).它是否正确?在整个故事中生成的moc文件的作用是什么?我不明白信号功能如何知道哪个插槽要调用,即哪个插槽连接到该信号.
谢谢你的时间
P S*_*ved 16
Qt以类似于解释语言的方式实现这些东西.即它构造符号表,将信号名称映射到函数指针,维护它们并在需要时按函数名称查找函数指针.
每次发出信号,即写入
emit something();
Run Code Online (Sandbox Code Playgroud)
你实际上调用了这个something()函数,它由元对象编译器自动生成并放入一个*.moc文件中.在此函数中,此时检查此信号连接到哪些插槽,并通过符号表(以上述方式)顺序调用适当的插槽功能(在您自己的源中实现).和emit其他特定于Qt的关键字一样,C++预处理器在*.moc生成之后就被丢弃了.实际上,在其中一个Qt头文件(qobjectdefs.h)中,存在以下行:
#define slots
#define signals protected
#define emit
Run Code Online (Sandbox Code Playgroud)
Connection函数(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 ...
}
Run Code Online (Sandbox Code Playgroud)
反过来,这个实例在初始化时将存储信号和槽的签名("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;
};
Run Code Online (Sandbox Code Playgroud)
现在,当为Qt类头moc创建moc_headername.cpp文件时,它会在headername.h其中放置正确初始化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 ...
}
Run Code Online (Sandbox Code Playgroud)
最后,为每个信号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
);
}
Run Code Online (Sandbox Code Playgroud)
最后,connect()调用将字符串方法签名转换为它们的整数id(由其使用的那些qt_metacall())并维护信号到槽连接的列表; 当发出信号时,activate()代码通过该列表并通过他们的qt_metacall()方法调用适当的对象"slots" .
总而言之,静态QMetaObject实例存储"元信息"(方法签名字符串等),生成的qt_metacall()方法提供"方法表",允许索引调用任何信号/槽,moc使用这些索引生成的信号实现通过activate(),最后connect()完成维护信号到插槽索引映射列表的工作.
*注意:当我们想要在不同线程之间传递信号时,这个方案有一个复杂的情况(我怀疑一个人必须查看blocking_activate()代码),但我希望一般的想法保持不变)
这是我对链接文章的粗略理解,这很容易出错,所以我建议直接去阅读)
PS.因为我想提高我对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问题:信号和插槽如何工作?