說法,簡單點說就是如何在一個類的一個函數中觸發另一個類的另一個函數調用,而且還要把相關的參數傳遞過去.好像這和回調函數也有點關係,但是消息機制可比回調函數有用
多了,也複雜多了
多態的底層實現機制只有兩種,一種是按照名稱查表,一種是按照位置查表,兩種方式各有利弊,而C++的虛函數機制無條件的採用了後者,導致的問題就是在子類很少重載基類實現
的時候開銷太大,再加上象界面編程這樣子類衆多的情況,基本上C++的虛函數機制就廢掉了,於是各家庫的編寫者就只好自謀生路了,說到底,這確實是C++語言本身的缺陷
#include <QApplication>
#include <QPushButton>
#include <QPointer>
{
QApplication app(argc, argv);
quit.resize(100, 30);
quit.show();
QObject::connect(&quit, SIGNAL(clicked()), &app, SLOT(quit()));
return app.exec();
}
前面已經說過了,Qt的信號槽機制其實就是按照名稱查表,因此這裏的首要問題是如何構造這個表?
和C++虛函數表機制類似的,在Qt中,這個表就是元數據表,Qt中的元數據表最大的作用就是支持信號槽機制,當然,也可以在此基礎上擴展出更多的功能,因爲
元數據是我們可以直接訪問到的,不再是象虛函數表那樣被編譯器遮遮掩掩的藏了起來,不過Qt似乎還沒有完全發揮元數據的能力,動態屬性,反射之類的機制好像還沒有
#define Q_OBJECT \
public: \
static const QMetaObject staticMetaObject; \
virtual const QMetaObject *metaObject() const; \
virtual void *qt_metacast(const char *); \
QT_TR_FUNCTIONS \
virtual int qt_metacall(QMetaObject::Call, int, void **); \
private:
{
...
struct { // private data
const QMetaObject *superdata;
const char *stringdata;
const uint *data;
const QMetaObject **extradata;
} d;
}
QMetaObject中有一個嵌套類封裝了所有的數據
const QMetaObject *superdata;//這是元數據代表的類的基類的元數據
const char *stringdata;//這是元數據的簽名標記
const uint *data;//這是元數據的索引數組的指針
const QMetaObject **extradata;//這是擴展元數據表的指針,一般是不用的
metaObject的作用是得到元數據表指針
qt_metacast的作用是根據簽名得到相關結構的指針,注意它返回的可是void*指針
qt_metacall的作用是查表然後調用調用相關的函數
# define QT_TR_FUNCTIONS \
static inline QString tr(const char *s, const char *c = 0) \
{ return staticMetaObject.tr(s, c); }
static const uint qt_meta_data_QPushButton[] = {
1, // revision
0, // classname
0, 0, // classinfo
2, 10, // methods
3, 20, // properties
0, 0, // enums/sets
13, 12, 12, 12, 0x0a,
24, 12, 12, 12, 0x08,
44, 39, 0x01095103,
56, 39, 0x01095103,
64, 39, 0x01095103,
};
"QPushButton\0\0showMenu()\0popupPressed()\0bool\0autoDefault\0default\0"
"flat\0"
};
{ &QAbstractButton::staticMetaObject, qt_meta_stringdata_QPushButton,
qt_meta_data_QPushButton, 0 }
};
const QMetaObject *superdata;//這是元數據代表的類的基類的元數據,被填充爲基類的元數據指針&QAbstractButton::staticMetaObject
const char *stringdata;//這是元數據的簽名標記,被填充爲qt_meta_stringdata_QPushButton
const uint *data;//這是元數據的索引數組的指針,被填充爲qt_meta_data_QPushButton
const QMetaObject **extradata;//這是擴展元數據表的指針,一般是不用的,被填充爲0
qt_meta_stringdata_QPushButton發揮作用的機會,可以說真正的元數據應該是qt_meta_data_QPushButton加上qt_meta_stringdata_QPushButton,那麼爲什麼
不把這兩個東西合在一起呢?估計是兩者都不是定長的結構,合在一起反而麻煩吧
{
int revision;
int className;
int classInfoCount, classInfoData;
int methodCount, methodData;
int propertyCount, propertyData;
int enumeratorCount, enumeratorData;
};
static inline const QMetaObjectPrivate *priv(const uint* data)
{ return reinterpret_cast<const QMetaObjectPrivate*>(data); }
static const uint qt_meta_data_QPushButton[] = {
1, // revision 版本號是1
0, // classname 類名存儲在qt_meta_stringdata_QPushButton中,索引是0,因此就是QPushButton了
0, 0, // classinfo 類信息數量爲0,數據也是0
2, 10, // methods QPushButton有2個自定義方法,方法數據存儲在qt_meta_data_QPushButton中,索引是10,就是下面的slots:開始的地方
3, 20, // properties QPushButton有3個自定義屬性,屬性數據存儲在qt_meta_data_QPushButton中,索引是20,就是下面的properties:開始的地方
0, 0, // enums/sets QPushButton沒有自定義的枚舉
13, 12, 12, 12, 0x0a,
第一個自定義方法的簽名存儲在qt_meta_data_QPushButton中,索引是13,就是showMenu()了
24, 12, 12, 12, 0x08,
第二個自定義方法的簽名存儲在qt_meta_data_QPushButton中,索引是24,popupPressed()了
44, 39, 0x01095103,
第一個自定義屬性的簽名存儲在qt_meta_data_QPushButton中,索引是44,就是autoDefault了
第一個自定義屬性的類型存儲在qt_meta_data_QPushButton中,索引是39,就是bool
56, 39, 0x01095103,
第二個自定義屬性的簽名存儲在qt_meta_data_QPushButton中,索引是56,就是default了
第二個自定義屬性的類型存儲在qt_meta_data_QPushButton中,索引是39,就是bool
64, 39, 0x01095103,
第三個自定義屬性的簽名存儲在qt_meta_data_QPushButton中,索引是64,就是flat了
第三個自定義屬性的類型存儲在qt_meta_data_QPushButton中,索引是39,就是bool
};
"QPushButton\0\0showMenu()\0popupPressed()\0bool\0autoDefault\0default\0"
"flat\0"
};
這裏把\0直接替換爲\是爲了數數的方便
static const uint qt_meta_data_QAbstractButton[] = {
1, // revision
0, // classname
0, 0, // classinfo
12, 10, // methods
9, 70, // properties
0, 0, // enums/sets
17, 16, 16, 16, 0x05,
27, 16, 16, 16, 0x05,
46, 38, 16, 16, 0x05,
60, 16, 16, 16, 0x25,
70, 38, 16, 16, 0x05,
89, 84, 16, 16, 0x0a,
113, 108, 16, 16, 0x0a,
131, 16, 16, 16, 0x2a,
146, 16, 16, 16, 0x0a,
154, 16, 16, 16, 0x0a,
163, 16, 16, 16, 0x0a,
182, 180, 16, 16, 0x1a,
202, 194, 0x0a095103,
213, 207, 0x45095103,
224, 218, 0x15095103,
246, 233, 0x4c095103,
260, 255, 0x01095103,
38, 255, 0x01195103,
270, 255, 0x01095103,
281, 255, 0x01095103,
295, 255, 0x01094103,
};
"QAbstractButton\0\0pressed()\0released()\0checked\0clicked(bool)\0"
"clicked()\0toggled(bool)\0size\0setIconSize(QSize)\0msec\0"
"animateClick(int)\0animateClick()\0click()\0toggle()\0setChecked(bool)\0"
"b\0setOn(bool)\0QString\0text\0QIcon\0icon\0QSize\0iconSize\0"
"QKeySequence\0shortcut\0bool\0checkable\0autoRepeat\0autoExclusive\0"
"down\0"
};
下面開始看信號和槽連接的源碼了
connect函數是連接信號和槽的橋樑,非常關鍵
bool QObject::connect(const QObject *sender, const char *signal,
const QObject *receiver, const char *method,
Qt::ConnectionType type)
{
#ifndef QT_NO_DEBUG
bool warnCompat = true;
#endif
if (type == Qt::AutoCompatConnection) {
type = Qt::AutoConnection;
#ifndef QT_NO_DEBUG
warnCompat = false;
#endif
}
if (sender == 0 || receiver == 0 || signal == 0 || method == 0) {
#ifndef QT_NO_DEBUG
qWarning("Object::connect: Cannot connect %s::%s to %s::%s",
sender ? sender->metaObject()->className() : "(null)",
signal ? signal+1 : "(null)",
receiver ? receiver->metaObject()->className() : "(null)",
method ? method+1 : "(null)");
#endif
return false;
}
QByteArray tmp_signal_name;
// 檢查是否是信號標記
if (!check_signal_macro(sender, signal, "connect", "bind"))
return false;
#endif
// 得到元數據類
const QMetaObject *smeta = sender->metaObject();
++signal; //skip code跳過信號標記,直接得到信號標識
// 得到信號的索引
int signal_index = smeta->indexOfSignal(signal);
if (signal_index < 0) {
// check for normalized signatures
tmp_signal_name = QMetaObject::normalizedSignature(signal).prepend(*(signal - 1));
signal = tmp_signal_name.constData() + 1;
signal_index = smeta->indexOfSignal(signal);
if (signal_index < 0) {
#ifndef QT_NO_DEBUG
err_method_notfound(QSIGNAL_CODE, sender, signal, "connect");
err_info_about_objects("connect", sender, receiver);
#endif
return false;
}
}
int membcode = method[0] - '0';
// 檢查是否是槽,用QSLOT_CODE 1標記
if (!check_method_code(membcode, receiver, method, "connect"))
return false;
#endif
++method; // skip code
const QMetaObject *rmeta = receiver->metaObject();
int method_index = -1;
// 這裏是一個case,信號即可以和信號連接也可以和槽連接
switch (membcode) {
case QSLOT_CODE:
// 得到槽的索引
method_index = rmeta->indexOfSlot(method);
break;
case QSIGNAL_CODE:
// 得到信號的索引
method_index = rmeta->indexOfSignal(method);
break;
}
if (method_index < 0) {
// check for normalized methods
tmp_method_name = QMetaObject::normalizedSignature(method);
method = tmp_method_name.constData();
switch (membcode) {
case QSLOT_CODE:
method_index = rmeta->indexOfSlot(method);
break;
case QSIGNAL_CODE:
method_index = rmeta->indexOfSignal(method);
break;
}
}
#ifndef QT_NO_DEBUG
err_method_notfound(membcode, receiver, method, "connect");
err_info_about_objects("connect", sender, receiver);
#endif
return false;
}
#ifndef QT_NO_DEBUG
// 檢查參數,信號和槽的參數必須一致,槽的參數也可以小於信號的參數
if (!QMetaObject::checkConnectArgs(signal, method)) {
qWarning("Object::connect: Incompatible sender/receiver arguments"
"\n\t%s::%s --> %s::%s",
sender->metaObject()->className(), signal,
receiver->metaObject()->className(), method);
return false;
}
#endif
if (type == Qt::QueuedConnection
&& !(types = ::queuedConnectionTypes(smeta->method(signal_index).parameterTypes())))
return false;
{
// 得到方法的元數據
QMetaMethod smethod = smeta->method(signal_index);
QMetaMethod rmethod = rmeta->method(method_index);
if (warnCompat) {
if(smethod.attributes() & QMetaMethod::Compatibility) {
if (!(rmethod.attributes() & QMetaMethod::Compatibility))
qWarning("Object::connect: Connecting from COMPAT signal (%s::%s).", smeta->className(), signal);
} else if(rmethod.attributes() & QMetaMethod::Compatibility && membcode != QSIGNAL_CODE) {
qWarning("Object::connect: Connecting from %s::%s to COMPAT slot (%s::%s).",
smeta->className(), signal, rmeta->className(), method);
}
}
}
#endif
// 調用元數據類的連接
QMetaObject::connect(sender, signal_index, receiver, method_index, type, types);
// 發送連接的通知,現在的實現是空的
const_cast<QObject*>(sender)->connectNotify(signal - 1);
return true;
}
static bool check_signal_macro(const QObject *sender, const char *signal,
const char *func, const char *op)
{
int sigcode = (int)(*signal) - '0';
if (sigcode != QSIGNAL_CODE) {
if (sigcode == QSLOT_CODE)
qWarning("Object::%s: Attempt to %s non-signal %s::%s",
func, op, sender->metaObject()->className(), signal+1);
else
qWarning("Object::%s: Use the SIGNAL macro to %s %s::%s",
func, op, sender->metaObject()->className(), signal);
return false;
}
return true;
}
int QMetaObject::indexOfSignal(const char *signal) const
{
int i = -1;
const QMetaObject *m = this;
while (m && i < 0) {
// 根據方法的數目倒序的查找
for (i = priv(m->d.data)->methodCount-1; i >= 0; --i)
// 得到該方法的類型
if ((m->d.data[priv(m->d.data)->methodData + 5*i + 4] & MethodTypeMask) == MethodSignal
&& strcmp(signal, m->d.stringdata
// 得到方法名稱的偏移
+ m->d.data[priv(m->d.data)->methodData + 5*i]) == 0) {
//如果找到了正確的方法,再增加所有基類的方法偏移量
i += m->methodOffset();
break;
}
// 在父類中繼續找
m = m->d.superdata;
}
#ifndef QT_NO_DEBUG
// 判斷是否於基類中的衝突
if (i >= 0 && m && m->d.superdata) {
int conflict = m->d.superdata->indexOfMethod(signal);
if (conflict >= 0)
qWarning("QMetaObject::indexOfSignal:%s: Conflict with %s::%s",
m->d.stringdata, m->d.superdata->d.stringdata, signal);
}
#endif
return i;
}
int QMetaObject::methodOffset() const
{
int offset = 0;
const QMetaObject *m = d.superdata;
while (m) {
offset += priv(m->d.data)->methodCount;
m = m->d.superdata;
}
return offset;
}
QMetaMethod QMetaObject::method(int index) const
{
int i = index;
// 要減去基類的偏移
i -= methodOffset();
// 如果本類找不到,就到基類中去找
if (i < 0 && d.superdata)
return d.superdata->method(index);
QMetaMethod result;
if (i >= 0 && i < priv(d.data)->methodCount) {
// 這裏是類的元數據
result.mobj = this;
// 這裏是方法相關數據在data數組中的偏移量
result.handle = priv(d.data)->methodData + 5*i;
}
return result;
}
const QObject *receiver, int method_index, int type, int *types)
{
// 得到全局的連接列表
QConnectionList *list = ::connectionList();
if (!list)
return false;
QWriteLocker locker(&list->lock);
// 增加一個連接
list->addConnection(const_cast<QObject *>(sender), signal_index,
const_cast<QObject *>(receiver), method_index, type, types);
return true;
}
QObject *receiver, int method,
int type, int *types)
{
// 構造一個連接
QConnection c = { sender, signal, receiver, method, 0, 0, types };
c.type = type; // don't warn on VC++6
int at = -1;
// 如果有中間被刪除的連接,可以重用這個空間
for (int i = 0; i < unusedConnections.size(); ++i) {
if (!connections.at(unusedConnections.at(i)).inUse) {
// reuse an unused connection
at = unusedConnections.takeAt(i);
connections[at] = c;
break;
}
}
if (at == -1) {
// append new connection
at = connections.size();
// 加入一個連接
connections << c;
}
// 構造sender,receiver連接的哈希表,加速搜索速度
sendersHash.insert(sender, at);
receiversHash.insert(receiver, at);
}
void Foo::setValue(int v)
{
if (v != val)
{
val = v;
emit valueChanged(v);
}
}
// SIGNAL 0
void Foo::valueChanged(int _t1)
{
// 首先把參數打包
void *_a[] = { 0, const_cast<void*>(reinterpret_cast<const void*>(&_t1)) };
// 調用元數據類的激活
QMetaObject::activate(this, &staticMetaObject, 0, _a);
}
void **argv)
{
// 增加一個基類偏移量
int offset = m->methodOffset();
activate(sender, offset + local_signal_index, offset + local_signal_index, argv);
}
{
// 這裏得到的是QObject的數據,首先判斷是否爲阻塞設置
if (sender->d_func()->blockSig)
return;
QConnectionList * const list = ::connectionList();
if (!list)
return;
if (qt_signal_spy_callback_set.signal_begin_callback != 0) {
locker.unlock();
qt_signal_spy_callback_set.signal_begin_callback(sender, from_signal_index,
argv ? argv : empty_argv);
locker.relock();
}
QConnectionList::Hash::const_iterator it = list->sendersHash.find(sender);
const QConnectionList::Hash::const_iterator end = list->sendersHash.constEnd();
if (qt_signal_spy_callback_set.signal_end_callback != 0) {
locker.unlock();
qt_signal_spy_callback_set.signal_end_callback(sender, from_signal_index);
locker.relock();
}
return;
}
const int currentQThreadId = currentThread ? QThreadData::get(currentThread)->id : -1;
QVarLengthArray<int> connections;
for (; it != end && it.key() == sender; ++it) {
connections.append(it.value());
// 打上使用標記,因爲可能是放在隊列中
list->connections[it.value()].inUse = 1;
}
const int at = connections.constData()[connections.size() - (i + 1)];
QConnectionList * const list = ::connectionList();
// 得到連接
QConnection &c = list->connections[at];
c.inUse = 0;
if (!c.receiver || (c.signal < from_signal_index || c.signal > to_signal_index))
continue;
// determine if this connection should be sent immediately or
// put into the event queue
if ((c.type == Qt::AutoConnection
&& (currentQThreadId != sender->d_func()->thread
|| c.receiver->d_func()->thread != sender->d_func()->thread))
|| (c.type == Qt::QueuedConnection)) {
::queued_activate(sender, c, argv);
continue;
}
const int method = c.method;
QObject * const previousSender = c.receiver->d_func()->currentSender;
c.receiver->d_func()->currentSender = sender;
list->lock.unlock();
qt_signal_spy_callback_set.slot_begin_callback(c.receiver, method, argv ? argv : empty_argv);
c.receiver->qt_metacall(QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);
#else
try {
// 調用receiver的方法
c.receiver->qt_metacall(QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv);
} catch (...) {
list->lock.lockForRead();
if (c.receiver)
c.receiver->d_func()->currentSender = previousSender;
throw;
}
#endif
qt_signal_spy_callback_set.slot_end_callback(c.receiver, method);
if (c.receiver)
c.receiver->d_func()->currentSender = previousSender;
}
locker.unlock();
qt_signal_spy_callback_set.signal_end_callback(sender, from_signal_index);
locker.relock();
}
}
int Foo::qt_metacall(QMetaObject::Call _c, int _id, void **_a)
{
// 首先在基類中調用方法,返回的id已經變成當前類的方法id了
_id = QObject::qt_metacall(_c, _id, _a);
if (_id < 0)
return _id;
if (_c == QMetaObject::InvokeMetaMethod) {
switch (_id) {
// 這裏就是真正的調用方法了,注意參數的解包用法
case 0: valueChanged(*reinterpret_cast< int(*)>(_a[1])); break;
case 1: setValue(*reinterpret_cast< int(*)>(_a[1])); break;
}
_id -= 2;
}
return _id;
}