QT 的信号和槽机制能十分方便的用来传输数据,但是如果数据种类比较多,分类比较多的时候,就需要更好地更高效的来传递数据的方法。以结构体作为参数是个很不错的选择。这几天写的程序正好需要以结构体来作为参数,但是网上搜的资料很少,讲的也不详细,我解决了问题后整理了一下,希望给有同样需求的同学一点帮助

Q_DECLARE_METATYPE与qRegisterMetaType


qRegisterMetaType:注册元类型主要是在定义信号槽的时候,传递的参数类型不一定是QT所识别的,QT不识别的就要先注册以下,让QT能够认识,就是用qRegisterMetaType注册

基本理解

  • Q_DECLARE_METATYPE
  • 如果要使自定义类型或其他非QMetaType内置类型在QVaiant中使用,必须使用该宏。
  • 该类型必须有公有的 构造、析构、复制构造 函数
  • qRegisterMetaType 必须使用该函数的两种情况
  • 如果非QMetaType内置类型要在 Qt 的属性系统中使用
  • 如果非QMetaType内置类型要在 queued 信号与槽 中使用

二者关系

二者的代码:

  • Q_DECLARE_METATYPE 展开后是一个特化后的类 QMetaTypeId<TYPE>
  • qRegisterMetaType 将某类型注册中 MetaType 系统中

二者的联系:

  • QMetaTypeId<TYPE>的类中的成员包含对qRegisterMetaType的调用
  • 我们知道类中的成员函数并不一定会被调用(即,该宏并不确保类型被注册到MetaType)。
  • 通过qRegisterMetaType可以确保类型被注册

两个qRegisterMetaType 的联系

  • 无参的qRegisterMetaType函数会通过该成员调用带参数的qRegisterMetaType()

这两个东西真难理清,不妨看看源码吧。

Q_DECLARE_METATYPE

代码来源:src/corelib/kernel/qmetatype.h



#define Q_DECLARE_METATYPE(TYPE)                                        \
    QT_BEGIN_NAMESPACE                                                  \
    template <>                                                         \
    struct QMetaTypeId< TYPE >                                          \
    {                                                                   \
        enum { Defined = 1 };                                           \
        static int qt_metatype_id()                                     \
            {                                                           \
                static QBasicAtomicInt metatype_id = Q_BASIC_ATOMIC_INITIALIZER(0); \
                if (!metatype_id)                                       \
                    metatype_id = qRegisterMetaType< TYPE >(#TYPE);     \
                return metatype_id;                                     \
            }                                                           \
    };                                                                  \
    QT_END_NAMESPACE





  • 宏展开是一个在Qt的命名空间中的一个类模板的特化 QMetaTypeId<TYPE>
  • 该类含一个enum和一个返回!QMetaType的id的成员函数

qRegisterMetaType(const char *typeName)

代码来源:src/corelib/kernel/qmetatype.h



template <typename T>
int qRegisterMetaType(const char *typeName)
{
    typedef void*(*ConstructPtr)(const T*);
    ConstructPtr cptr = qMetaTypeConstructHelper<T>;
    typedef void(*DeletePtr)(T*);
    DeletePtr dptr = qMetaTypeDeleteHelper<T>;

    return QMetaType::registerType(typeName, reinterpret_cast<QMetaType::Destructor>(dptr),
                                   reinterpret_cast<QMetaType::Constructor>(cptr));
}



  • 该函数的核心就是调用了registerType 函数
  • 两个Helper模板函数分别对构造和析构函数进行封装

registerType

代码来源:src/corelib/kernel/qmetatype.cpp



int QMetaType::registerType(const char *typeName, Destructor destructor, Constructor constructor)



函数功能:

  • 根据类型名查找其MetaType类型,如果已存在,则直接返回;否则创建后返回。
  • 创建一个 !QCustomTypeInfo 对象
  • 该对象包含要类型的构造、析构信息,已经规范化后的类型名
  • 该对象存入一个全局的!QVector中

qRegisterMetaType()

看manual,可以知道,qRegisterMetaType 还有一个无参的重载函数。



template <typename T>
inline int qRegisterMetaType()
{
    return qMetaTypeId(static_cast<T *>(0));
}



  • 函数看起来和带参数的那个似乎区别很大(难道不是么?)。
  • 手册中告诉我们,执行这个的时候,模板参数T必须用 Q_DECLARE_METATYPE() 声明过
  • 能猜到原因吗?注意看前面 Q_DECLARE_METATYPE() 代码,
  • 对了。类中的成员函数qt_metatype_id中包含对qRegisterMetaType(typeName)的调用
  • 这儿就是辗转调用了这个带参数的qRegisterMetaType函数 


unregisterType(const char *typeName)

函数的作用是取消自己先前注册的某个metatype类型。

 QVector<QCustomTypeInfo>中,当取消注册的时候是怎么样的呢?直接删除Vector中相应的项么?源码告诉我们,不是的。

实际是查找到相应的项,清空该项的内容。

for (int v = 0; v < ct->count(); ++v)
 {
 if (ct->at(v).typeName == typeName)
 {
 QCustomTypeInfo &inf = (*ct)[v];
 inf.typeName.clear();
 inf.constr = 0;
 inf.destr = 0;
 inf.alias = -1;
 }
}




1.首先是结构体的使用,需要使用Q_DECLARE_METATYPE宏
如:


1. struct DataStruct  
2. {  
3.     QByteArray DstAddr;  
4.     QByteArray ClusterId;  
5.     int DstEndpoint;  
6.     int DeviceEndpoint;  
7.     int CommandID;  
8.     QByteArray AttributeID;  
9.     int DataType;  
10. };  
11. Q_DECLARE_METATYPE(DataStruct)  //这个宏具体的用法参考帮助文档



2.然后是把该结构体封装如一个QVariant



1. DataStruct askData;  
2. QVariant DataVar;  
3. DataVar.setValue(askData);

   
3.然后是对QVariant进行注册,因为信号和槽的参数类型并不认识QVariant

   



1. qRegisterMetaType<QVariant>("QVariant"); //写在构造函数里

4.然后这个类中的信号就可以将QVariant作为参数了



1. signals:  
2.     void send_askData(QVariant dataVar);


5.接收类中,由于包含了发射类的头文件,所以不必再对结构体进行定义


1. connect(readThread,SIGNAL(send_askData(QVariant)),this,SLOT(AF_DATA_REQUEST(QVariant)));  
2. 在槽函数中  
3. DataStruct askData;  
4. askData = dataVar.value<DataStruct>();


    这样就可以提取出容器内的结构体数据,并进行操作了,这对需要传输比较复杂的数据时效果比较好

ERROR:

error: ‘qt_metatype_id’ is not a member of ‘QMetaTypeId<QVariant>’

解决方案:

     多做修改,将结构体搬出类,而不是写在类public里面,可以写在同一个头文件中。


#include <QtGui/QApplication> 
  
 
  
 
   #include <QVariant> 
  
 
  
 
     
  
 
  

    //*.h 
  
 
  
 
   struct 
   struct1 
  
 
  
 
   { 
  
 
  
 
    
   int 
   a; 
  
 
  
 
    
   double 
   b; 
  
 
  
 
   }; 
  
 
  
 
     
  
 
  
 
   struct 
   struct2 
  
 
  
 
   { 
  
 
  
 
    
   struct1 s; 
  
 
  
 
    
   int 
   c; 
  
 
  
 
   }; 
  
 
  
 
     
  
 
  
 
   Q_DECLARE_METATYPE(struct1)  
   //struct1与struct2 谁先谁后,没有影响 
  
 
  
 
   Q_DECLARE_METATYPE(struct2) 
  
 
  
 
     
  
 
  
 
   class Data() 
  
 
  
 
   { 
  
 
  
 
      public: 
  
 
  
 
           int x,y; 
  
 
  
 
   } 
  
 
  
 
     
  
 
  
 
   // *.cpp  
  
 
  
 
   int  
   main( 
   int  
   argc, 
   char  
   *argv[]) 
  
 
  
 
   { 
  
 
  
 
    
   QApplication a(argc, argv); 
  
 
  
 
    
   struct1 v1 = {1, 2.0}; 
  
 
  
 
    
   QVariant var1; 
  
 
  
 
    
   var1.setValue(v1); 
  
 
  
 
     
  
 
  
 
    
   if 
   (var1.canConvert<struct1>())    
   //判断能否转化为相应类型 
  
 
  
 
    
   { 
  
 
  
 
    
   struct1 v11 = var1.value<struct1>(); 
  
 
  
 
    
   } 
  
 
  
 
     
  
 
  
 
    
   struct2 v2 = {{2, 3.0}, 5}; 
  
 
  
 
    
   QVariant var2; 
  
 
  
 
     
  
 
  
 
    
   if 
   (var2.canConvert<struct2>()) 
  
 
  
 
    
   { 
  
 
  
 
    
   var2.setValue(v2); 
  
 
  
 
    
   struct2 v22 = var2.value<struct2>(); 
  
 
  
 
    
   } 
  
 
  
 
    
   return 
   a.exec(); 
  
 
  
 
   }

结束语:



    将结构体搬出类,而不是写在类public里面,可以写在同一个头文件中。这个注册函数暂时还没用到,看看宏定义,Q_DECLARE_METATYPE(),里面好像已近带了这个注册功能。

qRegisterMetaType<QVariant>("QVariant"); //写在构造函数里

QT 的信号和槽机制能十分方便的用来传输数据,但是如果数据种类比较多,分类比较多的时候,就需要更好地更高效的来传递数据的方法。以结构体作为参数是个很不错的选择。这几天写的程序正好需要以结构体来作为参数,但是网上搜的资料很少,讲的也不详细,我解决了问题后整理了一下,希望给有同样需求的同学一点帮助

Q_DECLARE_METATYPE与qRegisterMetaType


qRegisterMetaType:注册元类型主要是在定义信号槽的时候,传递的参数类型不一定是QT所识别的,QT不识别的就要先注册以下,让QT能够认识,就是用qRegisterMetaType注册

基本理解

  • Q_DECLARE_METATYPE
  • 如果要使自定义类型或其他非QMetaType内置类型在QVaiant中使用,必须使用该宏。
  • 该类型必须有公有的 构造、析构、复制构造 函数
  • qRegisterMetaType 必须使用该函数的两种情况
  • 如果非QMetaType内置类型要在 Qt 的属性系统中使用
  • 如果非QMetaType内置类型要在 queued 信号与槽 中使用

二者关系

二者的代码:

  • Q_DECLARE_METATYPE 展开后是一个特化后的类 QMetaTypeId<TYPE>
  • qRegisterMetaType 将某类型注册中 MetaType 系统中

二者的联系:

  • QMetaTypeId<TYPE>的类中的成员包含对qRegisterMetaType的调用
  • 我们知道类中的成员函数并不一定会被调用(即,该宏并不确保类型被注册到MetaType)。
  • 通过qRegisterMetaType可以确保类型被注册

两个qRegisterMetaType 的联系

  • 无参的qRegisterMetaType函数会通过该成员调用带参数的qRegisterMetaType()

这两个东西真难理清,不妨看看源码吧。

Q_DECLARE_METATYPE

代码来源:src/corelib/kernel/qmetatype.h



#define Q_DECLARE_METATYPE(TYPE)                                        \
    QT_BEGIN_NAMESPACE                                                  \
    template <>                                                         \
    struct QMetaTypeId< TYPE >                                          \
    {                                                                   \
        enum { Defined = 1 };                                           \
        static int qt_metatype_id()                                     \
            {                                                           \
                static QBasicAtomicInt metatype_id = Q_BASIC_ATOMIC_INITIALIZER(0); \
                if (!metatype_id)                                       \
                    metatype_id = qRegisterMetaType< TYPE >(#TYPE);     \
                return metatype_id;                                     \
            }                                                           \
    };                                                                  \
    QT_END_NAMESPACE





  • 宏展开是一个在Qt的命名空间中的一个类模板的特化 QMetaTypeId<TYPE>
  • 该类含一个enum和一个返回!QMetaType的id的成员函数

qRegisterMetaType(const char *typeName)

代码来源:src/corelib/kernel/qmetatype.h



template <typename T>
int qRegisterMetaType(const char *typeName)
{
    typedef void*(*ConstructPtr)(const T*);
    ConstructPtr cptr = qMetaTypeConstructHelper<T>;
    typedef void(*DeletePtr)(T*);
    DeletePtr dptr = qMetaTypeDeleteHelper<T>;

    return QMetaType::registerType(typeName, reinterpret_cast<QMetaType::Destructor>(dptr),
                                   reinterpret_cast<QMetaType::Constructor>(cptr));
}



  • 该函数的核心就是调用了registerType 函数
  • 两个Helper模板函数分别对构造和析构函数进行封装

registerType

代码来源:src/corelib/kernel/qmetatype.cpp



int QMetaType::registerType(const char *typeName, Destructor destructor, Constructor constructor)



函数功能:

  • 根据类型名查找其MetaType类型,如果已存在,则直接返回;否则创建后返回。
  • 创建一个 !QCustomTypeInfo 对象
  • 该对象包含要类型的构造、析构信息,已经规范化后的类型名
  • 该对象存入一个全局的!QVector中

qRegisterMetaType()

看manual,可以知道,qRegisterMetaType 还有一个无参的重载函数。



template <typename T>
inline int qRegisterMetaType()
{
    return qMetaTypeId(static_cast<T *>(0));
}



  • 函数看起来和带参数的那个似乎区别很大(难道不是么?)。
  • 手册中告诉我们,执行这个的时候,模板参数T必须用 Q_DECLARE_METATYPE() 声明过
  • 能猜到原因吗?注意看前面 Q_DECLARE_METATYPE() 代码,
  • 对了。类中的成员函数qt_metatype_id中包含对qRegisterMetaType(typeName)的调用
  • 这儿就是辗转调用了这个带参数的qRegisterMetaType函数 


unregisterType(const char *typeName)

函数的作用是取消自己先前注册的某个metatype类型。

 QVector<QCustomTypeInfo>中,当取消注册的时候是怎么样的呢?直接删除Vector中相应的项么?源码告诉我们,不是的。

实际是查找到相应的项,清空该项的内容。

for (int v = 0; v < ct->count(); ++v)
 {
 if (ct->at(v).typeName == typeName)
 {
 QCustomTypeInfo &inf = (*ct)[v];
 inf.typeName.clear();
 inf.constr = 0;
 inf.destr = 0;
 inf.alias = -1;
 }
}




1.首先是结构体的使用,需要使用Q_DECLARE_METATYPE宏
如:



1. struct DataStruct  
2. {  
3.     QByteArray DstAddr;  
4.     QByteArray ClusterId;  
5.     int DstEndpoint;  
6.     int DeviceEndpoint;  
7.     int CommandID;  
8.     QByteArray AttributeID;  
9.     int DataType;  
10. };  
11. Q_DECLARE_METATYPE(DataStruct)  //这个宏具体的用法参考帮助文档



2.然后是把该结构体封装如一个QVariant



1. DataStruct askData;  
2. QVariant DataVar;  
3. DataVar.setValue(askData);



   
3.然后是对QVariant进行注册,因为信号和槽的参数类型并不认识QVariant

   




1. qRegisterMetaType<QVariant>("QVariant"); //写在构造函数里



4.然后这个类中的信号就可以将QVariant作为参数了


1. signals:  
2.     void send_askData(QVariant dataVar);


5.接收类中,由于包含了发射类的头文件,所以不必再对结构体进行定义


1. connect(readThread,SIGNAL(send_askData(QVariant)),this,SLOT(AF_DATA_REQUEST(QVariant)));  
2. 在槽函数中  
3. DataStruct askData;  
4. askData = dataVar.value<DataStruct>();



    这样就可以提取出容器内的结构体数据,并进行操作了,这对需要传输比较复杂的数据时效果比较好

ERROR:

error: ‘qt_metatype_id’ is not a member of ‘QMetaTypeId<QVariant>’

解决方案:

     多做修改,将结构体搬出类,而不是写在类public里面,可以写在同一个头文件中。