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 lze zjistit členy dané třídy nebo typu hodnoty. To platí bez ohledu na to, jestli byl typ znám nebo odkazován v době kompilace. Díky tomu je reflexe užitečnou funkcí pro nástroje pro vývoj a správu kódu.
Všimněte si, že poskytnutý název sestavení je silný název (viz Vytváření a používání sestavení Strong-Named), který obsahuje informace o verzi sestavení, jazykové verzi a podepisování. Všimněte si také, že lze načíst název oboru názvů, ve kterém je definován datový typ, spolu s názvem základní třídy.
Nejběžnější způsob přístupu k funkcím reflexe je prostřednictvím metody GetType. Tuto metodu poskytuje System.Object, ze které jsou odvozeny všechny třídy uvolněné z paměti.
Poznámka
Reflexe na .exe sestavená pomocí kompilátoru Microsoft C++ je povolena pouze v případě, že je .exe sestaven pomocí možností kompilátoru /clr:pure nebo /clr:safe. Možnosti kompilátoru /clr:pure a /clr:safe jsou v Visual Studio 2015 zastaralé a v Visual Studio 2017 nejsou k dispozici. Další informace najdete v tématu /clr (kompilace common language runtime).
Další informace najdete v tématu System.Reflection.
Příklad: GetType
Metoda GetType vrací ukazatel na objekt třídy GetType který popisuje typ při jeho použití. (Objekt Type neobsahuje žádné informace specifické pro instanci.) Jednou z takových položek 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 je zobrazen 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
Typy hodnot lze použít také s GetType funkcí , ale musí být nejprve zašedlované.
// 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 metody vrací operátor typeid ukazatel na objekt Type, takže tento kód označuje název typu GetTypeSystem.Int32. GetType Zobrazení názvů typů je nejzákladnější funkcí reflexe, ale potenciálně užitečnější technikou je kontrola nebo zjištění platných hodnot 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ém formátu. Následující ukázka načte pole řetězců, které popisuje 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: Členy a vlastnosti GetType
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 členy v rámci tříd. Pro předvedení této funkce 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í. K načtení sestavení to zahrnuje použití funkce rozhraní API statické reflexe xref:System.Reflection.Assembly.Load%2A?displayProperty=nameWithType. Tato funkce vrátí adresu objektu sestavení, na který lze poté dotazovat moduly a typy v rámci.
Jakmile systém reflexe úspěšně načte sestavení, načte se pole objektů Type pomocí funkce 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 objektů MethodInfo, z nichž každý obsahuje informace o členské funkci, datovém členu nebo vlastnosti typu.
Všimněte si, že seznam metod obsahuje funkce explicitně definované v třídě TestClass a funkce implicitně zděděné z třídy System::Object. V rámci popisu v .NET místo v Visual C++ syntaxe se vlastnosti zobrazují jako podkladový datový člen, ke kterým přistupují 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 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 souboru 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 Plug-In komponent pomocí reflexe
Následující příklady kódu ukazují použití reflexe k implementaci jednoduché architektury "modulu plug-in". Prvním výpisem je aplikace a druhým je modul plug-in. Aplikace je formulář více dokumentů, který se naplní pomocí všech tříd založených na formulářích, které se nacházejí v knihovně DLL modulu plug-in poskytované jako argument příkazového řádku.
Aplikace se pokusí načíst poskytnuté sestavení pomocí metody System.Reflection.Assembly.Load. V případě úspěchu jsou typy uvnitř sestavení výčtu pomocí metody System.Reflection.Assembly.GetTypes. U každého typu se pak kontroluje kompatibilita pomocí metody System.Type.IsAssignableFrom. V tomto příkladu musí být třídy nalezené v poskytnutém sestavení odvozeny z třídy Form, aby byly kvalifikovány jako modul plug-in.
Kompatibilní třídy se pak vytvoří pomocí metody 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 metoda Load nepřijímá názvy sestavení, které obsahují příponu souboru. Hlavní funkce v aplikaci oříznutí všech poskytnutých rozšíření, takže následující příklad kódu funguje v obou případech.
Příklad aplikace
Následující kód definuje aplikaci, která přijímá moduly plug-in. Název sestavení musí být poskytnut jako první argument. Toto sestavení by mělo obsahovat alespoň jeden odvozený typ veřejného formuláře.
// 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 formuláře. Když se název výsledného sestavení předá spustitelnému souboru v předchozím výpisu, bude zjištěna a vytvořena instance každé z těchto tří tříd, a to bez ohledu na to, že všechny byly v době kompilace pro hostující aplikaci neznámé.
// 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í, buď 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.
System.Reflection.Assembly.Load vyžaduje, aby nebyla použita žádná přípona souboru. Proto použití "mscorlib.dll" jako argumentu příkazového řádku selže, zatímco použití pouze "mscorlib" bude mít za následek zobrazení .NET Framework typů. Pokud není poskytnut žádný název sestavení, kód zjistí a nahlásit 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);
}