Implementation of C++ class object reflection mechanism (dynamically creating class objects and assigning values to class fields)

1. Dynamic creation of classes
The class to be created must inherit the same parent class before using the parent class pointer to create the corresponding subclass.

class QObject
{<!-- -->
public:
QObject(){<!-- -->}
~QObject(){<!-- -->}
}

1.2. Register the class name, class character name, and class field data type into the corresponding linked list.
Find the corresponding class information from the linked list later

1.2.1. Add the declaration macro to create the object in the subclass to be created

#define REGISTER_CLASS_NAME()//Registration class information
public:
static QObject* CreateQObject();

Implemented outside class

//Register class field name and field data type
#define BEGIN_REGISTER_FILEDS_NAME(class_name)\
Qobject* class_name::CreateQObject(){<!-- -->return new class_name;}\
//A new class object comes out here

1.2.2 Registration field information

The first line must contain a structure that holds field information.

struct FieldNode
{<!-- -->
TCHAR name[20];//Field name
TCHAR TypeName[20];//Field data type name
size_t TypeHashCode;//The hash value of the data type, used to compare whether the data type is the same type
size_t filedOffs; //Offset of field name
}

As long as we get the structure of the class, we can assign values to the fields and obtain the values of the fields.
Therefore, you need to add a function to obtain the field information structure when registering in the class.

#define REGISTER_CLASS_NAME()//Registration class information
public:
static QObject* CreateQObject();
static const FieldNode* GetFieldArray(); //Get the address of the class field information structure array

The corresponding implementation outside the class is added

#define BEGIN_REGISTER_FILEDS_NAME(class_name)\
Qobject* class_name::CreateQObject(){<!-- -->return new class_name;}\
const FieldNode* class_name::GetFieldArray(){<!-- -->\
static const FieldNode class_name##FieldNode[]={<!-- -->\

//Add a static field information structure array class_name##FieldNode in the implementation function
//Finally add the field name and field type of the class to the array and it will be ok
//It is necessary to add macros of messages and message response functions to MFC.

//end macro
#define END_REGISTER_FILEDS_NAME()\
{<!-- -->0,0,0,0}}; return & amp;class_name##FieldNode[0];}
//At the end, add a field structure information with all 0s to determine whether the end of the array is reached.
//And return the first address of the array
//Because the class name in this macro cannot be recognized, an alias needs to be redefined in front.
typedef class_name thisClass;
//Add to the front of the class structure array
//The previous macro is modified as follows:

#define BEGIN_REGISTER_FILEDS_NAME(class_name)\
Qobject* class_name::CreateQObject(){<!-- -->return new class_name;}\
const FieldNode* class_name::GetFieldArray(){<!-- -->\
typedef class_name thisClass;\
static const FieldNode thisClass##FieldNode[]={<!-- -->\

//end macro
#define END_REGISTER_FILEDS_NAME()\
{<!-- -->0,0,0,0}}; return & amp;thisClass##FieldNode[0];}\


1.2.3 How to register class information
Of course, structures are also used to store class information.
I also used structures at the beginning, but in the end I still used classes to save them. There is not much difference between the two.
The main reason is that classes are better in structure and field privacy, because these data cannot be modified.

typedef QObject* (*CreateObjecFunc)(); //Define the function pointer to get the created object of the class

class RuntimeClass
{<!-- -->
public:
Runtimeclass(){<!-- -->}
~Runtimeclass(){<!-- -->}
private:
TCHAR Name[20]; //Class name
size_t Size; //The size of the class
size_t hashCode;//The hash value of the class, used to compare whether they are of the same type
CreateObjecFunc pCreatFunc; //Create function pointer of class object
const FieldNode* pFieldArray; //Field array address of the class
const RuntimeClass* pNext; //Save the address of the next class information

public:
static const RuntimeClass* g_pRuntimeClassHead;//Save the head node of the class information list

template<typename T>
static const RuntimeClass* GetRuntimeClass()//Get the head node of the linked list
{<!-- -->
const RuntimeClass* pRunClass=g_pRuntimeClassHead;
while(pRunClass)//Loop to find information about the corresponding class
{<!-- -->
if(pRunClass->hashCode==typeid(T).hash_code())//Compare the hash value of the class
return pRunClass; //Find the runtime information of the returned class
pRunClass=pRunClass->pNext;
{<!-- -->
\t
return nullptr; //Not found return nullptr
}
}
//Assign the static variable to nullptr outside the class
const RuntimeClass* RuntimeClass::g_pRuntimeClassHead=nullptr;

The runtime information of the class is also available, but how to add corresponding data to this class when the program is running?
There is definitely no data in it when running now.
Finally, we need to create a global object like the MFC application theApp.
That is to say, a class creates a global object and constructs it in the object
Here is the constructor of the class:

Runtimeclass(LPCTSTR clsName,size_t clSize,size_t clsHashCode,
CreateObjecFunc pCreateFunc,const FieldNode* pFieldArray){<!-- -->
lstrcpy(this->Name,clsName);
this->Size=clSize;
this->hashCode=clsHashCode;
this->pCreatFunc=pCreateFunc;
this->pFieldArray=pFieldArray;
this->pNext=nullptr;
//Assign value to the head of the linked list
//If there is other class information in the linked list, point the next node pointer of the current class to the original head node
//Finally, use the current class as the new head node, which is the head insertion method of the linked list
\t
if(g_pRuntimeClassHead!=nullptr)
this->pNext=g_pRuntimeClassHead;

g_pRuntimeClassHead=this;
}

This is to add class information data to the linked list
There is still no data added to the field information of the class. This requires creating a runtime class object in the previous declaration macro to add it.

The declaration macro and the closing macro remain unchanged,

Add a runtime class object in BEGIN_REGISTER_FILEDS_NAME
It cannot be added to the end because it is an incomplete function at the end and needs to be combined with the end macro to form a complete function.
If it is added to the end and is added to the function body, it will not be a global object.
So we need to add it to the front or between the two functions.

This is the construction of the global class runtime information object
RuntimeClass class_name##RuntimeClass(TEXT(#class_name),
sizeof(class_name),typeid(class_name).hash_code(),
class_name::CreateQObject,class_name::GetFieldArray());\

Parameter Description:
TEXT(#class_name) represents the class name
sizeof(class_name) class size
typeid(class_name).hash_code() gets the hash value of the class
class_name::CreateQObject The create object function pointer to create the class
class_name::GetFieldArray() obtains the field information structure array address. You must add parentheses to obtain the field data.
Without parentheses, it is a function pointer, but it is not called. With parentheses, the function is called to obtain the field data.

#define BEGIN_REGISTER_FILEDS_NAME(class_name)\
Qobject* class_name::CreateQObject(){<!-- -->return new class_name;}\
RuntimeClass class_name##RuntimeClass(TEXT(#class_name),\
sizeof(class_name),typeid(class_name).hash_code(),\
class_name::CreateQObject,class_name::GetFieldArray());\
const FieldNode* class_name::GetFieldArray(){<!-- -->\
typedef class_name thisClass;\
static const FieldNode thisClass##FieldNode[]={<!-- -->\

1.2.4. Add field information macro

#define ADD_FIELD_NAME(fName,fType)\
{<!-- -->TEXT(#fName),TEXT(#fType),typeid(fType).hash_code(),(size_t) & amp;((thisClass*)0)->fName},\

//TEXT(#fName) field name (name of variable, name, sex)
//TEXT(#fType) data type name (int, bool)
//(size_t) & amp;((thisClass*)0)->offset of fName field
//typeid(fType).hash_code() obtains the hash value of the data type and uses it to compare whether the data types are the same

1.2.5 Examples after the code of macros and classes is completed

class Student :public QObject
{<!-- -->
REGISTER_CLASS_NAME()
public:
int ID;
QString mName;
QString mSex;
UINT mAge;
double mSecoe;


Student() {<!-- --> memset(this, 0, sizeof(Student)); }
Student(int id, LPCTSTR name, LPCTSTR sex, UINT age, double secoe)
{<!-- -->
this->ID = id;
this->mName = name;
this->mSex = sex;
this->mAge = age;
this->mSecoe = secoe;
}

~Student() {<!-- -->}
};


BEGIN_REGISTER_FILEDS_NAME(Student)
ADD_FIELD_NAME(ID, int)
ADD_FIELD_NAME(mName, QString)
ADD_FIELD_NAME(mSex, QString)
ADD_FIELD_NAME(mAge, UINT)
ADD_FIELD_NAME(mSecoe, double)
END_REGISTER_FILEDS_NAME()

Example application:

//Get the runtime information of the class first
const RuntimeClass* pRunClass=RuntimeClass::GetRuntimeClass<Student>();
assert(pRunclass);

//Get the first address of the field information structure array
const FieldNode* pField=pRunClass->GetFieldArray();
assert(pField);

//Create object
Student* pStu=(Student*)pRunClass->CreateObjec();//Object created by calling the creation function pointer of the class
//Student stu(1,TEXT("Zhang San"),TEXT("Male"),33,23.6);//Test object

//Traverse the field information array to obtain data or set field values
while(pField->TypeHashCode!=0)
{<!-- -->
//Get the value of the field
//First get the type of field and compare it with hash value
if(pField->TypeHashCode==typeid(int).hash_code())
{<!-- -->
//If it is a value of type int
int id= *((int*)((unsigned char*) & amp;stu + pField->filedOffs));
}
//Other types are the same, only the conversion type changes

if(pField->TypeHashCode==typeid(UINT).hash_code())
{<!-- -->
//If it is a value of type UINT
UINT age= *((UINT*)((unsigned char*) & amp;stu + pField->filedOffs));
}
\t
if(pField->TypeHashCode==typeid(QString).hash_code())
{<!-- -->
//If it is a value of type QString
Qstring name= *((QString*)((unsigned char*) & amp;stu + pField->filedOffs));
}
pField + + ;
}


//Just set the field value to the opposite of the above
*((UINT*)((unsigned char*) & amp;stu + pField->filedOffs))=23;

So finally add in the field structure
Get_Field_Values(); Get field data
Set_Field_Values(); Set field data

template<typename T>
void Set_Field_Values(QObject* pObj,T value)
{<!-- -->
if(pField->TypeHashCode==typeid(T).hash_code()) //The values must be assigned if the types are the same
*((T*)((unsigned char*) & amp;stu + pField->filedOffs))=value;
}

template<typename T>
void Get_Field_Values(QObject* pObj,T & outValue)
{<!-- -->
if(pField->TypeHashCode==typeid(T).hash_code()) //The values must be assigned if the types are the same
outValue=*((T*)((unsigned char*) & amp;stu + pField->filedOffs));
}

With these two template functions
Modify as follows when obtaining field data:

while(pField->TypeHashCode!=0)
{<!-- -->
//Get the value of the field
if(lstrcmp(pField->Name,TEXT("mName"))==0)//Find the field information structure whose field is mName
{<!-- -->
QString name;
pField->Get_Field_Values( & amp;stu,name); //Get the value of the field named mName
}

if(lstrcmp(pField->Name,TEXT("mSex"))==0)//Find the field information structure whose field is mSex
{<!-- -->
QString sex;
pField->Get_Field_Values( & amp;stu,sex); //Get the value of the field named mSex
}
\t
if(lstrcmp(pField->Name,TEXT("mAge"))==0)//Find the field information structure whose field is mAge
{<!-- -->
UINT age;
pField->Get_Field_Values( & amp;stu,(UINT)age); //Get the value of the field named mAge, you must force it, otherwise the type will be different
}
pField + + ;
}


//Of course, setting the field value is the same.
while(pField->TypeHashCode!=0)
{<!-- -->
//Set the field value
\t
if(lstrcmp(pField->Name,TEXT("mName"))==0)//Find the field information structure whose field is mName
{<!-- -->
pField->Set_Field_Values( & amp;stu,QString(TEXT("Zhao Si"))); //Get the value of the field named mName, and also force it to QString
}
//Other types are the same, only the conversion type changes

if(lstrcmp(pField->Name,TEXT("mSex"))==0)//Find the field information structure whose field is mSex
{<!-- -->
pField->Set_Field_Values( & amp;stu,QString(TEXT("male"))); //Get the value of the field named mSex
}
\t
if(lstrcmp(pField->Name,TEXT("mAge"))==0)//Find the field information structure whose field is mAge
{<!-- -->
pField->Set_Field_Values( & amp;stu,(UINT)45); //Get the value of the field named mAge, you must force it, otherwise the type will be different
}
pField + + ;
}


Summarize:
That’s basically it. This is mainly for reading data from other places and adding it to the object.
For example, reading data from the database and automatically adding data to the corresponding fields of the object,
Outside we only read the object’s data and don’t care about the database fields.
Next time I will write about how to automatically add data from the database to the class object.