C++ Builder 参考手册 ➙ C++ Builder 的反射 (一) - Reflection 简单实现
由于 C++ 没有直接提供反射功能,所以很多人都在想尽各种办法来实现。
本文提供由 TObject 继承的类的反射方法,这是 VCL 和 FMX 框架的类和控件的公共的顶级父类,其他类另写文章来论述。本文不是官方提供的,也是作者自己研究出来的方法,按照简单易懂的方式,尽量使用更少的代码,如果有其他想法和意见,欢迎讨论。
-
控件的反射
1.1 方法1:使用模板函数和 lambda 表达式注册类,需要 C++ 11 / clang 编译器
1.2 方法2:使用宏定义来模仿第一种方法,支持所有版本的编译器 -
自己写的类的反射
2.1 方法1:使用模板函数和 lambda 表达式注册类,需要 C++ 11 / clang 编译器
2.2 方法2:使用宏定义来模仿第一种方法,支持所有版本的编译器
1. 控件的反射
1.1. 方法1:使用模板函数和 lambda 表达式注册类,需要 C++ 11 / clang 编译器
由于 C++ 在编译时,从未使用过的类不会编译到 exe 里面,所以要把所有需要使用的类都注册一遍,把创建类的方法和类名做一个 std::map 映射,以下两个函数:RegCtrl 为注册控件类,CreateCtrl 为通过类名字符串创建控件类的对象。
#include <map>
std::map<UnicodeString,TControl*(*)(TComponent*)>_ClassMap;
template <class T>
void RegCtrl(void)
{
_ClassMap[T::ClassName()] = [](TComponent *pOwner) -> TControl*
{
return new T(pOwner);
};
}
//---------------------------------------------------------------------------
TControl *CreateCtrl(UnicodeString sClassName, TComponent *pOwner)
{
auto iter = _ClassMap.find(sClassName);
if(iter == _ClassMap.end())
throw Exception(L"类 \"" + sClassName + L"\" 未注册");
return iter->second(pOwner);
}
在使用上,在程序开始运行的时候,例如在主窗口的构造函数里面,把所有需要反射的控件类都注册一遍,以后就可以通过 CreateCtrl("控件类名") 来创建控件了。这个例子注册了 TLabel、TButton、TMemo、TEdit、TCheckBox、TRadioButton、TComboBox 等几个控件类,可以使用他们的类名字符串来创建控件对象。
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
RegCtrl<TLabel>();
RegCtrl<TButton>();
RegCtrl<TMemo>();
RegCtrl<TEdit>();
RegCtrl<TCheckBox>();
RegCtrl<TRadioButton>();
RegCtrl<TComboBox>();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ButtonCreateClick(TObject *Sender)
{
try
{
TControl *p = CreateCtrl(EditClassName->Text, this);
p->Parent = this;
p->SetBounds(EditLeft->Text.ToIntDef(0), EditTop->Text.ToIntDef(0), EditWidth->Text.ToIntDef(80), EditHeight->Text.ToIntDef(30));
}
catch(Exception &e)
{
ShowMessage(e.Message);
}
}
编辑框 EditClassName 输入控件的类名,
编辑框 EditLeft, EditTop, EditWidth, EditHeight 分别用于输入控件的位置和大小,
按钮 ButtonCreate 创建控件,ButtonCreateClick 方法就是点击这个按钮执行的代码。
运行结果:
创建一个 TComboBox:
再创建一个 TCheckBox 和一个 TRadioButton
1.2 方法2:使用宏定义来模仿第一种方法,支持所有版本的编译器
由于老版本编译器没有 lambda 表达式,所以只能是每个类定义一个全局函数,用来创建这个类,使用宏定义 RegCtrlCreate 来定义这些函数,使用宏定义 RegCtrlClass 把类和函数做 std::map 映射。全局函数和映射必须分开来写,所以做成了两个宏。
#include <map>
std::map<UnicodeString,TControl*(*)(TComponent*)>_ClassMap;
#define RegCtrlCreate(T) TControl *_RegCtrl_Create_##T(TComponent *pOwner){ return new T(pOwner); }
#define RegCtrlClass(T) _ClassMap[T::ClassName()] = _RegCtrl_Create_##T;
//---------------------------------------------------------------------------
TControl *CreateCtrl(UnicodeString sClassName, TComponent *pOwner)
{
std::map<UnicodeString,TControl*(*)(TComponent*)>::iterator
iter = _ClassMap.find(sClassName);
if(iter == _ClassMap.end())
throw Exception(L"类 \"" + sClassName + L"\" 未注册");
return iter->second(pOwner);
}
在使用上,在全局位置 (不能写在函数里面) 使用 RegCtrlCreate 定义每个类的创建函数,在程序开始运行的时候,例如在主窗口的构造函数里面,把所有需要反射的控件类使用 RegCtrlClass 注册一遍,以后就可以通过 CreateCtrl("控件类名") 来创建控件了。这个例子注册了 TLabel、TButton、TMemo、TEdit、TCheckBox、TRadioButton、TComboBox 等几个控件类,可以使用他们的类名字符串来创建控件对象。
RegCtrlCreate(TLabel);
RegCtrlCreate(TButton);
RegCtrlCreate(TMemo);
RegCtrlCreate(TEdit);
RegCtrlCreate(TCheckBox);
RegCtrlCreate(TRadioButton);
RegCtrlCreate(TComboBox);
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
RegCtrlClass(TLabel);
RegCtrlClass(TButton);
RegCtrlClass(TMemo);
RegCtrlClass(TEdit);
RegCtrlClass(TCheckBox);
RegCtrlClass(TRadioButton);
RegCtrlClass(TComboBox);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::ButtonCreateClick(TObject *Sender)
{
try
{
TControl *p = CreateCtrl(EditClassName->Text, this);
p->Parent = this;
p->SetBounds(EditLeft->Text.ToIntDef(0), EditTop->Text.ToIntDef(0), EditWidth->Text.ToIntDef(80), EditHeight->Text.ToIntDef(30));
}
catch(Exception &e)
{
ShowMessage(e.Message);
}
}
2. 自己写的类的反射
由于本文讨论从 TObject 继承的类的反射,这里写几个类:其中 THsuanluBase 从 TObject 继承,作为其他几个类的公共父类,反射的结果是通过这个类型的指针返回通过类名字符串创建的对象。
class THsuanluBase : public TObject
{
public:
virtual void PrintMessage(void){ Form1->Memo1->Lines->Add(L"这是父类的 PrintMessage 方法"); }
};
//---------------------------------------------------------------------------
class THsuanluTest1 : public THsuanluBase
{
public:
virtual void PrintMessage(void){ Form1->Memo1->Lines->Add(L"这是 THsuanluTest1 的 PrintMessage 方法"); }
};
//---------------------------------------------------------------------------
class THsuanluTest2 : public THsuanluBase
{
public:
virtual void PrintMessage(void){ Form1->Memo1->Lines->Add(L"这是 THsuanluTest2 的 PrintMessage 方法"); }
};
//---------------------------------------------------------------------------
class THsuanluTest3 : public THsuanluTest2
{
public:
virtual void PrintMessage(void){ Form1->Memo1->Lines->Add(L"这是 THsuanluTest3 的 PrintMessage 方法"); }
};
2.1 方法1:使用模板函数和 lambda 表达式注册类,需要 C++ 11 / clang 编译器
和控件反射一样,做一个注册类的函数和一个反射创建的函数,在程序开始执行的时候,即主窗口的构造函数里面注册所有需要反射的类,然后就可以使用类名字符串来创建这些类的对象了。
#include <map>
std::map<UnicodeString, THsuanluBase*(*)()>_ClassMap;
template <class T>
void RegCtrl(void)
{
_ClassMap[T::ClassName()] = []() -> THsuanluBase*
{
return new T();
};
}
//---------------------------------------------------------------------------
THsuanluBase *CreateCtrl(UnicodeString sClassName)
{
auto iter = _ClassMap.find(sClassName);
if(iter == _ClassMap.end())
throw Exception(L"类 \"" + sClassName + L"\" 未注册");
return iter->second();
}
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
RegCtrl<THsuanluTest1>();
RegCtrl<THsuanluTest2>();
RegCtrl<THsuanluTest3>();
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
try
{
auto *p1 = CreateCtrl(L"THsuanluTest1");
auto *p2 = CreateCtrl(L"THsuanluTest2");
auto *p3 = CreateCtrl(L"THsuanluTest3");
p1->PrintMessage();
p2->PrintMessage();
p3->PrintMessage();
delete p1;
delete p2;
delete p3;
}
catch(Exception &e)
{
ShowMessage(e.Message);
}
}
运行结果:
2.2 方法2:使用宏定义来模仿第一种方法,支持所有版本的编译器
由于老版本编译器没有 lambda 表达式,所以只能是每个类定义一个全局函数,用来创建这个类,使用宏定义 RegCtrlCreate 来定义这些函数,使用宏定义 RegCtrlClass 把类和函数做 std::map 映射。全局函数和映射必须分开来写,所以做成了两个宏。
在使用上,在全局位置 (不能写在函数里面) 使用 RegCtrlCreate 定义每个类的创建函数,在程序开始运行的时候,例如在主窗口的构造函数里面,把所有需要反射的类使用 RegCtrlClass 注册一遍,以后就可以通过 CreateCtrl("类名") 来创建这些类的对象了。
#include <map>
std::map<UnicodeString,THsuanluBase*(*)()>_ClassMap;
#define RegCtrlCreate(T) THsuanluBase *_RegCtrl_Create_##T(){ return new T(); }
#define RegCtrlClass(T) _ClassMap[T::ClassName()] = _RegCtrl_Create_##T;
//---------------------------------------------------------------------------
THsuanluBase *CreateCtrl(UnicodeString sClassName)
{
std::map<UnicodeString,THsuanluBase*(*)()>::iterator
iter = _ClassMap.find(sClassName);
if(iter == _ClassMap.end())
throw Exception(L"类 \"" + sClassName + L"\" 未注册");
return iter->second();
}
//---------------------------------------------------------------------------
RegCtrlCreate(THsuanluTest1);
RegCtrlCreate(THsuanluTest2);
RegCtrlCreate(THsuanluTest3);
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
RegCtrlClass(THsuanluTest1);
RegCtrlClass(THsuanluTest2);
RegCtrlClass(THsuanluTest3);
}
//---------------------------------------------------------------------------
void __fastcall TForm1::Button1Click(TObject *Sender)
{
try
{
THsuanluBase *p1 = CreateCtrl(L"THsuanluTest1");
THsuanluBase *p2 = CreateCtrl(L"THsuanluTest2");
THsuanluBase *p3 = CreateCtrl(L"THsuanluTest3");
p1->PrintMessage();
p2->PrintMessage();
p3->PrintMessage();
delete p1;
delete p2;
delete p3;
}
catch(Exception &e)
{
ShowMessage(e.Message);
}
}
运行结果与 方法1 相同。
相关:
- C++ Builder 的反射 (三) - 通用 Reflection Factory
- C++ Builder 的反射 (二) - Reflection Factory
- 枚举控件所有的属性、事件和方法
- 枚举窗口内所有的控件
- C++ Builder 的枚举类型
- C / C++ 可变参数的函数
- C / C++ 可变参数的宏,__VA_ARGS__,...
- C++ 可变参数的模板
- C++ Builder 的 PME 架构
C++ Builder 参考手册 ➙ C++ Builder 的反射 (一) - Reflection 简单实现