※注意:本稿は Qt のソースを引用しており、その部分の著作権は Nokia にあり、
ライセンスは LGPL である。
したがって、本稿のソース部分をあなたのプロジェクトで使用するとLGPLに感染するので注意されたし。
引用元の著作権・ライセンス:
/**************************************************************************** ** ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtCore module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial Usage ** Licensees holding valid Qt Commercial licenses may use this file in ** accordance with the Qt Commercial License Agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and Nokia. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3.0 as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU General Public License version 3.0 requirements will be ** met: http://www.gnu.org/copyleft/gpl.html. ** ** If you have questions regarding the use of this file, please contact ** Nokia at qt-info@nokia.com. ** $QT_END_LICENSE$ ** ****************************************************************************/
signals または slots 宣言されたメソッドは、moc により、メソッド名、引数情報を抽出され、
アプリケーション実行時にそれらを参照することが出来るようになる。
connect() により、特定オブジェクトの signal が emit(発行)された時に実行される
特定オブジェクトの slot メソッドを接続することができる。
アプリケーション実行時に、disconnect() により接続を解除することもできる。
1: // copyright (c) 2010 by N.Tsuda, MIT License 2: #include <QObject> 3: 4: class CFoo : public QObject 5: { 6: Q_OBJECT 7: public: 8: explicit CFoo(QObject *parent = 0); 9: 10: public slots: 11: int hoge2(char, short); 12: void hoge(); 13: signals: 14: void mySignal(); 15: };
バージョン番号などの値、次節の stringdata インデックス値を含む uint 配列
signalCount までは QMetaObjectPrivate のデータ。signed, unsigned が一致していないのはご愛嬌?
0 を終端データにしているので、コンストラクタはシグナル・スロットに出来ない。;^p
static const uint qt_meta_data_CFoo[] = { // content: 4, // revision 0, // classname 0, 0, // classinfo 3, 14, // methods 0, 0, // properties 0, 0, // enums/sets 0, 0, // constructors 0, // flags 1, // signalCount // signals: signature, parameters, type, tag, flags 6, 5, 5, 5, 0x05, // slots: signature, parameters, type, tag, flags 23, 21, 17, 5, 0x0a, 41, 5, 5, 5, 0x0a, 0 // eod };
クラス名、メソッド名などの文字列情報が含まれる。前節の qt_meta_data_CFoo に本配列のインデクス値が含まれる。
static const char qt_meta_stringdata_CFoo[] = { "CFoo\0\0mySignal()\0int\0,\0hoge2(char,short)\0" "hoge()\0" };
各文字列のインデックス:
0 "CFoo" 5 "" 6 "mySignal()" 17 "int" 21 "," 23 "hoge2(char,short)" 41 ""hoge()"
emit 時にコールされる activate() へ渡されるメタオブジェクト情報
const QMetaObject CFoo::staticMetaObject = { { &QObject::staticMetaObject, qt_meta_stringdata_CFoo, qt_meta_data_CFoo, 0 } };
_c == QMetaObject::InvokeMetaMethod ならは、_id を指標に、メンバ関数をコールする。
返り値を格納するアドレスは _a[0] に、第1引数以降のアドレスは _a[1] 以降に格納されている。
signal, slot メソッドには、qt_meta_data_CFoo と同じ順序で、シーケンシャル番号が振られている。
最後に _id から3を減算しているのは親クラスのメソッドをコール可能にするため
int CFoo::qt_metacall(QMetaObject::Call _c, int _id, void **_a) { _id = QObject::qt_metacall(_c, _id, _a); if (_id < 0) return _id; if (_c == QMetaObject::InvokeMetaMethod) { switch (_id) { case 0: mySignal(); break; case 1: { int _r = hoge2((*reinterpret_cast< char(*)>(_a[1])),(*reinterpret_cast< short(*)>(_a[2]))); if (_a[0]) *reinterpret_cast< int*>(_a[0]) = _r; } break; case 2: hoge(); break; default: ; } _id -= 3; } return _id; }
SIGNAL(), SLOT() はメソッド名を文字列とし、先頭に '2'(for SIGNAL), '1'(for SLOT) を付加するマクロ。
"C:\Qt\2010.04\qt\src\corelib\kernel\qobjectdefs.h"
Q_CORE_EXPORT const char *qFlagLocation(const char *method); #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() マクロにより、メソッド名は文字列に変換されて QObject::connect() がコールされる。
bool QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type) { ..... }
const QMetaObject *smeta = sender->metaObject(); const char *signal_arg = signal; ++signal; //skip code int signal_index = QMetaObjectPrivate::indexOfSignalRelative(&smeta, signal);
1: bool QMetaObjectPrivate::connect(const QObject *sender, int signal_index, 2: const QObject *receiver, int method_index, int type, int *types) 3: { 4: QObject *s = const_cast(sender); 5: QObject *r = const_cast (receiver); 6: 7: QOrderedMutexLocker locker(signalSlotLock(sender), 8: signalSlotLock(receiver)); 9: 10: if (type & Qt::UniqueConnection) { // Qt::UniqueConnection の場合、重複 connect はエラーとなる 11: QObjectConnectionListVector *connectionLists = QObjectPrivate::get(s)->connectionLists; 12: if (connectionLists && connectionLists->count() > signal_index) { 13: const QObjectPrivate::Connection *c2 = 14: (*connectionLists)[signal_index].first; 15: 16: while (c2) { 17: if (c2->receiver == receiver && c2->method == method_index) 18: return false; 19: c2 = c2->nextConnectionList; 20: } 21: } 22: type &= Qt::UniqueConnection - 1; // あまり感心しないコーディング(?) 23: } 24: // Connection オブジェクト生成 25: QObjectPrivate::Connection *c = new QObjectPrivate::Connection; 26: c->sender = s; 27: c->receiver = r; 28: c->method = method_index; 29: c->connectionType = type; 30: c->argumentTypes = types; 31: c->nextConnectionList = 0; 32: // ↓コネクションリストに追加、参照:addConnection 33: QT_TRY { 34: QObjectPrivate::get(s)->addConnection(signal_index, c); 35: } QT_CATCH(...) { 36: delete c; 37: QT_RETHROW; 38: } 39: // ↓レシーバ側のコネクションリンクを張っている部分 40: c->prev = &(QObjectPrivate::get(r)->senders); 41: c->next = *c->prev; 42: *c->prev = c; 43: if (c->next) 44: c->next->prev = &c->next; 45: 46: QObjectPrivate *const sender_d = QObjectPrivate::get(s); 47: if (signal_index < 0) { 48: sender_d->connectedSignals[0] = sender_d->connectedSignals[1] = ~0; 49: } else if (signal_index < (int)sizeof(sender_d->connectedSignals) * 8) { 50: sender_d->connectedSignals[signal_index >> 5] |= (1 << (signal_index & 0x1f)); 51: } 52: 53: return true; 54: }
Connection *prev; ではなく Connection **prev; としているは、 リンクリストが環状ではなく、リンク元のクラスを任意にするため。
1: class Q_CORE_EXPORT QObjectPrivate : public QObjectData 2: { 3: Q_DECLARE_PUBLIC(QObject) 4: ..... 5: struct Connection 6: { 7: QObject *sender; 8: QObject *receiver; 9: int method; 10: uint connectionType : 3; // 0 == auto, 1 == direct, 2 == queued, 4 == blocking 11: QBasicAtomicPointerargumentTypes; 12: // The next pointer for the singly-linked ConnectionList 13: Connection *nextConnectionList; 14: //senders linked list 15: Connection *next; 16: Connection **prev; 17: ~Connection(); 18: }; 19: ..... 20: static QObjectPrivate *get(QObject *o) { 21: return o->d_func(); // d_func() は QObject* から QObjectPrivate* を取得するメソッド 22: } 23: ..... 24: QObjectConnectionListVector *connectionLists; 25: Connection *senders; // linked list of connections connected to this object 26: ..... 27: };
void QObjectPrivate::addConnection(int signal, Connection *c) { if (!connectionLists) // connectionLists がなければ生成 connectionLists = new QObjectConnectionListVector(); if (signal >= connectionLists->count()) // 空きが無ければ確保 connectionLists->resize(signal + 1); // コネクションのリストに追加 ConnectionList &connectionList = (*connectionLists)[signal]; if (connectionList.last) { connectionList.last->nextConnectionList = c; } else { connectionList.first = c; } connectionList.last = c; cleanConnectionLists(); }
sender のコネクションリスト(QObjectPrivate::Connection *c)から (receiver, method_index) とのコネクションを外す。
レシーバ側の双方向リンクからは実際に外すが、センダー側は消去せず c->receiver = 0; とするだけ。
/*! \internal Helper function to remove the connection from the senders list and setting the receivers to 0 */ bool QMetaObjectPrivate::disconnectHelper(QObjectPrivate::Connection *c, const QObject *receiver, int method_index, QMutex *senderMutex, DisconnectType disconnectType) { bool success = false; while (c) { if (c->receiver && (receiver == 0 || (c->receiver == receiver && (method_index < 0 || c->method == method_index)))) { bool needToUnlock = false; QMutex *receiverMutex = 0; if (!receiver) { receiverMutex = signalSlotLock(c->receiver); // need to relock this receiver and sender in the correct order needToUnlock = QOrderedMutexLocker::relock(senderMutex, receiverMutex); } if (c->receiver) { *c->prev = c->next; // 双方向リンクから外す if (c->next) c->next->prev = c->prev; } if (needToUnlock) receiverMutex->unlock(); c->receiver = 0; success = true; if (disconnectType == DisconnectOne) return success; } c = c->nextConnectionList; } return success; }
signal_index < 0 の場合は、センダーの全てのコネクションを解除
そうでない場合は、指定 signal_index の (receiver, method_index) とのコネクションを解除
bool QMetaObjectPrivate::disconnect(const QObject *sender, int signal_index, const QObject *receiver, int method_index, DisconnectType disconnectType) { if (!sender) return false; QObject *s = const_cast(sender); QMutex *senderMutex = signalSlotLock(sender); QMutex *receiverMutex = receiver ? signalSlotLock(receiver) : 0; QOrderedMutexLocker locker(senderMutex, receiverMutex); QObjectConnectionListVector *connectionLists = QObjectPrivate::get(s)->connectionLists; if (!connectionLists) return false; // prevent incoming connections changing the connectionLists while unlocked ++connectionLists->inUse; bool success = false; if (signal_index < 0) { // remove from all connection lists for (signal_index = -1; signal_index < connectionLists->count(); ++signal_index) { QObjectPrivate::Connection *c = (*connectionLists)[signal_index].first; if (disconnectHelper(c, receiver, method_index, senderMutex, disconnectType)) { success = true; connectionLists->dirty = true; } } } else if (signal_index < connectionLists->count()) { QObjectPrivate::Connection *c = (*connectionLists)[signal_index].first; // センダー側の signal_index 番目のコネクションリスト if (disconnectHelper(c, receiver, method_index, senderMutex, disconnectType)) { success = true; connectionLists->dirty = true; } } --connectionLists->inUse; Q_ASSERT(connectionLists->inUse >= 0); if (connectionLists->orphaned && !connectionLists->inUse) delete connectionLists; return success; }
1: // SIGNAL 0 2: void CFoo::mySignal() 3: { 4: QMetaObject::activate(this, &staticMetaObject, 0, 0); 5: }
"C:\Qt\2010.04\qt\src\corelib\kernel\qobject.cpp"
指定オブジェクトの指定番号のシグナルにコネクトされているスロットをコールする。
コネクトタイプには Qt::QueuedConnection などがあるので、それぞれで処理を分けている。
void QMetaObject::activate(QObject *sender, const QMetaObject *m, int local_signal_index, void **argv) { int signalOffset; int methodOffset; computeOffsets(m, &signalOffset, &methodOffset); int signal_index = signalOffset + local_signal_index; if (!sender->d_func()->isSignalConnected(signal_index)) return; // nothing connected to these signals, and no spy if (sender->d_func()->blockSig) return; int signal_absolute_index = methodOffset + local_signal_index; void *empty_argv[] = { 0 }; if (qt_signal_spy_callback_set.signal_begin_callback != 0) { qt_signal_spy_callback_set.signal_begin_callback(sender, signal_absolute_index, argv ? argv : empty_argv); } QMutexLocker locker(signalSlotLock(sender)); QThreadData *currentThreadData = QThreadData::current(); QObjectConnectionListVector *connectionLists = sender->d_func()->connectionLists; if (!connectionLists) { locker.unlock(); if (qt_signal_spy_callback_set.signal_end_callback != 0) qt_signal_spy_callback_set.signal_end_callback(sender, signal_absolute_index); return; } ++connectionLists->inUse; if (signal_index >= connectionLists->count()) { signal_index = -2; //for "all signals"; } do { QObjectPrivate::Connection *c = connectionLists->at(signal_index).first; if (!c) continue; // We need to check against last here to ensure that signals added // during the signal emission are not emitted in this emission. QObjectPrivate::Connection *last = connectionLists->at(signal_index).last; do { if (!c->receiver) // dissconnect 済みの場合 continue; QObject * const receiver = c->receiver; // determine if this connection should be sent immediately or // put into the event queue if ((c->connectionType == Qt::AutoConnection && (currentThreadData != sender->d_func()->threadData || receiver->d_func()->threadData != sender->d_func()->threadData)) || (c->connectionType == Qt::QueuedConnection)) { // レシーバ側のキューに積まれ、イベントループに戻ったときに実行される queued_activate(sender, signal_absolute_index, c, argv ? argv : empty_argv); continue; } else if (c->connectionType == Qt::BlockingQueuedConnection) { // レシーバ側のキューに積まれ、イベントループに戻ったときに実行される // 実行が終わるまでブロッキング blocking_activate(sender, signal_absolute_index, c, argv ? argv : empty_argv); continue; } const int method = c->method; QObjectPrivate::Sender currentSender; const bool receiverInSameThread = currentThreadData == receiver->d_func()->threadData; QObjectPrivate::Sender *previousSender = 0; if (receiverInSameThread) { currentSender.sender = sender; currentSender.signal = signal_absolute_index; currentSender.ref = 1; previousSender = QObjectPrivate::setCurrentSender(receiver, ¤tSender); } locker.unlock(); if (qt_signal_spy_callback_set.slot_begin_callback != 0) { qt_signal_spy_callback_set.slot_begin_callback(receiver, method, argv ? argv : empty_argv); } #if defined(QT_NO_EXCEPTIONS) metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv); #else QT_TRY { metacall(receiver, QMetaObject::InvokeMetaMethod, method, argv ? argv : empty_argv); } QT_CATCH(...) { locker.relock(); if (receiverInSameThread) QObjectPrivate::resetCurrentSender(receiver, ¤tSender, previousSender); --connectionLists->inUse; Q_ASSERT(connectionLists->inUse >= 0); if (connectionLists->orphaned && !connectionLists->inUse) delete connectionLists; QT_RETHROW; } #endif if (qt_signal_spy_callback_set.slot_end_callback != 0) qt_signal_spy_callback_set.slot_end_callback(receiver, method); locker.relock(); if (receiverInSameThread) QObjectPrivate::resetCurrentSender(receiver, ¤tSender, previousSender); if (connectionLists->orphaned) break; } while (c != last && (c = c->nextConnectionList) != 0); if (connectionLists->orphaned) break; } while (signal_index >= 0 && (signal_index = -1)); //start over for -1 (all signal) --connectionLists->inUse; Q_ASSERT(connectionLists->inUse >= 0); if (connectionLists->orphaned) { if (!connectionLists->inUse) delete connectionLists; } else { sender->d_func()->cleanConnectionLists(); } locker.unlock(); if (qt_signal_spy_callback_set.signal_end_callback != 0) qt_signal_spy_callback_set.signal_end_callback(sender, signal_absolute_index); }
スロットコールがキューイングされる場合とブロッキングされる場合の処理:
static void queued_activate(QObject *sender, int signal, QObjectPrivate::Connection *c, void **argv, QSemaphore *semaphore = 0) { if (!c->argumentTypes && c->argumentTypes != &DIRECT_CONNECTION_ONLY) { QMetaMethod m = sender->metaObject()->method(signal); int *tmp = queuedConnectionTypes(m.parameterTypes()); if (!tmp) // cannot queue arguments tmp = &DIRECT_CONNECTION_ONLY; if (!c->argumentTypes.testAndSetOrdered(0, tmp)) { if (tmp != &DIRECT_CONNECTION_ONLY) delete [] tmp; } } if (c->argumentTypes == &DIRECT_CONNECTION_ONLY) // cannot activate return; int nargs = 1; // include return type while (c->argumentTypes[nargs-1]) ++nargs; int *types = (int *) qMalloc(nargs*sizeof(int)); Q_CHECK_PTR(types); void **args = (void **) qMalloc(nargs*sizeof(void *)); Q_CHECK_PTR(args); types[0] = 0; // return type args[0] = 0; // return value for (int n = 1; n < nargs; ++n) args[n] = QMetaType::construct((types[n] = c->argumentTypes[n-1]), argv[n]); QCoreApplication::postEvent(c->receiver, new QMetaCallEvent(c->method, sender, signal, nargs, types, args, semaphore)); } static void blocking_activate(QObject *sender, int signal, QObjectPrivate::Connection *c, void **argv) { if (QThread::currentThread() == c->receiver->thread()) { qWarning("Qt: Dead lock detected while activating a BlockingQueuedConnection: " "Sender is %s(%p), receiver is %s(%p)", sender->metaObject()->className(), sender, c->receiver->metaObject()->className(), c->receiver); } #ifdef QT_NO_THREAD queued_activate(sender, signal, c, argv); #else QSemaphore semaphore; queued_activate(sender, signal, c, argv, &semaphore); QMutex *mutex = signalSlotLock(sender); mutex->unlock(); semaphore.acquire(); mutex->lock(); #endif }