QObject的Connect方法
前两天分析了元对象系统的中,元对象编译器所做的部分,进过分析后,我们可以清楚的看到,实际上元对象编译器做了很简单的一个工作,甚至我们可以自己写一个程序来对这一部分代码进行优化。
对于Qt生成的代码,我最不满意的地方就是那个通过switch-case进行信号和槽的选择调用,速度好慢的,不知道能否采用其他方式进行一定得优化。
言归正传,我们开始今天的分析,关于connect函数的实现。要想了解connect的实现首先要看参数是怎么传递的:
信号的函数和槽的函数通过两个宏定义(SIGNAL,SLOT)完成了一次转换,具体是怎么做的呢?我们看下这两个宏的定义:
Q_CORE_EXPORT const char *qFlagLocation(const char *method);
#define QTOSTRING_HELPER(s) #s
#define QTOSTRING(s) QTOSTRING_HELPER(s)
#ifndef QT_NO_DEBUG
# define QLOCATION "/0"__FILE__":"QTOSTRING(__LINE__)
# define METHOD(a) qFlagLocation("0"#a QLOCATION)
# define SLOT(a) qFlagLocation("1"#a QLOCATION)
# define SIGNAL(a) qFlagLocation("2"#a QLOCATION)
#else
# define METHOD(a) "0"#a
# define SLOT(a) "1"#a
# define SIGNAL(a) "2"#a
#endif
通过这个,我们可以看到,实际上SIGNAL和SLOT实现的方法是将函数名称转换为字符串。这里采用qDebug()输入转换后的参数,发现,实际上Qt只是为每个函数的名字前面加上了数字编号的0、1、2,0表示普通方法,1表示槽,2表示信号。但是弄不明白的一个问题是,看似在函数转换的时候,如果没有定义了QT_NO_DEBUG的时候,Qt还添加了一些信息在里面,但是转换后的结果都是一个。
通过前面的一些分析,我们将宏替换掉,得到的真实代码是这样的:
qFlagLocation(“1Hello/0”__FILE__”:__LINE__”);
我用程序测试了一下这个代码,发现其真实的结果是:
__FILE__显示了当前文件的名称,__LINE__显示了当前指令所在的行数。
这样的写法是在Debug版本中的,在Release版本中的就只有”1Hello”了。接下来,我们看下qFlagLocation的实现:
const int flagged_locations_count = 2;
static const char* flagged_locations[flagged_locations_count] = {0};
const char *qFlagLocation(const char *method)
{
static int idx = 0;
flagged_locations[idx] = method;
idx = (idx+1) % flagged_locations_count;
return method;
}
这里保留了当前信号或者槽的名称变量地址。并没有对字符串做任何处理。
这样,我们明白了Qt中SIGNAL宏和SLOT宏产生的效果,接下来就是看connect函数的实现了。
const void *cbdata[] = { sender, signal, receiver, method, &type };
if (QInternal::activateCallbacks(QInternal::ConnectCallback, (void **) cbdata))
return true;
这样,我们就可以清楚的看到,上面这段代码的含义了,将通过形参传递过来的变量的指针赋值到一个数组,然后调用了QInternal类中的activateCallbacks方法,在第一个参数那里,看到了ConnectCallback的字样,从字面的理解就是一个连接的回调函数。让我们进入这个函数,来一看究竟吧。
我们先看下QInternal类的声明:
class Q_CORE_EXPORT QInternal {
public:
enum PaintDeviceFlags {
UnknownDevice = 0x00,
Widget = 0x01,
Pixmap = 0x02,
Image = 0x03,
Printer = 0x04,
Picture = 0x05,
Pbuffer = 0x06, // GL pbuffer
FramebufferObject = 0x07, // GL framebuffer object
CustomRaster = 0x08,
MacQuartz = 0x09,
PaintBuffer = 0x0a,
OpenGL = 0x0b
};
enum RelayoutType {
RelayoutNormal,
RelayoutDragging,
RelayoutDropped
};
enum Callback {
ConnectCallback,
DisconnectCallback,
AdoptCurrentThread,
EventNotifyCallback,
LastCallback
};
enum InternalFunction {
CreateThreadForAdoption,
RefAdoptedThread,
DerefAdoptedThread,
SetCurrentThreadToMainThread,
SetQObjectSender,
GetQObjectSender,
ResetQObjectSender,
LastInternalFunction
};
enum DockPosition {
LeftDock,
RightDock,
TopDock,
BottomDock,
DockCount
};
static bool registerCallback(Callback, qInternalCallback);
static bool unregisterCallback(Callback, qInternalCallback);
static bool activateCallbacks(Callback, void **);
static bool callFunction(InternalFunction func, void **);
};
做了一些枚举数据,这里的类型还是比较熟悉的,之后是注册回到函数,撤销回调函数,激活回调函数等操作。
bool QInternal::registerCallback(Callback cb, qInternalCallback callback)
{
if (cb >= 0 && cb < QInternal::LastCallback) {
QInternal_CallBackTable *cbt = global_callback_table();
cbt->callbacks.resize(cb + 1);
cbt->callbacks[cb].append(callback);
return true;
}
return false;
}
整体的实现不是很多,首先就是重新分配回调函数表的大小,之后将新的回调函数加入。
bool QInternal::unregisterCallback(Callback cb, qInternalCallback callback)
{
if (cb >= 0 && cb < QInternal::LastCallback) {
QInternal_CallBackTable *cbt = global_callback_table();
return (bool) cbt->callbacks[cb].removeAll(callback);
}
return false;
}
撤销的实现也很简单,直接进行了删除操作。
bool QInternal::activateCallbacks(Callback cb, void **parameters)
{
Q_ASSERT_X(cb >= 0, "QInternal::activateCallback()", "Callback id must be a valid id");
QInternal_CallBackTable *cbt = global_callback_table();
if (cbt && cb < cbt->callbacks.size()) {
QList<qInternalCallback> callbacks = cbt->callbacks[cb];
bool ret = false;
for (int i=0; i<callbacks.size(); ++i)
ret |= (callbacks.at(i))(parameters);
return ret;
}
return false;
}
调用则是一个查找的过程,在找到之后,将函数指针对应的做调用即可。可以看出来,整个的过程,只是简单的函数指针调用。
这里还有对回调函数数据的存储方式设计:
struct QInternal_CallBackTable {
QVector<QList<qInternalCallback> > callbacks;
};
这部分很简单,之后的会怎么样呢?才做了这枚一些简单的判定,已经到了必须睡觉的时候,今天只能到这样了。明天继续。
2009年10月19日星期一 23:57