C++ Builder 參考手冊 ? C++ Builder 的反射 (一) - Reflection 簡單實現(xiàn)
由于 C++ 沒有直接提供反射功能,所以很多人都在想盡各種辦法來實現(xiàn)。
本文提供由 TObject 繼承的類的反射方法,這是 VCL 和 FMX 框架的類和控件的公共的頂級父類,其他類另寫文章來論述。本文不是官方提供的,也是作者自己研究出來的方法,按照簡單易懂的方式,盡量使用更少的代碼,如果有其他想法和意見,歡迎討論。
-
控件的反射
1.1 方法1:使用模板函數(shù)和 lambda 表達(dá)式注冊類,需要 C++ 11 / clang 編譯器
1.2 方法2:使用宏定義來模仿第一種方法,支持所有版本的編譯器 -
自己寫的類的反射
2.1 方法1:使用模板函數(shù)和 lambda 表達(dá)式注冊類,需要 C++ 11 / clang 編譯器
2.2 方法2:使用宏定義來模仿第一種方法,支持所有版本的編譯器
1. 控件的反射
1.1. 方法1:使用模板函數(shù)和 lambda 表達(dá)式注冊類,需要 C++ 11 / clang 編譯器
由于 C++ 在編譯時,從未使用過的類不會編譯到 exe 里面,所以要把所有需要使用的類都注冊一遍,把創(chuàng)建類的方法和類名做一個 std::map 映射,以下兩個函數(shù):RegCtrl 為注冊控件類,CreateCtrl 為通過類名字符串創(chuàng)建控件類的對象。
#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);
}
在使用上,在程序開始運行的時候,例如在主窗口的構(gòu)造函數(shù)里面,把所有需要反射的控件類都注冊一遍,以后就可以通過 CreateCtrl("控件類名") 來創(chuàng)建控件了。這個例子注冊了 TLabel、TButton、TMemo、TEdit、TCheckBox、TRadioButton、TComboBox 等幾個控件類,可以使用他們的類名字符串來創(chuàng)建控件對象。
__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 創(chuàng)建控件,ButtonCreateClick 方法就是點擊這個按鈕執(zhí)行的代碼。

運行結(jié)果:
創(chuàng)建一個 TComboBox:

再創(chuàng)建一個 TCheckBox 和一個 TRadioButton

1.2 方法2:使用宏定義來模仿第一種方法,支持所有版本的編譯器
由于老版本編譯器沒有 lambda 表達(dá)式,所以只能是每個類定義一個全局函數(shù),用來創(chuàng)建這個類,使用宏定義 RegCtrlCreate 來定義這些函數(shù),使用宏定義 RegCtrlClass 把類和函數(shù)做 std::map 映射。全局函數(shù)和映射必須分開來寫,所以做成了兩個宏。
#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);
}
在使用上,在全局位置 (不能寫在函數(shù)里面) 使用 RegCtrlCreate 定義每個類的創(chuàng)建函數(shù),在程序開始運行的時候,例如在主窗口的構(gòu)造函數(shù)里面,把所有需要反射的控件類使用 RegCtrlClass 注冊一遍,以后就可以通過 CreateCtrl("控件類名") 來創(chuàng)建控件了。這個例子注冊了 TLabel、TButton、TMemo、TEdit、TCheckBox、TRadioButton、TComboBox 等幾個控件類,可以使用他們的類名字符串來創(chuàng)建控件對象。
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 繼承,作為其他幾個類的公共父類,反射的結(jié)果是通過這個類型的指針返回通過類名字符串創(chuàng)建的對象。
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:使用模板函數(shù)和 lambda 表達(dá)式注冊類,需要 C++ 11 / clang 編譯器
和控件反射一樣,做一個注冊類的函數(shù)和一個反射創(chuàng)建的函數(shù),在程序開始執(zhí)行的時候,即主窗口的構(gòu)造函數(shù)里面注冊所有需要反射的類,然后就可以使用類名字符串來創(chuàng)建這些類的對象了。
#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);
}
}
運行結(jié)果:

2.2 方法2:使用宏定義來模仿第一種方法,支持所有版本的編譯器
由于老版本編譯器沒有 lambda 表達(dá)式,所以只能是每個類定義一個全局函數(shù),用來創(chuàng)建這個類,使用宏定義 RegCtrlCreate 來定義這些函數(shù),使用宏定義 RegCtrlClass 把類和函數(shù)做 std::map 映射。全局函數(shù)和映射必須分開來寫,所以做成了兩個宏。
在使用上,在全局位置 (不能寫在函數(shù)里面) 使用 RegCtrlCreate 定義每個類的創(chuàng)建函數(shù),在程序開始運行的時候,例如在主窗口的構(gòu)造函數(shù)里面,把所有需要反射的類使用 RegCtrlClass 注冊一遍,以后就可以通過 CreateCtrl("類名") 來創(chuàng)建這些類的對象了。
#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);
}
}
運行結(jié)果與 方法1 相同。
相關(guān):
- C++ Builder 的反射 (三) - 通用 Reflection Factory
- C++ Builder 的反射 (二) - Reflection Factory
- 枚舉控件所有的屬性、事件和方法
- 枚舉窗口內(nèi)所有的控件
- C++ Builder 的枚舉類型
- C / C++ 可變參數(shù)的函數(shù)
- C / C++ 可變參數(shù)的宏,__VA_ARGS__,...
- C++ 可變參數(shù)的模板
- C++ Builder 的 PME 架構(gòu)
C++ Builder 參考手冊 ? C++ Builder 的反射 (一) - Reflection 簡單實現(xiàn)