리플렉션(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

메서드는 GetType 클래스 개체에 대한 포인터를 Type 반환하며, 개체의 기반이 되는 경우의 형식을 설명합니다. (다음 항목형식 개체에는 인스턴스별 정보가 없습니다.) 이러한 항목 중 하나는 다음과 같이 표시할 수 있는 형식의 전체 이름입니다.

형식 이름에는 네임스페이스를 포함하여 형식이 정의된 전체 범위가 포함되며 범위 확인 연산자로 점이 있는 .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'

예: boxed 값 형식

값 형식도 함수와 GetType 함께 사용할 수 있지만 먼저 boxed해야 합니다.

// 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'

예: typeid

메서드와 GetType 마찬가지로 typeid 연산자는 Type 개체에 대한 포인터를 반환하므로 이 코드는 System.Int32 형식 이름을 나타냅니다. 형식 이름을 표시하는 것이 리플렉션의 가장 기본적인 기능이지만, 잠재적으로 더 유용한 방법은 열거형 형식에 대한 유효한 값을 검사하거나 검색하는 것입니다. 이 작업은 각각 텍스트 형식의 열거형 값을 포함하는 문자열 배열을 반환하는 정적 Enum::GetNames 함수를 사용하여 수행할 수 있습니다. 다음 샘플에서는 CLR(Options) 열거형의 값 열거 값을 설명하는 문자열 배열을 검색하여 루프에 표시합니다.

옵션 열거형에 네 번째 옵션이 추가되면 이 코드는 열거형이 별도의 어셈블리에 정의되어 있더라도 다시 컴파일하지 않고 새 옵션을 보고합니다.

// 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 멤버 및 속성

개체는 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을 사용하여 어셈블리를 로드하는 작업이 포함됩니다. 이 함수는 어셈블리 개체의 주소를 반환하며, 이 주소는 내의 모듈 및 형식에 대해 쿼리할 수 있습니다.

리플렉션 시스템이 어셈블리를 성공적으로 로드하면 함수와 함께 Type 개체의 배열이 Assembly.GetTypes 검색됩니다. 각 배열 요소에는 다른 형식에 대한 정보가 포함되어 있지만 이 경우 하나의 클래스만 정의됩니다. 루프를 사용하여 이 배열의 각 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);
}

방법: 리플렉션을 사용하여 플러그 인 구성 요소 아키텍처 구현

다음 코드 예제에서는 리플렉션을 사용하여 간단한 "플러그 인" 아키텍처를 구현하는 방법을 보여 줍니다. 첫 번째 목록은 애플리케이션이고, 두 번째 목록은 플러그 인입니다. 애플리케이션은 명령줄 인수로 제공된 플러그 인 DLL에 있는 양식 기반 클래스를 사용하여 자신을 채우는 여러 문서 양식입니다.

애플리케이션이 메서드를 사용하여 제공된 어셈블리를 로드하려고 시도합니다 System.Reflection.Assembly.Load . 성공하면 어셈블리 내의 형식은 메서드를 System.Reflection.Assembly.GetTypes 사용하여 열거됩니다. 각 형식은 메서드를 사용하여 System.Type.IsAssignableFrom 호환성을 위해 검사. 이 예제에서는 제공된 어셈블리에 있는 클래스를 플러그 인으로 한정하려면 클래스에서 Form 파생되어야 합니다.

호환되는 클래스는 인수로 수락하고 새 인스턴스에 System.Activator.CreateInstance 대한 포인터를 Type 반환하는 메서드로 인스턴스화됩니다. 그러면 각 새 인스턴스가 양식에 연결되고 표시됩니다.

메서드는 Load 파일 확장명을 포함하는 어셈블리 이름을 허용하지 않습니다. 애플리케이션의 기본 함수는 제공된 모든 확장을 트리밍하므로 다음 코드 예제는 두 경우 모두 작동합니다.

예제 앱

다음 코드는 플러그 인을 허용하는 애플리케이션을 정의합니다. 어셈블리 이름은 첫 번째 인수로 제공되어야 합니다. 이 어셈블리에는 하나 이상의 공용 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파생된 세 가지 클래스를 정의합니다. 결과 어셈블리 이름의 이름이 이전 목록의 실행 파일에 전달되면 컴파일 시간에 호스팅 애플리케이션에 모두 알 수 없음에도 불구하고 이러한 세 클래스가 각각 검색되고 인스턴스화됩니다.

// 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);
}

참고 항목