c – QObject通用信号处理程序

(“信号处理程序”是指插槽,而不是POSIX信号的处理程序.)

我需要将来自QObject的一个(尚未知道的)子类的实例的所有信号“连接”(可能不直接使用QObject :: connect)到另一个QObject的单个插槽.我需要这个以便通过网络发送信号(带参数)(对于支持信号的自己的RPC系统).

(“还不知道”我的意思是我的代码应该尽可能通用.所以它不能包含我在RPC系统中使用的每个类中的每个信号的连接语句,但提供类似RPC :: connectAllSignals(QObject *);然后在运行时扫描所有信号并连接它们.)

我想要实现的是:处理所有信号并将它们串行化(信号名称参数).我已经可以序列化参数,但我不知道如何获取信号名称.在谷歌搜索之后,似乎不可能使用类似于QObject实例的sender()之类的东西.所以我需要做一些更复杂的事情.

我当前用于将参数传递给远程端目标函数的类型系统无论如何都限于某些类型. (那是因为我需要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;
};

然后我可以连接一个名为obj的QMetaMethod调用方法(已知是一个信号),这个可以使用某种类型的脚本为所有支持的类型和参数计数生成…是的…它正在脏!):

// ...
}
else if(type1 == "int" && type2 == "char" && type3 == "bool")
{
    MySignalMapper<int,char,bool> *sm = new MySignalMapper<int,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,bool)));
    connect(sm,SIGNAL(mapped(int,bool)),this,SLOT(handleSignal(const char*,QVariantList)));
}
else if(type1 == ...)
{
    // ...

由于这可能有效,它确实是一个肮脏的解决方案.我需要很多宏来覆盖最多N个参数的所有类型组合(其中N大约是3到5,还不知道),或者是为所有情况生成代码的简单脚本.问题在于这将是很多情况,因为我支持每个参数大约70种不同的类型(10种基本类型嵌套列表和每种类型的深度为2的映射).因此,对于N的参数计数限制,有N ^ 70个案例要覆盖!

这个目标有一个完全不同的方法,我忽略了吗?

更新:

我自己解决了这个问题(见答案).如果您对完整源代码感兴趣,请参阅我刚刚发布的RPC系统bitbucket上的我的存储库:bitbucket.org/leemes/qtsimplerpc

解决方法

在查看问题评论中HostileFork建议的 Conan代码之后,我找到了一个问题的解决方案:

我通过使用自定义的moc输出文件(通过将生成文件移动到我的源中并随后从我的.pro文件删除类’标题)为助手QObject编写了一个自定义的qt_static_Metacall.我需要小心,但它似乎远不如我在问题中建议的解决方案那么脏.

对于具有一些插槽的类,这里例如两个插槽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的元方法是我在下一节中解释的另一个信号,因此修改后的方法是案例1):

void GenericSignalMapper::qt_static_Metacall(QObject *_o,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);
}

最后,其他一些类可以连接映射信号,它将提供发送方对象,信号为QMetaMethod(我们可以从中读取名称),参数为QVariants.

这不是一个完整的解决方案,但最后一步很简单:对于要检查的类的每个信号,我们创建一个GenericSignalMapper,提供信号的元方法.我们将map连接到对象并映射到最终接收器,然后能够处理(并区分)源对象发出的所有信号.

我仍然在将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,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*,QVariantList)));

GenericSignalMapper mapper2(signal);
QObject::connect(&test2,&mapper2,SLOT(map()));
QObject::connect(&mapper2,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参数无法正确打印,但它正确存储在QVariant中.其他类型的工作方式就像魅力.)

相关文章

本程序的编译和运行环境如下(如果有运行方面的问题欢迎在评...
水了一学期的院选修,万万没想到期末考试还有比较硬核的编程...
补充一下,先前文章末尾给出的下载链接的完整代码含有部分C&...
思路如标题所说采用模N取余法,难点是这个除法过程如何实现。...
本篇博客有更新!!!更新后效果图如下: 文章末尾的完整代码...
刚开始学习模块化程序设计时,估计大家都被形参和实参搞迷糊...