QObject的通用信号处理程序(QObject generic signal handler)

2019-07-29 15:32发布

(用“信号处理程序”我的意思是槽,用于POSIX信号不处理程序。)

我需要“连接”( 可能不使用QObject::connect直接) 所有信号自QObject的(还不知道)子类的实例到另一个QObject中的一个单一的插槽 。 我需要这一点是为了通过网络(与信号支持的RPC自己的系统)发送信号(带参数)。

(以“还不知道”我的意思是,我的代码应该尽可能通用。所以souldn't包含一个connect在我使用的是我的RPC系统中的每个类中的每个信号的声明,但提供类似RPC::connectAllSignals(QObject*); 。,运行时间期间,然后scanns所有信号,并连接它们)

我想实现的是:处理所有信号,并连载他们(信号名称+参数)。 我已经可以连载的论据,但我不知道怎么弄的信号名称。 谷歌搜索后,这似乎是不可能使用类似的东西好像有sender()为QObject的实例。 所以,我需要做的更为复杂的东西。

我的用于传递的参数的目标函数在远端当前类型系统被限制为某些类型的反正。 (这是因为我需要qt_metacall ,其中节选的参数为类型void*用“正确的类型”甩在身后。我的RPC系统采用QVariants只有几个类型的内部,我将它们转换为void*使用正确的类型。自定义的方法我听说QVariant::constData来不及使用它,它可能会不适合呢。所以我会坚持我的类型转换,如果没有缺点)

目标插槽,所有的信号应该被映射到,应该类似于此:

void handleSignal(QByteArray signalName, QVariantList arguments);

这将是最好的,如果解决方案是由C ++ 03的支持,所以我只需要使用可变参数模板,如果它是一个很大的缺点,不使用它们。 在这种情况下,C ++ 11是确定的,所以我也很高兴关于使用C ++ 11的答案。


现在我可能的解决方案我在想这样一个问题:

我可以使用其扫描对象的所有信号QMetaObject然后创建一个QSignalMapper (或类似的东西,其传递所有参数)为每个信号。 这是容易的,我需要这部分没有任何帮助。 正如前面提到的,我已经受限于某些类型的参数,并且我也可以住上参数计数的限制。

这听起来像一个肮脏的黑客,但我可以用某种类似这样的定制,基于模板的信号映射器(在本例中为三个参数):

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;
};

然后,我可以连接QMetaMethod称为method (这是众所周知的是一个信号)的一个QObject称为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为每一类型的其中)。 所以对于一个参数数量限制NN ^70箱子覆盖!

是否有这个目标,我俯瞰这完全不同的方法?


更新:

我解决我自己的问题(见接听)。 如果你有兴趣在完整的源代码,看到我的RPC系统,我刚才发布的到位桶我的仓库:bitbucket.org/leemes/qtsimplerpc

Answer 1:

我发现我的问题的解决方案,寻找到的代码后, 柯南在这个问题的意见HostileFork的建议:

我写定制qt_static_metacall通过使用定制的辅助QObject的moc输出文件(由将生成的文件移动到我的源和从我的pro文件之后去除类的报头)。 我必须要小心,但它似乎比我在这个问题的建议解决方法远不如脏。

对于具有一些槽的一类,此处例如两个时隙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。 固定。 _a还包括用于在索引的返回值的占位符0 ,所以在参数索引开始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其他类型的工作就像一个魅力。)



Answer 2:

你可以让每个参数的通用调度,以及有关SLOT / SIGNAL他们只是字符串,所以它不是问题,开拓他们。 这一切都使得一个模板函数将在每一个参数传递到调度和合并所有的结果。 这甚至可以拥有的参数数量不受限制,如果您使用C ++ 11。



Answer 3:

我一直在寻找出于同样的原因的通用信号处理程序,即通过RPC转移信号调用。 还有就是QObject的-QMetaObject魔术在一个非常有趣和详细说明QtDevDays演示 。 特别是,他们还描述了检查调试或接口的通用信号与脚本语言的愿望 - 所以这是一个完美的读取。

长话短说:您的解决方案是修改qt_static_metacall在MOC代码。 (?现在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;
};

魔术捕获所有的插槽是在基类(这里定义只是一个虚拟的方法void handleRegisteredObjectSignal()其采用什么和什么也不做。 我查询其元法-ID在构造函数中,并存储为static int避免每次寻找它。

在这个自定义metacall处理您拦截到您的魔法捕获所有插槽调用和检查发件人对象和信号。 这提供了所有的转换所需要的类型信息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;
}

我只是处理一切在此方法中,但你也可以重新发射所有被聚集在另一个信号,并重视在运行时的信息。

如果有人有兴趣,我还设置了一个资源库和我的解决方案在这里 。



文章来源: QObject generic signal handler