ネットワーク上のQtメタシステム。 パート2-信号とスロット



パート1-プロパティ

Qtメタシステムを引き続き扱います。 今回は、仮想信号とスロットの作成を検討してください。



1.シグナル



より正確には、あらゆる信号への接続。 ここでのすべてはプロパティの操作に非常に似ており、引数は0〜10のみです。目標は、任意のシグナルに接続できるDynamicQObjectクラスを取得し、アクティブになったときにシグナル名とQVariantListの形式で渡された引数でメソッドを呼び出すことです。 また、コードをコメント付きの断片に分割します。先に進みましょう。

dynamicqobject.h
class DynamicQObject : public QObject { public: DynamicQObject(QObject *mapToObject, const char *signalCatchMethod, QObject *parent = 0); bool addSlot(QObject *object, const char *signal, const QString &slotName); bool removeSlot(const QString &name); bool addSignal(const QString &name, QObject *object, const char *slot); bool removeSignal(const QString &name); bool activate(const QString &signalName, const QVariantList &args); int qt_metacall(QMetaObject::Call call, int id, void **arguments); private: // virtual slots bool containsSlot(const QString &name); QObject *m_mapTo; const char *m_catchMethod; typedef struct { bool isEmpty; // true after removeSlot() QObject *object; int signalIdx; QString name; // virtual slot name QVector<int> parameterTypes; } slot_t; QVector<slot_t> m_slotList; // virtual signals typedef struct { bool isEmpty; // // true after removeSignal() QObject *reciever; int slotIdx; QString name; QVector<int> parameterTypes; } signal_t; QVector<signal_t> m_signalList; QHash<QString, int> m_signalHash; void *m_parameters[11]; // max 10 parameters + ret value };
      
      







addSlot - slotNameという名前の新しい仮想スロットを作成し、オブジェクトobjectのシグナルsignalに接続します。 ここですべてがどのように機能するかを見てみましょう:

 bool DynamicQObject::addSlot(QObject *object, const char *signal, const QString &slotName) { if (containsSlot(slotName)) return false; if (signal[0] != '2') { qWarning() << "Use SIGNAL() macro"; return false; }
      
      





同じ名前のスロットがすでに存在するかどうか、またその名前の先頭に文字「2」が存在するかどうかを確認します。このシンボルはSIGNAL()マクロを追加します。

  QByteArray theSignal = QMetaObject::normalizedSignature(&signal[1]); int signalId = object->metaObject()->indexOfSignal(theSignal); if (signalId < 0) { qWarning() << "signal" << signal << "doesn't exist"; return false; } QVector<int> parameterTypes; QMetaMethod signalMethod = object->metaObject()->method(signalId); for (int i = 0; i < signalMethod.parameterCount(); ++i) parameterTypes.push_back(signalMethod.parameterType(i));
      
      





前回と同様に、シグナルインデックスを取得し、このインデックスでQMetaMethodを取得します。 信号引数を一度に1つずつ取得し、それらからベクトルを作成します。 さらに、取得した引数を正確にこれらの型に減らします。

  int slotIdx = -1; for (int i = 0; i < m_slotList.count(); ++i) { if (m_slotList[i].isEmpty == true) { slotIdx = i; break; } } bool addEntry = false; if (slotIdx == -1) { addEntry = true; slotIdx = m_slotList.count(); }
      
      





ここではすべてが非常に簡単です。m_slotListで空のレコードを検索(または新しいレコードを作成)し、作成されたスロットに関する情報を保存します。 スロットを削除すると空のレコードが形成されます(removeSlot()) インデックスはスロットに関連付けられているため、それらのシフトは無効なスロットの呼び出しにつながります。 ここでQHashまたはQMapを使用することも可能ですが、作成するよりも頻繁にスロットを削除するのではなく、非常に頻繁に呼び出すので、ベクトルは明らかに効率的です。 そのインデックスアクセスは、O(1)に対するものであり、最悪の場合、QMapおよびQHashに対するものは、それぞれO(logn)およびO(n)に対するものです。

実際には、信号に接続するだけです。

  if (!QMetaObject::connect(object, signalId, this, slotIdx + metaObject()->methodCount())) { qWarning() << "can't connect" << signal << "signal to virtual slot"; return false; } if (addEntry) { m_slotList.push_back({false, object, signalId, slotName, parameterTypes}); } else { slot_t &slot = m_slotList[slotIdx]; slot.isEmpty = false; slot.object = object; slot.signalIdx = signalId; slot.name = slotName; slot.parameterTypes = parameterTypes; } return true; }
      
      





そして、彼に関するすべての必要な情報を保存します。



スロットコール


ここにあるものはすべて、プロパティを備えたフォーカスのように見えます。qt_metacallの独自の実装を作成します。

 int DynamicQObject::qt_metacall(QMetaObject::Call call, int id, void **arguments) { id = QObject::qt_metacall(call, id, arguments); if (id < 0 || call != QMetaObject::InvokeMetaMethod) return id; Q_ASSERT(id < m_slotList.size());
      
      





スロット(メタメソッド)が実際に呼び出されること、およびインデックスが正しいことも確認します。

  const slot_t &slotInfo = m_slotList[id]; QVariantList parameters; for (int i = 0; i < slotInfo.parameterTypes.count(); ++i) { void *parameter = arguments[i + 1]; parameters.append(QVariant(slotInfo.parameterTypes[i], parameter)); }
      
      





以前に保存されたスロットに関する情報を取得します。 そして、受け取った引数と保存された型からQVariantsのリストを作成します。

  QMetaObject::invokeMethod(m_mapTo, m_catchMethod, Q_ARG(QString, slotInfo.name), Q_ARG(QVariantList, parameters)); return -1; }
      
      





スロットの名前と引数を使用して、コンストラクターで指定されたメソッドを呼び出すだけです。



removeSlot()には興味深いものは何もないので、すぐに使用例を見てみましょう。

 Reciever reciever; DynamicQObject dynamic(&reciever, "signalCatched"); Tester tester; dynamic.addSlot(&tester, SIGNAL(signal1(int,int,QString)), "myslot1"); dynamic.addSlot(&tester, SIGNAL(signal2(QPoint)), "myslot2"); tester.emitSignal1(); tester.emitSingal2();
      
      





DynamicQObjectコンストラクターでは、QObjectの子孫へのポインターと、信号を受信したときに呼び出されるメソッド名(Q_INVOKABLE)を渡します。

Recieverクラスには次のメソッドがあります: Q_INVOKABLE void signalCatched(const QString&signalName、const QVariantList&args) 、単にコンソールに引数を表示します。

そして、テスターに​​は、指定された引数を持つ2つの信号と、それらを生成する2つの関数があり、すべてが明確になっているはずです。 以下を開始します。

「Myslot1」(QVariant(int、123)、QVariant(int、456)、QVariant(QString、「str」))

Myslot2(QVariant(QPoint、QPoint(3,4)))


すべてが機能していることがわかります:) Qtメタシステムに既知の任意の型を絶対に使用できます。



2.スロット



つまり 仮想信号を作成して通常のスロットに接続すると、1つを他のスロットと混同しやすくなります。これには、addSignal()、removeSignal()、activate()の3つの関数が関与します。

最も興味深いものを検討してください。 信号作成:

 bool DynamicQObject::addSignal(const QString &name, QObject *object, const char *slot) { if (slot[0] != '1') { qWarning() << "Use SLOT() macro"; return false; } int slotIdx = object->metaObject()-> indexOfSlot(&slot[1]); // without 1 added by SLOT() macro if (slotIdx < 0) { qWarning() << slot << "slot didn't exist"; return false; }
      
      





いつものように、すべてが正常に行われていることを確認します。

  QVector<int> parameterTypes; QMetaMethod slotMethod = object->metaObject()->method(slotIdx); for (int i = 0; i < slotMethod.parameterCount(); ++i) parameterTypes.push_back(slotMethod.parameterType(i)); int signalIdx = -1; for (int i = 0; i < m_slotList.count(); ++i) { if (m_slotList[i].isEmpty == true) { signalIdx = i; break; } } bool addEntry = false; if (signalIdx == -1) { addEntry = true; signalIdx = m_signalList.count(); }
      
      





同様に、引数型のベクトルを作成します。

  if (!QMetaObject::connect(this, signalIdx + metaObject()->methodCount(), object, slotIdx)) { qWarning() << "can't connect virtual signal" << name << "to slot" << slot; return false; } if (addEntry) { m_signalList.append({false, object, slotIdx, name, parameterTypes}); } else { signal_t &signal = m_signalList[signalIdx]; signal.isEmpty = false; signal.reciever = object; signal.slotIdx = slotIdx; signal.name = name; signal.parameterTypes = parameterTypes; } m_signalHash.insert(name, signalIdx); return true; }
      
      





繰り返しますが、すべてがほぼ同じです-仮想信号をスロットに接続し、m_signalHashに名前のペアを保存します-信号インデックス 。 したがって、シグナルがアクティブになると、名前からインデックスを取得します。

削除、または多くのコードさえ考慮しません...



信号活性化


すでに新しいポイント、つまり型キャストがあります。

 bool DynamicQObject::activate(const QString &signalName, const QVariantList &args) { int signalIdx = m_signalHash.value(signalName, -1); if (signalIdx == -1) { qWarning() << "signal" << signalName << "doesn't exist"; return false; } signal_t &signal = m_signalList[signalIdx];
      
      





信号が存在することを確認し、以前に保存された情報を取得します。

  if (args.count() < signal.parameterTypes.count()) { qWarning() << "parameters count mismatch:" << signalName << "provided:" << args.count() << "need >=:" << signal.parameterTypes.count(); return false; }
      
      





十分な引数があることを確認し、余分なものを削除します...

  QVariantList argsCopy = args; for (int i = 0; i < signal.parameterTypes.count(); ++i) { if (!argsCopy[i].convert(signal.parameterTypes[i])) { qWarning() << "can't cast parameter" << i << signalName; return false; } m_parameters[i + 1] = argsCopy[i].data(); }
      
      





引数リストのコピーを次のように作成します それは一定ですが、型をキャストする必要があります。 それを変更します。 そして、各引数を目的の型にキャストしようとします。 したがって、たとえば、int型の引数でスロットを呼び出し、QString型のactivate引数を渡すことができます。主なことは、文字列に数字が含まれていることです。

例:

  DynamicQObject dynamic(&reciever, "signalCatched"); Reciever reciever; dynamic.addSignal("virtual_signal", &reciever, SLOT(slot1(int,QString))); dynamic.activate("virtual_signal", QVariantList() << "123" << QString("qwerty") << 2 << 3);
      
      





したがって、Recieverには通常のスロットが含まれています。 仮想信号を作成し、引数のリストで呼び出します。

結果は次のとおりです。

slot1_call 123 "qwerty"


2つの余分な引数が破棄され、最初に型変換が実行されたため、これが非常に必要なものであるかどうかはわかりませんが、ここではそれ自体が判明しますが、この動作を禁止することができます...



今のところすべてです。 次に、メソッドを把握し、簡単なネットワークインターフェイスを固定します...



PSこのクラスを書く際に、記事: Dynamic Signals and Slotsから多くの興味深いことを学ぶことができ、本当に助けになりました。



All Articles