Reflexe (C++/CLI)
Reflexe umožňuje kontrolu známých datových typů za běhu. Reflexe umožňuje výčet datových typů v daném sestavení a členy daného typu třídy nebo hodnoty lze zjistit. To platí bez ohledu na to, zda byl typ známý nebo odkazován v době kompilace. Díky tomu je reflexe užitečnou funkcí pro vývojové nástroje a nástroje pro správu kódu.
Všimněte si, že zadaný název sestavení je silný název (viz Vytváření a používání sestavení se silným názvem), který obsahuje verzi sestavení, jazykovou verzi a podpisové informace. Všimněte si také, že název oboru názvů, ve kterém je datový typ definován, lze načíst spolu s názvem základní třídy.
Nejběžnějším způsobem, jak získat přístup k funkcím reflexe, je metoda GetType . Tato metoda je poskytována System.Object, ze kterého jsou odvozeny všechny třídy uvolňování paměti.
Poznámka:
Reflexe .exe sestavená pomocí kompilátoru Microsoft C++ je povolena pouze v případě, že je .exe sestavena s možnostmi kompilátoru /clr:pure nebo /clr:safe . Možnosti kompilátoru /clr:pure a /clr:safe jsou v sadě Visual Studio 2015 zastaralé a v sadě Visual Studio 2017 nejsou k dispozici. Další informace najdete v tématu /clr (kompilace modulu Common Language Runtime).
Další informace viz System.Reflection.
Příklad: GetType
Metoda GetType
vrátí ukazatel na Type objekt třídy, který popisuje typ na základě objektu. (Objekt typu neobsahuje žádné informace specifické pro instanci.) Jedna taková položka je úplný název typu, který lze zobrazit následujícím způsobem:
Všimněte si, že název typu zahrnuje úplný obor, ve kterém je typ definován, včetně oboru názvů, a že se zobrazí v syntaxi .NET s tečkou jako operátor rozlišení oboru.
// vcpp_reflection.cpp
// compile with: /clr
using namespace System;
int main() {
String ^ s = "sample string";
Console::WriteLine("full type name of '{0}' is '{1}'", s, s->GetType());
}
full type name of 'sample string' is 'System.String'
Příklad: typy hodnot v rámečku
S funkcí se dají použít GetType
i typy hodnot, ale musí být nejprve v rámečku.
// vcpp_reflection_2.cpp
// compile with: /clr
using namespace System;
int main() {
Int32 i = 100;
Object ^ o = i;
Console::WriteLine("type of i = '{0}'", o->GetType());
}
type of i = 'System.Int32'
Příklad: typeid
Stejně jako u GetType
metody vrátí operátor typeid ukazatel na objekt Type , takže tento kód označuje název typu System.Int32. Zobrazení názvů typů je nejzásadnější funkcí reflexe, ale potenciálně užitečnější technikou je zkontrolovat nebo zjistit platné hodnoty pro výčtové typy. To lze provést pomocí statické funkce Enum::GetNames , která vrací pole řetězců, z nichž každý obsahuje hodnotu výčtu v textové podobě. Následující ukázka načte pole řetězců, které popisují hodnoty výčtu hodnot pro výčet Options (CLR) a zobrazí je ve smyčce.
Pokud je do výčtu Options přidána čtvrtá možnost, bude tento kód hlásit novou možnost bez rekompilace, i když je výčet definován v samostatném sestavení.
// vcpp_reflection_3.cpp
// compile with: /clr
using namespace System;
enum class Options { // not a native enum
Option1, Option2, Option3
};
int main() {
array<String^>^ names = Enum::GetNames(Options::typeid);
Console::WriteLine("there are {0} options in enum '{1}'",
names->Length, Options::typeid);
for (int i = 0 ; i < names->Length ; i++)
Console::WriteLine("{0}: {1}", i, names[i]);
Options o = Options::Option2;
Console::WriteLine("value of 'o' is {0}", o);
}
there are 3 options in enum 'Options'
0: Option1
1: Option2
2: Option3
value of 'o' is Option2
Příklad: GetType – členy a vlastnosti
Objekt GetType
podporuje řadu členů a vlastností, které lze použít k prozkoumání typu. Tento kód načte a zobrazí některé z těchto informací:
// vcpp_reflection_4.cpp
// compile with: /clr
using namespace System;
int main() {
Console::WriteLine("type information for 'String':");
Type ^ t = String::typeid;
String ^ assemblyName = t->Assembly->FullName;
Console::WriteLine("assembly name: {0}", assemblyName);
String ^ nameSpace = t->Namespace;
Console::WriteLine("namespace: {0}", nameSpace);
String ^ baseType = t->BaseType->FullName;
Console::WriteLine("base type: {0}", baseType);
bool isArray = t->IsArray;
Console::WriteLine("is array: {0}", isArray);
bool isClass = t->IsClass;
Console::WriteLine("is class: {0}", isClass);
}
type information for 'String':
assembly name: mscorlib, Version=1.0.5000.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089
namespace: System
base type: System.Object
is array: False
is class: True
Příklad: výčet typů
Reflexe také umožňuje výčet typů v rámci sestavení a členů v rámci tříd. Pokud chcete tuto funkci předvést, definujte jednoduchou třídu:
// vcpp_reflection_5.cpp
// compile with: /clr /LD
using namespace System;
public ref class TestClass {
int m_i;
public:
TestClass() {}
void SimpleTestMember1() {}
String ^ SimpleMember2(String ^ s) { return s; }
int TestMember(int i) { return i; }
property int Member {
int get() { return m_i; }
void set(int i) { m_i = i; }
}
};
Příklad: kontrola sestavení
Pokud je výše uvedený kód zkompilován do knihovny DLL s názvem vcpp_reflection_6.dll, můžete pomocí reflexe zkontrolovat obsah tohoto sestavení. To zahrnuje použití funkce rozhraní API statické reflexe xref:System.Reflection.Assembly.Load%2A?displayProperty=nameWithType k načtení sestavení. Tato funkce vrátí adresu objektu Assembly , který se pak dá dotazovat na moduly a typy v rámci.
Jakmile systém reflexe úspěšně načte sestavení, načte se pomocí funkce pole objektů typu.Assembly.GetTypes Každý prvek pole obsahuje informace o jiném typu, i když v tomto případě je definována pouze jedna třída. Pomocí smyčky se každý typ v tomto poli dotazuje na členy typu pomocí funkce Type::GetMembers . Tato funkce vrátí pole MethodInfo objektů, každý objekt obsahující informace o členské funkci, datovém členu nebo vlastnosti v typu.
Všimněte si, že seznam metod zahrnuje funkce explicitně definované v TestClass a funkce implicitně zděděné ze Třídy System::Object . Jako součást popisu v .NET místo v syntaxi Visual C++ se vlastnosti zobrazují jako podkladový datový člen, ke kterého přistupuje funkce get/set. Funkce get/set se v tomto seznamu zobrazují jako běžné metody. Reflexe je podporována prostřednictvím modulu CLR (Common Language Runtime), nikoli kompilátorem jazyka Microsoft C++.
I když jste tento kód použili ke kontrole sestavení, které jste definovali, můžete tento kód použít také ke kontrole sestavení .NET. Pokud například změníte TestAssembly na mscorlib, zobrazí se seznam všech typů a metod definovaných v mscorlib.dll.
// vcpp_reflection_6.cpp
// compile with: /clr
using namespace System;
using namespace System::IO;
using namespace System::Reflection;
int main() {
Assembly ^ a = nullptr;
try {
// load assembly -- do not use file extension
// will look for .dll extension first
// then .exe with the filename
a = Assembly::Load("vcpp_reflection_5");
}
catch (FileNotFoundException ^ e) {
Console::WriteLine(e->Message);
return -1;
}
Console::WriteLine("assembly info:");
Console::WriteLine(a->FullName);
array<Type^>^ typeArray = a->GetTypes();
Console::WriteLine("type info ({0} types):", typeArray->Length);
int totalTypes = 0;
int totalMembers = 0;
for (int i = 0 ; i < typeArray->Length ; i++) {
// retrieve array of member descriptions
array<MemberInfo^>^ member = typeArray[i]->GetMembers();
Console::WriteLine(" members of {0} ({1} members):",
typeArray[i]->FullName, member->Length);
for (int j = 0 ; j < member->Length ; j++) {
Console::Write(" ({0})",
member[j]->MemberType.ToString() );
Console::Write("{0} ", member[j]);
Console::WriteLine("");
totalMembers++;
}
totalTypes++;
}
Console::WriteLine("{0} total types, {1} total members",
totalTypes, totalMembers);
}
Postupy: Implementace architektury komponent plug-in pomocí reflexe
Následující příklady kódu ukazují použití reflexe k implementaci jednoduché architektury "plug-in". První výpis je aplikace a druhý je modul plug-in. Aplikace je vícedokument formuláře, který se naplní všemi třídami založenými na formuláři nalezené v knihovně DLL modulu plug-in poskytnuté jako argument příkazového řádku.
Aplikace se pokusí načíst zadané sestavení pomocí System.Reflection.Assembly.Load metody. V případě úspěchu se typy uvnitř sestavení vyčíslí pomocí System.Reflection.Assembly.GetTypes metody. Každý typ se pak pomocí metody zkontroluje z důvodu kompatibility System.Type.IsAssignableFrom . V tomto příkladu musí být třídy nalezené v poskytnutém sestavení odvozeny od Form třídy, aby bylo možné kvalifikovat jako modul plug-in.
Kompatibilní třídy se pak vytvoří instance s metodou System.Activator.CreateInstance , která přijímá Type jako argument a vrací ukazatel na novou instanci. Každá nová instance se pak připojí k formuláři a zobrazí se.
Všimněte si, že Load metoda nepřijímá názvy sestavení, které obsahují příponu souboru. Hlavní funkce v aplikaci oříznou všechna poskytnutá rozšíření, takže následující příklad kódu funguje v obou případech.
Ukázková aplikace
Následující kód definuje aplikaci, která přijímá moduly plug-in. Jako první argument musí být uveden název sestavení. Toto sestavení by mělo obsahovat alespoň jeden veřejný Form odvozený typ.
// plugin_application.cpp
// compile with: /clr /c
#using <system.dll>
#using <system.drawing.dll>
#using <system.windows.forms.dll>
using namespace System;
using namespace System::Windows::Forms;
using namespace System::Reflection;
ref class PluggableForm : public Form {
public:
PluggableForm() {}
PluggableForm(Assembly^ plugAssembly) {
Text = "plug-in example";
Size = Drawing::Size(400, 400);
IsMdiContainer = true;
array<Type^>^ types = plugAssembly->GetTypes( );
Type^ formType = Form::typeid;
for (int i = 0 ; i < types->Length ; i++) {
if (formType->IsAssignableFrom(types[i])) {
// Create an instance given the type description.
Form^ f = dynamic_cast<Form^> (Activator::CreateInstance(types[i]));
if (f) {
f->Text = types[i]->ToString();
f->MdiParent = this;
f->Show();
}
}
}
}
};
int main() {
Assembly^ a = Assembly::LoadFrom("plugin_application.exe");
Application::Run(gcnew PluggableForm(a));
}
Příklady modulů plug-in
Následující kód definuje tři třídy odvozené z Form. Když se název výsledného názvu sestavení předá spustitelnému souboru v předchozím výpisu, každá z těchto tří tříd bude zjištěna a vytvořena instance, i když byly všechny neznámé hostitelské aplikaci v době kompilace.
// plugin_assembly.cpp
// compile with: /clr /LD
#using <system.dll>
#using <system.drawing.dll>
#using <system.windows.forms.dll>
using namespace System;
using namespace System::Windows::Forms;
using namespace System::Reflection;
using namespace System::Drawing;
public ref class BlueForm : public Form {
public:
BlueForm() {
BackColor = Color::Blue;
}
};
public ref class CircleForm : public Form {
protected:
virtual void OnPaint(PaintEventArgs^ args) override {
args->Graphics->FillEllipse(Brushes::Green, ClientRectangle);
}
};
public ref class StarburstForm : public Form {
public:
StarburstForm(){
BackColor = Color::Black;
}
protected:
virtual void OnPaint(PaintEventArgs^ args) override {
Pen^ p = gcnew Pen(Color::Red, 2);
Random^ r = gcnew Random( );
Int32 w = ClientSize.Width;
Int32 h = ClientSize.Height;
for (int i=0; i<100; i++) {
float x1 = w / 2;
float y1 = h / 2;
float x2 = r->Next(w);
float y2 = r->Next(h);
args->Graphics->DrawLine(p, x1, y1, x2, y2);
}
}
};
Postupy: Vytvoření výčtu datových typů v sestaveních pomocí reflexe
Následující kód ukazuje výčet veřejných typů a členů pomocí System.Reflection.
Vzhledem k názvu sestavení v místním adresáři nebo v GAC se následující kód pokusí otevřít sestavení a načíst popisy. V případě úspěchu se každý typ zobrazí se svými veřejnými členy.
Upozorňujeme, že System.Reflection.Assembly.Load nevyžaduje použití žádné přípony souboru. Proto použití "mscorlib.dll" jako argument příkazového řádku selže, zatímco použití pouze mscorlib způsobí zobrazení typů rozhraní .NET Framework. Pokud není zadaný žádný název sestavení, kód rozpozná a oznámí typy v aktuálním sestavení (exe, který je výsledkem tohoto kódu).
Příklad
// self_reflection.cpp
// compile with: /clr
using namespace System;
using namespace System::Reflection;
using namespace System::Collections;
public ref class ExampleType {
public:
ExampleType() {}
void Func() {}
};
int main() {
String^ delimStr = " ";
array<Char>^ delimiter = delimStr->ToCharArray( );
array<String^>^ args = Environment::CommandLine->Split( delimiter );
// replace "self_reflection.exe" with an assembly from either the local
// directory or the GAC
Assembly^ a = Assembly::LoadFrom("self_reflection.exe");
Console::WriteLine(a);
int count = 0;
array<Type^>^ types = a->GetTypes();
IEnumerator^ typeIter = types->GetEnumerator();
while ( typeIter->MoveNext() ) {
Type^ t = dynamic_cast<Type^>(typeIter->Current);
Console::WriteLine(" {0}", t->ToString());
array<MemberInfo^>^ members = t->GetMembers();
IEnumerator^ memberIter = members->GetEnumerator();
while ( memberIter->MoveNext() ) {
MemberInfo^ mi = dynamic_cast<MemberInfo^>(memberIter->Current);
Console::Write(" {0}", mi->ToString( ) );
if (mi->MemberType == MemberTypes::Constructor)
Console::Write(" (constructor)");
Console::WriteLine();
}
count++;
}
Console::WriteLine("{0} types found", count);
}