C++ Builder 參考手冊 ? C++ Builder 獲取任意一個類或?qū)ο蟮念惷?/strong>
前面寫了兩篇關(guān)于 C++ Builder 反射的文章,應(yīng)該寫第三篇了。前面兩篇都是關(guān)于從 TObject 繼承的類的反射,那么其他類到底和 TObject 繼承的類有什么不同?又是因?yàn)槭裁从绊懥似渌惖姆瓷洌颗c前一篇文章《C++ Builder 的反射 (二) - Reflection Factory》比較,其他的類只差一個獲取類名,從 TObject 繼承的類可以直接通過類名或?qū)ο笾羔樀玫筋惷址渌念惒荒?。本文程序和例子?C++ Builder 10.2.3 版本 clang 32位 和 clang64位編譯器測試通過。
- C++ Builder 獲取類名的方法
1.1. THsuanluClassName 類及使用方法
1.2. THsuanluClassName 類的實(shí)現(xiàn) - 獲取類名的示例程序
1. C++ Builder 獲取類名的方法
1.1. THsuanluClassName 類及使用方法
class THsuanluClassName
{
public:
static UnicodeString _TObject_QualifiedClassName(UnicodeString s); // Vcl.StdCtrls.TMemo -> Vcl::Stdctrls::TMemo
static UTF8String _TypeNameToQualifiedClassName32(UTF8String s); // $Hsuanlu@Test@THsuanluClass -> Hsuanlu::Test::THsuanluClass
static UTF8String _TypeNameToQualifiedClassName64(UTF8String s); // N7Hsuanlu4Test13THsuanluClassE -> Hsuanlu::Test::THsuanluClass
static UTF8String _TypeNameToQualifiedClassName(UTF8String s); // Hsuanlu::Test::THsuanluClass
static UTF8String _TypeNameToClassName(UTF8String s); // THsuanluClass
template <class T>
static UnicodeString GetClassName(std::true_type)
{
return T::ClassName(); // 這是從 TObject 繼承的類的類名
}
template <class T>
static UnicodeString GetClassName(std::false_type)
{
return _TypeNameToClassName(typeid(T).name()); // 不是從 TObject 繼承的類的類名
}
template <class T>
static UnicodeString GetClassName(T*) // 通過對象指針獲取類名
{
return GetClassName<T>(std::bool_constant<__is_base_of(TObject,T)>());
}
template <class T>
static UnicodeString GetClassName(void) // 直接獲取類的類名
{
return GetClassName<T>(std::bool_constant<__is_base_of(TObject,T)>());
}
template <class T>
static UnicodeString GetQualifiedClassName(std::true_type)
{
return _TObject_QualifiedClassName(T::QualifiedClassName()); // 這是從 TObject 繼承的類的類名,帶命名空間
}
template <class T>
static UnicodeString GetQualifiedClassName(std::false_type)
{
return _TypeNameToQualifiedClassName(typeid(T).name()); // 不是從 TObject 繼承的類的類名,帶命名空間
}
template <class T>
static UnicodeString GetQualifiedClassName(T*) // 通過對象指針獲取類名,帶命名空間
{
return GetQualifiedClassName<T>(std::bool_constant<__is_base_of(TObject,T)>());
}
template <class T>
static UnicodeString GetQualifiedClassName(void) // 直接獲取類的類名,帶命名空間
{
return GetQualifiedClassName<T>(std::bool_constant<__is_base_of(TObject,T)>());
}
};
這個類里面都是靜態(tài)函數(shù)和靜態(tài)函數(shù)模板,在實(shí)際應(yīng)用上,一般只需要調(diào)用這兩個成員:
? THsuanluClassName::GetClassName 獲取類名;
? THsuanluClassName::GetQualifiedClassName 獲取帶命名空間的類名;
由于其他函數(shù)在某些特定環(huán)境可能會使用,所以都放在了 public: 里面。
使用方法:由于類型只能做模板參數(shù),對象指針可以做函數(shù)參數(shù),所以帶參數(shù)的函數(shù)版本的參數(shù)是對象指針,不帶參數(shù)的函數(shù)利用模板參數(shù)直接獲取類的類名。
例如:
s = THsuanluClassName::GetClassName<TMemo>();
s = THsuanluClassName::GetClassName(Memo1);
都將得到 L"TMemo" 字符串。
s = THsuanluClassName::GetQualifiedClassName<TMemo>();
s = THsuanluClassName::GetQualifiedClassName(Memo1);
都將得到 L"Vcl::Stdctrls::TMemo" 字符串
通過 this 指針也可以得到當(dāng)前代碼所在類的類名,例如在窗口 Form1 的代碼里面 THsuanluClassName::GetClassName(this) 可以得到字符串 "TForm1"
1.2. THsuanluClassName 類的實(shí)現(xiàn)
#include <memory>
#include <cxxabi.h> // $(BDS)\source\cpprtl\Source\libcxxabi\win64\include
UnicodeString THsuanluClassName::_TObject_QualifiedClassName(UnicodeString s)
{
std::auto_ptr<TStringList> slNames(new TStringList);
slNames->TrailingLineBreak = false;
slNames->LineBreak = L".";
slNames->Text = s;
slNames->LineBreak = L"::";
int iCount = slNames->Count;
for(int iIdx=0; iIdx<iCount-1; iIdx++) // 處理命名空間
{
UnicodeString s = slNames->Strings[iIdx]; // 從 TObject 繼承的類的命名空間
slNames->Strings[iIdx] = s.SubString(1,1).UpperCase() // 第一個字母大寫
+ s.SubString(2,s.Length()).LowerCase(); // 其余字母小寫
}
return slNames->Text;
}
UTF8String THsuanluClassName::_TypeNameToQualifiedClassName32(UTF8String s)
{
UTF8String sReal;
const char *pChar = s.c_str();
if(*pChar++ == '$')
{
bool bTemplate = false;
while(*pChar)
{
if(bTemplate)
{
if(*pChar == '$')
break;
sReal += *pChar;
}
else switch(*pChar)
{
case '%': bTemplate = true; break;
case '@': sReal += "::"; break;
default : sReal += *pChar; break;
}
pChar++;
}
}
return sReal;
}
UTF8String THsuanluClassName::_TypeNameToQualifiedClassName64(UTF8String s)
{
UTF8String sReal;
char *psname = abi::__cxa_demangle(s.c_str(), nullptr, nullptr, nullptr);
if(psname)
{
sReal = psname;
free(psname);
}
return sReal;
}
UTF8String THsuanluClassName::_TypeNameToQualifiedClassName(UTF8String s)
{
UTF8String sRealName = _TypeNameToQualifiedClassName32(s);
if(sRealName.IsEmpty())
sRealName = _TypeNameToQualifiedClassName64(s);
if(sRealName.IsEmpty())
sRealName = s;
int iPos = sRealName.Pos("<");
if(iPos > 0)
sRealName = sRealName.SubString(1, iPos-1);
return sRealName;
}
UTF8String THsuanluClassName::_TypeNameToClassName(UTF8String s)
{
UTF8String sRealName = _TypeNameToQualifiedClassName(s);
const char *pLastColon = std::strrchr(sRealName.c_str(),':');
if(pLastColon)
return pLastColon+1;
return sRealName;
}
標(biāo)準(zhǔn) C++ 的類,可以通過 typeid(T).name() 獲取類型名,對于類來說,不僅僅是類名,還包含了命名空間和一些其他信息,這個格式并沒有統(tǒng)一標(biāo)準(zhǔn),gcc 和 clang 都給出了 abi::__cxa_demangle 函數(shù)把類型名轉(zhuǎn)成類名,包含在頭文件 cxxabi.h 里面。
C++ Builder 使用 clang 編譯器,所以也應(yīng)該有這個頭文件,結(jié)果在 C++ Builder 的文件夾里面找到了在 $(BDS)\source\cpprtl\Source\libcxxabi\win64\include 這個文件夾里面,為什么在 Win64 里面,其他平臺里面沒有?經(jīng)過測試,Win32 使用這個頭文件可以編譯通過,但是無法把類型名轉(zhuǎn)成類名,Win64 可以轉(zhuǎn)換成功,所以頭文件就在 Win64 文件夾里面。
經(jīng)過測試,C++ Builder 的 Win32 編譯器類的類型名都是 $ 字符開頭,命名空間之間用 @ 分割,例如 $Hsuanlu@Test@THsuanluClass 即為 Hsuanlu::Test::THsuanluClass,而 Win64 編譯器生成的類名都是字母或數(shù)字開頭的,所以程序就簡單了,先判斷如果 $ 開頭使用這個規(guī)則,不是 $ 開頭的使用 abi::__cxa_demangle 函數(shù)轉(zhuǎn)換。
由于從 TObject 繼承的類與標(biāo)準(zhǔn) C++ 的類使用不同的 RTTI,TObject 是為了兼容 Delphi 程序,使用的是兼容的 Delphi RTTI,而其他的不從 TObject 繼承的類,都是使用的標(biāo)準(zhǔn) C++ RTTI,這兩種 RTTI 不兼容,如果用 typeid(T).name() 獲取 TObject 繼承的類的類型名會拋出內(nèi)存訪問錯誤的異常,所以程序必須先判斷是否從 TObject 繼承,然后再判斷是否 $ 開頭的類型名。
函數(shù) _TObject_QualifiedClassName 是把 TObject::QualifiedClassName 獲取到的帶命名空間的類名轉(zhuǎn)成 C++ 格式,因?yàn)橹苯荧@取到的命名空間和類名之間用 . 分割,需要改成 ::,并且命名空間的大小寫也和實(shí)際不符,所有這些類的命名空間應(yīng)該是開頭字母大寫,其余小寫,所以用這個函數(shù)轉(zhuǎn)換。
函數(shù) _TypeNameToQualifiedClassName32 把 $ 開頭的類型名轉(zhuǎn)成帶命名空間的類名,如果類型是模板,類名會在第一個 % 和之后的第一個 $ 之間。
函數(shù) _TypeNameToQualifiedClassName64 把字母和數(shù)字開頭的類型名,使用 abi::__cxa_demangle 轉(zhuǎn)成類名。
函數(shù) _TypeNameToQualifiedClassName 把類型名轉(zhuǎn)成帶命名空間的類名,先使用 32 位那個 $ 開頭的轉(zhuǎn)換,如果轉(zhuǎn)換失敗,即開頭不是 $,那么就使用 64 位那個字母或數(shù)字開頭的類型名轉(zhuǎn)換,如果都失敗了,直接返回類型名。如果是模板,把模板參數(shù)去掉,即把第一個 < 和后面的東西刪掉。
函數(shù) _TypeNameToClassName 把類型名轉(zhuǎn)成類名,先調(diào)用 _TypeNameToQualifiedClassName 得到帶命名空間的類名,然后把命名空間去掉,保留最后一個 : 后面的部分。
由于 typeid(T).name() 返回的類型名是 UTF-8 編碼的,所以處理類型名的函數(shù)都是 UTF8String 類型的參數(shù)和返回值,在 GetClassName 和 GetQualifiedClassName 里面調(diào)用并轉(zhuǎn)成 UnicodeString 類型的,這也說明類型是可以用漢字或其他非英語語言的。
函數(shù)模板 GetClassName 使用 __is_base_of(TObject,T) 判斷 T 是為 TObject 或他的子類,如果是,使用 std::true_type 參數(shù)的函數(shù),如果否,使用 std::false_type 參數(shù)的函數(shù)。使用這個方法,而不是使用 if else,原因是 if else 無論是否滿足條件都要編譯,不滿足條件會語法錯誤,而這個方法,不滿足條件不但不會執(zhí)行,也不會編譯和檢查語法錯誤。
2. 獲取類名的示例程序
通過自己寫的類 (非 TObject 繼承) 和控件類 (TObject 繼承) 測試獲取類名。
包括帶命名空間和不帶命名空間的,同時也測試了漢字類名。
namespace Hsuanlu {
namespace Test {
class THsuanluClass
{
public:
THsuanluClass(){}
virtual ~THsuanluClass(){}
class TTest1{};
};
}
class 玄坴測試類
{
public:
玄坴測試類(){}
};
template<class T>
class THsuanluTemp : public Test::THsuanluClass
{
};
}
using namespace Hsuanlu;
using namespace Hsuanlu::Test;
void __fastcall TForm1::Button1Click(TObject *Sender)
{
THsuanluClass Obj;
THsuanluClass::TTest1 t1;
玄坴測試類 Test;
const THsuanluClass Obj1;
THsuanluTemp<double> Temp;
Memo1->Lines->Add(THsuanluClassName::GetClassName(this));
Memo1->Lines->Add(THsuanluClassName::GetClassName(Memo1));
Memo1->Lines->Add(THsuanluClassName::GetClassName(&Obj));
Memo1->Lines->Add(THsuanluClassName::GetClassName(&Obj1));
Memo1->Lines->Add(THsuanluClassName::GetClassName(&t1));
Memo1->Lines->Add(THsuanluClassName::GetClassName(&Test));
Memo1->Lines->Add(L"");
Memo1->Lines->Add(THsuanluClassName::GetClassName<TForm>());
Memo1->Lines->Add(THsuanluClassName::GetClassName<TMemo>());
Memo1->Lines->Add(THsuanluClassName::GetClassName<THsuanluClass>());
Memo1->Lines->Add(THsuanluClassName::GetClassName<THsuanluClass::TTest1>());
Memo1->Lines->Add(THsuanluClassName::GetClassName<玄坴測試類>());
Memo1->Lines->Add(THsuanluClassName::GetClassName<THsuanluTemp<UnicodeString>>());
Memo1->Lines->Add(THsuanluClassName::GetClassName(&Temp));
Memo1->Lines->Add(L"");
Memo1->Lines->Add(THsuanluClassName::GetQualifiedClassName(this));
Memo1->Lines->Add(THsuanluClassName::GetQualifiedClassName(Memo1));
Memo1->Lines->Add(THsuanluClassName::GetQualifiedClassName(&Obj));
Memo1->Lines->Add(THsuanluClassName::GetQualifiedClassName(&Obj1));
Memo1->Lines->Add(THsuanluClassName::GetQualifiedClassName(&t1));
Memo1->Lines->Add(THsuanluClassName::GetQualifiedClassName(&Test));
Memo1->Lines->Add(L"");
Memo1->Lines->Add(THsuanluClassName::GetQualifiedClassName<TForm>());
Memo1->Lines->Add(THsuanluClassName::GetQualifiedClassName<TMemo>());
Memo1->Lines->Add(THsuanluClassName::GetQualifiedClassName<THsuanluClass>());
Memo1->Lines->Add(THsuanluClassName::GetQualifiedClassName<THsuanluClass::TTest1>());
Memo1->Lines->Add(THsuanluClassName::GetQualifiedClassName<玄坴測試類>());
Memo1->Lines->Add(THsuanluClassName::GetQualifiedClassName<THsuanluTemp<UnicodeString>>());
Memo1->Lines->Add(THsuanluClassName::GetQualifiedClassName(&Temp));
}
運(yùn)行結(jié)果:

相關(guān):
- C++ Builder 的反射 (三) - 通用 Reflection Factory
- C++ Builder 的反射 (二) - Reflection Factory
- C++ Builder 的反射 (一) - Reflection 簡單實(shí)現(xiàn)
- 枚舉控件所有的屬性、事件和方法
- 枚舉窗口內(nèi)所有的控件
- C++ Builder 的枚舉類型
- C / C++ 可變參數(shù)的函數(shù)
- C / C++ 可變參數(shù)的宏,__VA_ARGS__,...
- C++ 可變參數(shù)的模板
- C++ Builder 的 PME 架構(gòu)
C++ Builder 參考手冊 ? C++ Builder 獲取任意一個類或?qū)ο蟮念惷?/strong>