C++ Builder 的反射 (一) - Reflection 簡單實現(xiàn)

C++ Builder 參考手冊 ? C++ Builder 的反射 (一) - Reflection 簡單實現(xiàn)


由于 C++ 沒有直接提供反射功能,所以很多人都在想盡各種辦法來實現(xiàn)。
本文提供由 TObject 繼承的類的反射方法,這是 VCL 和 FMX 框架的類和控件的公共的頂級父類,其他類另寫文章來論述。本文不是官方提供的,也是作者自己研究出來的方法,按照簡單易懂的方式,盡量使用更少的代碼,如果有其他想法和意見,歡迎討論。

  1. 控件的反射
    1.1 方法1:使用模板函數(shù)和 lambda 表達(dá)式注冊類,需要 C++ 11 / clang 編譯器
    1.2 方法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í)行的代碼。

測試控件反射 - 窗口設(shè)計

運行結(jié)果:

創(chuàng)建一個 TComboBox:

創(chuàng)建一個 TComboBox

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

再創(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é)果:

運行結(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 參考手冊 ? C++ Builder 的反射 (一) - Reflection 簡單實現(xiàn)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容