トレーニング
リフレクションの問題 (C++/CLI)
リフレクションでは、既知のデータ型を実行時に調査できます。 リフレクションでは、アセンブリに含まれているデータ型を列挙したり、クラス型や値型のメンバーを探索したりできます。 この処理は、コンパイル時に型が認識または参照された場合のどちらにも対応できます。 そのため、リフレクションは、開発およびコード管理ツール向けの便利な機能です。
出力されたアセンブリ名は、アセンブリのバージョン、カルチャ、および署名情報が含まれた厳密な名前です。詳細については、「厳密な名前付きアセンブリの作成と使用」を参照してください。 また、基底クラスの名前に加えて、データ型が定義されている名前空間の名前も取得できることに注目してください。
リフレクションの機能にアクセスする最も一般的な方法は、GetType メソッドを使用することです。 このメソッドは、すべてのガベージ コレクション クラスの派生元である System.Object に用意されています。
注意
Microsoft C++ コンパイラで構築された .exe の場合は、その .exe が /clr:pure または /clr:safe コンパイラ オプションを使用して構築された場合にのみ、リフレクションを使用できます。 /clr:pure および /clr:safe コンパイラ オプションは Visual Studio 2015 では非推奨とされており、Visual Studio 2017 では使用できません。 詳細については、「/clr (共通言語ランタイムのコンパイル)」を参照してください。
詳細については、System.Reflection を参照してください
GetType
メソッドは、オブジェクトが基になっている場合は型を表す Type クラス オブジェクトへのポインターを返します。 ( Type オブジェクトにはインスタンス固有の情報は含まれません)。このような項目の 1 つは型の完全な名前であり、次のように表示できます。
型名には、型が定義されている完全なスコープ (名前空間も含む) が含まれています。また、.NET 構文でスコープ解決演算子としてドット (.) が付けられている点にも注意してください。
// 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'
値型を GetType
関数で使用する場合も、最初にボックス化する必要があります。
// 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'
GetType
メソッドの場合と同様、typeid 演算子は Type オブジェクトへのポインターを返します。したがって、このコードでは System.Int32 という型名を指定します。 型名を表示することは、リフレクションの最も基本的な機能ですが、より便利であると思われるのが、列挙型の有効な値を調査または探索できる点です。 静的関数 Enum::GetNames を使用すると、それぞれがテキスト形式の列挙値を含む文字列の配列が返されます。 次のサンプルは、Options (CLR) 列挙型の値の列挙値を表す文字列の配列を取得し、ループ処理で結果を表示します。
Options 列挙型に 4 つ目のオプションを追加する場合は、この列挙型が別のアセンブリに定義されている場合でも、再コンパイルせずにこのコードから新しいオプションが報告されます。
// 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
GetType
オブジェクトは、型を調べるために使用できる多くのメンバーとプロパティをサポートします。 次のコードは、この情報の一部を取得して表示します。
// 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
リフレクションを使用すると、アセンブリ内の型とクラス内のメンバーも列挙できます。 この機能の例を示すために、次のような単純なクラスを定義します。
// 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; }
}
};
このコードを vcpp_reflection_6.dll という DLL にコンパイルすると、リフレクションを使用してアセンブリの内容を確認できます。 この処理を行うには、静的リフレクション API 関数 xref:System.Reflection.Assembly.Load%2A?displayProperty=nameWithType を使用してアセンブリを読み込みます。 この関数が Assembly オブジェクトのアドレスを返すことによって、そのオブジェクト内のモジュールや型の問い合わせができるようになります。
一度、リフレクション システムがアセンブリを正常に読み込むと、Assembly.GetTypes 関数が Type オブジェクトの配列を取得します。 各配列要素には、異なる型の情報が含まれています。ただし、この例で定義されているクラスは 1 つだけです。 ループを使用して、この配列内の各 Type に対して Type::GetMembers 関数で型のメンバーを問い合わせます。 この関数は、MethodInfo オブジェクトの配列を返します。各配列要素には、メンバー関数、データ メンバー、または型のプロパティに関する情報が含まれています。
メソッドのリストには、TestClass で明示的に定義された関数と、System::Object クラスから暗黙的に継承された関数が含まれていることに注意してください。 Visual C++ 構文ではなく .NET で記述されている部分では、プロパティは get 関数と set 関数でアクセスする基底のデータ メンバーとして表示されます。 get 関数と set 関数は、通常のメソッドとしてこのリストに表示されています。 リフレクションは、Microsoft C++ コンパイラではなく、共通言語ランタイムを通じてサポートされています。
このコードを使用すると、定義したアセンブリを調べるだけでなく、.NET アセンブリを調べることもできます。 たとえば、TestAssembly を mscorlib に置き換えると、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);
}
以下のコード例では、リフレクションを使用して単純な "プラグイン" アーキテクチャを実装する方法を示します。 最初に示すのはアプリケーションで、2 つ目はプラグインです。 このアプリケーションは、コマンド ライン引数として指定されたプラグイン DLL 内のフォーム ベースのクラスを使用して自身を設定する、マルチ ドキュメント フォームです。
このアプリケーションは、System.Reflection.Assembly.Load メソッドを使用して、指定されたアセンブリの読み込みを試行します。 成功した場合は、System.Reflection.Assembly.GetTypes メソッドを使用してアセンブリ内の型が列挙されます。 その後、System.Type.IsAssignableFrom メソッドを使用して、各型の互換性がチェックされます。 この例では、指定されたアセンブリ内のクラスが、Form クラスから派生している必要があります。そうでないと、プラグインとして扱われません。
その後、互換性のあるクラスは System.Activator.CreateInstance メソッドを使用してインスタンス化されます。このメソッドは、Type を引数として受け取り、新しいインスタンスへのポインターを返します。 その後、新しい各インスタンスがフォームにアタッチされ、表示されます。
Load メソッドでは、ファイル拡張子を含んでいるアセンブリ名は受け入れられません。 アプリケーションの main 関数によって、指定された拡張子がトリミングされます。そのため、次のコード例はどちらの場合にも機能します。
次のコードでは、プラグインを受け入れるアプリケーションを定義しています。アセンブリ名を、最初の引数として指定する必要があります。 このアセンブリには、少なくとも 1 つのパブリック Form 派生型が含まれている必要があります。
// 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));
}
次のコードでは、Form から派生する 3 つのクラスを定義しています。 結果のアセンブリ名が上記の例の実行可能ファイルに渡されると、これら 3 つの各クラスは、コンパイル時にホスティング アプリケーションに対して不明であったにもかかわらず、検出されてインスタンス化されます。
// 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);
}
}
};
次のコードは、System.Reflection を使用してパブリック型とメンバーを列挙する方法を示したものです。
ローカル ディレクトリまたは GAC 内のアセンブリの名前を指定すると、次のコードはアセンブリを開いて説明を取得しようとします。 成功した場合は、各型がパブリック メンバーと一緒に表示されます。
System.Reflection.Assembly.Load に対しては、ファイル拡張子を使用できません。 そのため、コマンド ライン引数として "mscorlib.dll" を使用すると失敗します。"mscorlib" を使用すると、.NET Framework 型が表示されます。 アセンブリ名が指定されていない場合、コードは現在のアセンブリ (このコードから生成された EXE) 内の型を検出し、報告します。
// 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);
}