7

(「シグナルハンドラー」とは、POSIX シグナルのハンドラーではなく、スロットを意味します。)

QObjectの(まだ知られていない)サブクラスのインスタンスからのすべてのシグナルを別のQObjectの1つのスロットに「接続」する(おそらくQObject::connect直接使用しない)必要があります。これは、ネットワーク経由でシグナル (引数付き) を送信するために必要です (シグナルをサポートする独自の RPC システムの場合)。

(「まだ知られていない」とは、私のコードが可能な限り一般的であるべきであることを意味します。したがってconnect、RPC システムで使用している各クラスの各シグナルのステートメントを含める必要はありませんが、次のようなものを提供しますRPC::connectAllSignals(QObject*);。ランタイム中にすべてのシグナルを送信し、それらを接続します。)

私が達成したいのは、すべてのシグナルを処理し、それらをシリアル化することです(シグナル名+引数)。すでに引数をシリアル化できますが、シグナル名を取得する方法がわかりません。sender()グーグルで調べたところ、QObject インスタンスのようなものを使用することは不可能のようです。だから私はもっと複雑なことをする必要があります。

とにかく、リモート エンドのターゲット関数に引数を渡すための現在の型システムは、いくつかの型に制限されています。(これqt_metacallは、引数がvoid*背後にある「正しい型」を持つ型であることを除いて、 が必要だからです。私の RPC システムは、内部でいくつかの型のみを持つ QVariant を使用し、void*カスタム メソッドを使用してそれらを正しい型に変換します。聞いたことがあります。使用するにはQVariant::constData遅すぎるし、とにかく適合しない可能性があるため、欠点がなければ型変換に固執します。)

すべての信号がマップされるターゲット スロットは、次のようになります。

void handleSignal(QByteArray signalName, QVariantList arguments);

ソリューションが C++03 でサポートされているのがベストなので、使用しないことが大きな欠点である場合にのみ、可変個引数テンプレートを使用したいと考えています。今回はC++11でOKなので、C++11での回答も嬉しいです。


今私が考えている質問に対する私の可能な解決策:

そのオブジェクトを使用してオブジェクトのすべてのシグナルをスキャンし、シグナルに対して (またはすべての引数を渡す同様のもの) をQMetaObject作成できます。これは簡単で、この部分については助けを必要としません。前に述べたように、私はすでにいくつかの引数の型に制限されており、引数の数に制限を加えることもできます。QSignalMapper

汚いハックのように聞こえますが、次のようなカスタムのテンプレートベースのシグナルマッパーを使用できます (この例では 3 つの引数):

template<class T1, class T2, class T3>
class MySignalMapper : public QObject
{
    Q_OBJECT
public:
    void setSignalName(QByteArray signalName)
    {
        this->signalName = signalName;
    }
signals:
    void mapped(QByteArray signalName, QVariantList arguments);
public slots:
    void map(T1 arg1, T2 arg2, T3 arg3)
    {
        QVariantList args;
        // QVariant myTypeConverter<T>(T) already implemented:
        args << myTypeConverter(arg1);
        args << myTypeConverter(arg2);
        args << myTypeConverter(arg3);
        emit mapped(signalName, args);
    }
private:
    QByteArray signalName;
};

次に、このように呼び出された QObject の QMetaMethod と呼ばれるmethod(シグナルとして知られている) を接続できます (これはobj、サポートされているすべての型と引数カウントに対して何らかのスクリプトを使用して生成される可能性があります...そうです...汚れています! ):

    // ...
}
else if(type1 == "int" && type2 == "char" && type3 == "bool")
{
    MySignalMapper<int,char,bool> *sm = new MySignalMapper<int,char,bool>(this);
    QByteArray signalName = method.signature();
    signalName = signalName.left(signalName.indexOf('(')); // remove parameters
    sm->setMember(signalName);

    // prepend "2", like Qt's SIGNAL() macro does:
    QByteArray signalName = QByteArray("2") + method.signature();

    // connect the mapper:
    connect(obj, signalName.constData(),
            sm, SLOT(map(int,char,bool)));
    connect(sm, SIGNAL(mapped(int,char,bool)),
            this, SLOT(handleSignal(const char*,QVariantList)));
}
else if(type1 == ...)
{
    // ...

これは機能する可能性があるため、実際には汚い解決策です。ほとんどのN引数 (N約 3 ~ 5、まだ不明) の型のすべての組み合わせをカバーする多くのマクロ、またはすべてのケースのコードを生成する単純なスクリプトのいずれかが必要です。問題は、引数ごとに約 70 の異なる型 (10 のプリミティブ型 + ネストされたリストとそれらのすべての型の深さ 2 のマップ)をサポートしているため、これが多くのケースになることです。したがって、引数の数の制限に対して、^ 70 のケースをカバーする必要があります。NN

私が見落としている、この目的のためのまったく異なるアプローチはありますか?


アップデート:

私は自分で問題を解決しました(回答を参照)。完全なソース コードに興味がある場合は、公開したばかりの RPC システムの bitbucket にあるリポジトリを参照してください: bitbucket.org/leemes/qtsimplerpc

4

3 に答える 3

5

質問のコメントで HostileFork が提案したように、 Conanのコードを調べた後、質問の解決策を見つけました。

カスタマイズされた出力ファイルqt_static_metacallを使用して (生成されたファイルをソースに移動し、後で .pro ファイルからクラスのヘッダーを削除することにより)、ヘルパー QObject 用にカスタマイズしたものを作成しました。moc注意が必要ですが、質問で提案した解決策よりもはるかに汚れていないようです。

いくつかのスロットを持つクラス (ここでは例として 2 つのスロットexampleA(int)exampleB(bool)) の場合、次のように定義されます。

void ClassName::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        Q_ASSERT(staticMetaObject.cast(_o));
        ClassName *_t = static_cast<ClassName *>(_o);
        switch (_id) {
        case 0: _t->exampleA((*reinterpret_cast< int(*)>(_a[1]))); break;
        case 1: _t->exampleB((*reinterpret_cast< bool(*)>(_a[1]))); break;
        default: ;
        }
    }
}

ご覧のとおり、呼び出し先によって提供されたオブジェクト ポインターの「実際の」メソッドに呼び出しをリダイレクトします。

引数なしのスロットを持つクラスを作成しました。これは、検査したいシグナルのターゲットとして使用されます。

class GenericSignalMapper : public QObject
{
    Q_OBJECT
public:
    explicit GenericSignalMapper(QMetaMethod mappedMethod, QObject *parent = 0);
signals:
    void mapped(QObject *sender, QMetaMethod signal, QVariantList arguments);
public slots:
    void map();
private:
    void internalSignalHandler(void **arguments);
    QMetaMethod method;
};

スロットmap()が実際に呼び出されることはありません。これは、独自のメソッドを に配置することでこの呼び出しプロセスに踏み込むためですqt_static_metacall(ID 0 のメタ メソッドは、次のセクションで説明する別のシグナルであるため、変更されたメソッドは ですcase 1)。

void GenericSignalMapper::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        Q_ASSERT(staticMetaObject.cast(_o));
        GenericSignalMapper *_t = static_cast<GenericSignalMapper *>(_o);
        switch (_id) {
        case 0: _t->mapped((*reinterpret_cast< QObject*(*)>(_a[1])),(*reinterpret_cast< QMetaMethod(*)>(_a[2])),(*reinterpret_cast< QVariantList(*)>(_a[3]))); break;
        case 1: _t->internalSignalHandler(_a); break;
        default: ;
        }
    }
}

私たちがしていることは: 解釈されていない引数配列を独自のハンドラーに渡すだけです。これは、その型 (またはカウント) について特定できないためです。このハンドラーを次のように定義しました。

void GenericSignalMapper::internalSignalHandler(void **_a)
{
    QVariantList args;
    int i = 0;
    foreach(QByteArray typeName, method.parameterTypes())
    {
        int type = QMetaType::type(typeName.constData());

        QVariant arg(type, _a[++i]); // preincrement: start with 1
                                     // (_a[0] is return value)
        args << arg;
    }
    emit mapped(sender(), method, args);
}

最後に、他のクラスがシグナルに接続しmapped、送信者オブジェクト、QMetaMethod (名前を読み取ることができる) としてのシグナル、および QVariants としての引数を提供します。

これは完全な解決策ではありませんが、最後のステップは簡単です。検査するクラスのシグナルごとに、シグナルのメタ メソッドを提供する GenericSignalMapper を作成します。マップをオブジェクトに接続し、最終的なレシーバーにマッピングします。これにより、ソース オブジェクトによって送信されたすべての信号を処理 (および区別) できます。

void*引数を QVariants に変換する際にまだ問題があります。修理済み。_aindex には戻り値のプレースホルダーも含まれている0ため、引数は index から始まります1


例:

この例では、「最終ステップ」(各シグナルのマッパーの作成と接続) は手動で行われます。

検査するクラス:

class Test : public QObject
{
    Q_OBJECT
public:
    explicit Test(QObject *parent = 0);

    void emitTestSignal() {
        emit test(1, 'x');
    }

signals:
    void test(int, char);
};

マッパーを介してすべてのシグナルを受信する最終的なハンドラー クラス:

class CommonHandler : public QObject
{
    Q_OBJECT
public:
    explicit CommonHandler(QObject *parent = 0);

signals:

public slots:
    void handleSignal(QObject *sender, QMetaMethod signal, QVariantList arguments)
    {
        qDebug() << "Signal emitted:";
        qDebug() << "  sender:" << sender;
        qDebug() << "  signal:" << signal.signature();
        qDebug() << "  arguments:" << arguments;
    }
};

オブジェクトを作成して接続するコード:

CommonHandler handler;

// In my scenario, it is easy to get the meta objects since I loop over them.
// Here, 4 is the index of SIGNAL(test(int,char))
QMetaMethod signal = Test::staticMetaObject.method(4);

Test test1;
test1.setObjectName("test1");
Test test2;
test2.setObjectName("test2");

GenericSignalMapper mapper1(signal);
QObject::connect(&test1, SIGNAL(test(int,char)), &mapper1, SLOT(map()));
QObject::connect(&mapper1, SIGNAL(mapped(QObject*,QMetaMethod,QVariantList)), &handler, SLOT(handleSignal(QObject*,QMetaMethod,QVariantList)));

GenericSignalMapper mapper2(signal);
QObject::connect(&test2, SIGNAL(test(int,char)), &mapper2, SLOT(map()));
QObject::connect(&mapper2, SIGNAL(mapped(QObject*,QMetaMethod,QVariantList)), &handler, SLOT(handleSignal(QObject*,QMetaMethod,QVariantList)));

test1.emitTestSignal();
test2.emitTestSignal();

出力:

Signal emitted: 
  sender: Test(0xbf955d70, name = "test1") 
  signal: test(int,char) 
  arguments: (QVariant(int, 1) ,  QVariant(char, ) )  
Signal emitted: 
  sender: Test(0xbf955d68, name = "test2") 
  signal: test(int,char) 
  arguments: (QVariant(int, 1) ,  QVariant(char, ) ) 

(char引数は正しく出力されませんが、QVariant に正しく格納されます。他の型は魅力的に機能します。)

于 2012-05-29T19:54:21.480 に答える
1

引数ごとに一般的なディスパッチを作成できます。SLOT/SIGNALについては、それらは単なる文字列であるため、偽造しても問題ありません。引数ごとにディスパッチに渡し、すべての結果をマージする1つのテンプレート関数を作成することがすべてです。c ++ 11を使用する場合、これには無制限の数の引数を含めることもできます。

于 2012-05-29T19:22:12.993 に答える
1

同じ理由で、つまり RPC 経由でシグナル呼び出しを転送するという一般的なシグナル ハンドラーを探していました。QtDevDays プレゼンテーションには、QObject-QMetaObject マジックの非常に興味深い詳細な説明があります。特に、デバッグやスクリプト言語とのインターフェースのために一般的なシグナルを検査したいという願望についても説明しているので、これは完璧な読み物です。

簡単に言えば、あなたの解決策はqt_static_metacallモックコードで変更することでした。(今は Qt5 ですか?) 同じことは、QObject ベースのクラスをサブクラス化し、 をオーバーライドすることで実現できます。qt_metacall次に例を示します。

class QRpcService : public QRpcServiceBase
{
public:
    explicit QRpcService(QTcpServer* server, QObject *parent = 0);
    virtual ~QRpcService();

    virtual int qt_metacall(QMetaObject::Call, int, void**);
private:
    static int s_id_handleRegisteredObjectSignal;
};

魔法の capture-all-slot は、基本クラス (ここでvoid handleRegisteredObjectSignal()は ) で定義されたダミー メソッドであり、何も取得せず、何も実行しません。コンストラクターでメタメソッド ID を照会し、static int毎回検索しないように保存します。

このカスタム メタコール ハンドラ内で、magic-capture-all スロットへの呼び出しをインターセプトし、sender オブジェクトとシグナルを調べます。void**これにより、引数を QVariant リストに変換するために必要なすべての型情報が提供されます

int QRpcService::qt_metacall(QMetaObject::Call c, int id, void **a)
{
    // only handle calls to handleRegisteredObjectSignal
    // let parent qt_metacall do the rest
    if (id != QRpcService::s_id_handleRegisteredObjectSignal)
        return QRpcServiceBase::qt_metacall(c, id, a);

    // inspect sender and signal
    QObject* o = sender();
    QMetaMethod signal = o->metaObject()->method(senderSignalIndex());
    QString signal_name(signal.name());

    // convert signal args to QVariantList
    QVariantList args;
    for (int i = 0; i < signal.parameterCount(); ++i)
        args << QVariant(signal.parameterType(i), a[i+1]);

    // ...
    // do whatever you want with the signal name and arguments
    // (inspect, send via RPC, push to scripting environment, etc.)
    // ...

    return -1;
}

このメソッド内ですべてを処理しましたが、別のシグナルで収集されたすべての情報を再送信し、実行時にそれにアタッチすることもできます。

誰かが興味を持っている場合は、ここで私のソリューションを含むリポジトリもセットアップします。

于 2016-05-03T09:29:00.570 に答える