QObject通用信号处理程序 [英] QObject generic signal handler

查看:245
本文介绍了QObject通用信号处理程序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

(信号处理程序我的意思是槽,而不是POSIX信号的处理程序。)



我需要连接 >使用 QObject :: connect 直接)所有信号从QObject的一个(尚未知的)子类的实例一个单槽。我需要这个以便通过网络发送信号(带有参数)(对于支持信号的自己的RPC系统)。



(未知意味着我的代码应尽可能通用,所以它不会包含每个类中的每个信号的 connect 语句我使用我的RPC系统,但提供例如 RPC :: connectAllSignals(QObject *); ,然后在运行时扫描所有信号并连接它们。)



我想实现的是:处理所有信号并将其串行化(信号名称+参数)。我已经可以序列化参数,但我不知道如何获取信号名称。在googling之后,似乎不可能使用类似的东西,如对于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< ;类T1,类T2,类T3> 
class MySignalMapper:public QObject
{
Q_OBJECT
public:
void setSignalName(QByteArray signalName)
{
this-> signalName = signalName;
}
信号:
void mapped(QByteArray signalName,QVariantList arguments);
public slots:
void map(T1 arg1,T2 arg2,T3 arg3)
{
QVariantList args;
//已经实现的QVariant myTypeConverter< T>(T):
args< myTypeConverter(arg1);
args<< myTypeConverter(arg2);
args<< myTypeConverter(arg3);
emit mapped(signalName,args);
}
private:
QByteArray signalName;
};然后我可以连接一个名为方法的QMetaMethod

(这可能是使用某种类型的脚本为所有支持的类型和参数计数生成的)的一个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);

// prepend2 ,如Qt的SIGNAL()宏:
QByteArray signalName = QByteArray(2)+ method.signature();

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

由于此可能会工作,所以这是一个肮脏的解决方案需要许多宏来覆盖所有类型的组合,最多 N 个参数(其中 N 约为3问题是这将是一个很多情况下,因为我支持大约70种不同的类型每个参数(10)原始类型+嵌套列表和深度为2的映射,用于每个类型)。因此,对于 N 的参数计数限制,有 N ^个情况覆盖!



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






UPDATE:



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

解决方案

我找到了一个解决我的问题的方法,在查看 Conan ,由HostileFork在问题的意见中提出:



我写了一个自定义的<$ c通过使用自定义的 moc 输出文件(通过将生成的文件移动到我的源中并删除类的头部)来为帮助器QObject创建$ c> qt_static_metacall 从我的.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])));打破;
case 1:_t-> exampleB((* reinterpret_cast< bool(*)>(_a [1])));打破;
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]打破;
case 1:_t-> internalSignalHandler(_a);打破;
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]是返回值)
args< arg;
}
emit mapped(sender(),method,args);
}

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



这不是一个完整的解决方案,但最后一步很容易:对于要检查的类的每个信号,我们创建一个GenericSignalMapper,提供信号的元方法。我们将映射连接到对象并映射到最终的接收者,然后它可以处理(和区分)源对象发出的所有信号。



I仍然有问题将 void * 参数转换为QVariants。已修复。 _a 还包括索引 0 处的返回值的占位符,因此参数从索引 1






示例:



<在此示例中,最后一步(为每个信号创建和连接映射器)是手动完成的。



要检查的类:

 类测试:public QObject 
{
Q_OBJECT
public:
explicit Test = 0);

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

信号:
void test(int,char);
};

最终处理程序类通过映射器接收所有信号:

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

信号:

public slots:
void handleSignal(QObject * sender,QMetaMethod signal,QVariantList arguments)
{
qDebug << 信号发射:;
qDebug()<< sender:<发送者
qDebug()<< signal:< signal.signature();
qDebug()<< arguments:<参数;
}
};

我们创建对象并连接对象的代码:

  CommonHandler处理程序; 

//在我的场景中,很容易获取元对象,因为我循环它们。
//这里,4是SIGNAL的索引(test(int,char))
QMetaMethod signal = Test :: staticMetaObject.method(4)。

测试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)

GenerateSignalMapper 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();

输出:

 code>发出的信号:
sender:Test(0xbf955d70,name =test1)
signal:test(int,char)
arguments:(QVariant(int,1) QVariant(char,))
发出的信号:
sender:Test(0xbf955d68,name =test2)
signal:test(int,char)
arguments: int,1),QVariant(char,))

/ code>参数没有正确打印,但它正确存储在QVariant中。其他类型的工作方式类似于charm。)


(With "signal handler" I mean slots, not handlers for POSIX signals.)

I need to "connect" (probably not using QObject::connect directly) all signals from an instance of a (not yet known) subclass of QObject to one single slot of another QObject. I need this in order to send the signal (with arguments) over network (for an own RPC system with support for signals).

(With "not yet known" I mean that my code should be as generic as possible. So it souldn't contain a connect statement for each signal in each class I'm using with my RPC system, but provide something like RPC::connectAllSignals(QObject*);, which then scanns all signals during runtime and connects them.)

What I'd like to achieve is: Handle all signals and serialise them (signal name + arguments). I already can serialise the arguments, but I don't know how to get the signal name. After googling, it seems to be impossible to use something similar like there is sender() for the QObject instance. So I need to do something far more complicated.

My current type system for passing the arguments to a target function on the remote end is restricted to some types anyway. (That's because I need qt_metacall, which excepts the arguments to be of type void* with the "correct types" behind them. My RPC system uses QVariants with only a couple of types internally and I convert them to void* of the correct types using custom methods. I heard about QVariant::constData too late to use it, and it probably won't fit anyway; so I will stick to my type conversion if there is no drawback.)

The target slot, where all signals should be mapped to, should look similar to this:

void handleSignal(QByteArray signalName, QVariantList arguments);

It would be best if the solution is supported by C++03, so I only want to use variadic templates if it is a big drawback to not use them. In this case C++11 is OK, so I'm also happy about answers using C++11.


Now my possible solution to the question I'm thinking about:

I could scan all signals of the object using its QMetaObject and then creating a QSignalMapper (or something similar which passes all arguments) for each signal. This is easy, and I need no help on this part. As mentioned before, I'm already restricted to some types for arguments, and I can also live with a restriction on the argument count.

It sounds like a dirty hack, but I could use some sort of custom, template-based signal mappers like this (in this example for three arguments):

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

Then I could connect a QMetaMethod called method (which is known to be a signal) of a QObject called obj like this (which might be generated using some sort of script for all supported types and argument counts... yeah... it's getting dirty!):

    // ...
}
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 == ...)
{
    // ...

As this may work, it really is a dirty solution. I'd need either a lot of macros to cover all combinations of types for at most N arguments (where N is about 3 to 5, not yet known), or a simple script generating the code for all cases. The problem is that this will be a lot of cases, as I'm supporting about 70 different types per argument (10 primitive types + nested lists and maps with depth 2 for every type of them). So for an argument count limit of N there are N ^ 70 cases to cover!

Is there a completely different approach for this objective, which I'm overlooking?


UPDATE:

I solved the problem on my own (see answer). If you are interested in the full source code, see my repository on bitbucket of my RPC system, which I have just published: bitbucket.org/leemes/qtsimplerpc

解决方案

I found a solution for my question, after looking into the code of Conan as suggested by HostileFork in the question's comments:

I wrote a customized qt_static_metacall for a helper QObject by using a customized moc output file (by moving the generated file into my sources and removing the class' header from my .pro file afterwards). I need to be careful, but it seems to be far less dirty than my suggested solution in the question.

For a class with some slots, here for example the two slots exampleA(int) and exampleB(bool), it is defined like this:

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

As you can see, it redirects the call to the "real" method on the object pointer provided by the callee.

I made a class with some slot without any arguments, which will be used as the target of the signal we want to inspect.

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

The slot map() never gets called in real, because we step in this calling process by putting our own method in the qt_static_metacall (note that the meta method with ID 0 is another signal I explain in the next section, so the modified method is the 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: ;
        }
    }
}

What we do is: We just pass the uninterpreted argument array to our own handler, because we can't be specific about its types (or even the count). I defined this handler as follows:

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

Finally, some other class may connect to the mapped signal, which will provide the sender object, the signal as a QMetaMethod (from which we can read the name) and the arguments as QVariants.

This is not a full solution, but the final step is easy: For each signal of the class to be inspected, we create a GenericSignalMapper providing the meta method of the signal. We connect map to the object and mapped to the final receiver, which is then able to handle (and distinguish) all emitted signals by the source object.

I still have problems converting the void* arguments to QVariants. Fixed. _a also includes a placeholder for the return value at index 0, so arguments start at index 1.


Example:

In this example, the "final step" (create and connect mappers for each signal) is done manually.

The class to be inspected:

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

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

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

The final handler class receiving all signals via the mappers:

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

The code where we create the objects and connect them:

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();

Output:

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, ) ) 

(The char argument doesn't get printed correctly, but it is stored in the QVariant correctly. Other types work like a charm.)

这篇关于QObject通用信号处理程序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆