A Reflection Example
The example code is contained in a solution named ReflectionExample. This program loads an assembly named MyClassLibrary.dll and writes out some of its metadata to a file named output.txt. You can examine the source code and output to see how it works. Of course, you should also look at the source code of the target assembly that is being analyzed, to see how the type information obtained via reflection corresponds to the actual type information defined in the target assembly. The target assembly solution, written in C#, is named MyClassLibrary. Because the amount of metadata written is large, only a small part is shown in this article; please run the provided solution and view the entire metadata output file, comparing it to the code contained in the ReflectionExample.cpp and MyClassLibrary.cs source files.
There is much more that such a program could do. Try writing your own program that shows other metadata information that is not described in this article. If you are feeling frisky, you may even want to try experimenting with the classes contained in the System.Reflection.Emit namespace to generate code dynamically. Sophisticated tools, such as compilers and script engines, use these classes. You may even want to experiment with genetic algorithms, but that would involve significant time and effort.
Listing 1 gives the source code for the target assembly that we will be analyzing via reflection. Only one class is shown here, to conserve space. The actual sample code contains several classes, as well as an interface, a delegate, and a structure. This example happens to be written in C#, but it could have been written in any .NET language, including Visual C++ with managed extension, or VB.NET.
Listing 1: The Target Assembly
//MyClassLibrary.cs using System; using System.Reflection; //examples of assembly attributes [assembly:AssemblyCopyright( "Copyright (c) 2001, Fictitious Company Corporation")] [assembly:AssemblyCompany( "Fictitious Company Corporation")] [assembly:AssemblyProduct("Reflection Example Class Library")] namespace ClassLibrary1 { [SerializableAttribute()] public class Class1 { private const float pi = 3.141592F; public Class1() { } [ObsoleteAttribute("No workaround available")] public void Method1A() { } internal void Method1B() { } protected void Method1C() { } private void Method1D() { } public static void Method1E() { } [MTAThreadAttribute()] public int Method1F(int i, int j) { return i + j; } } ... }
Now we will shift our focus to the source code for the ReflectionExample program that analyzes the target assembly via reflection. The program needs to use classes in several namespaces, including System::Reflection:
//ReflectionExample.cpp #using <mscorlib.dll> using namespace System; using namespace System::IO; using namespace System::Reflection; ...
First, we load the assembly into memory by calling the static method Assembly::LoadFrom:
Assembly *assembly; try { assembly = Assembly::LoadFrom( "C:\\...\\MyClassLibrary\\bin\\Debug\\MyClassLibrary.dll"); } catch(Exception *e) { ... }
The example then discovers general information about the assembly as a whole. Note that standard output is redirected to the disk file by a call to Console::SetOut in the main function (not shown here). The code that generates this general assembly information is contained in a method named ShowAssemblyInfo (see Listing 2).
Listing 2: The ShowAssemblyInfo Method
static void ShowAssemblyInfo(Assembly *assembly) { Console::WriteLine( "Assembly: {0}", assembly->FullName); Console::WriteLine(); Object *attribs [] = assembly->GetCustomAttributes(false); for(int i=0; i<attribs->Length; i++) { Type *typeAttrib = attribs[i]->GetType(); if (typeAttrib == Type::GetType( "System.Reflection.AssemblyCopyrightAttribute")) Console::WriteLine( "AssemblyCopyright: {0}", dynamic_cast<AssemblyCopyrightAttribute *> (attribs[i])->Copyright); if (typeAttrib == Type::GetType( "System.Reflection.AssemblyCompanyAttribute")) Console::WriteLine( "AssemblyCompanyAttribute: {0}", dynamic_cast<AssemblyCompanyAttribute *> (attribs[i])->Company); if (typeAttrib == Type::GetType( "System.Reflection.AssemblyProductAttribute")) Console::WriteLine( "AssemblyProductAttribute: {0}", dynamic_cast<AssemblyProductAttribute *> (attribs[i])->Product); } Console::WriteLine(); }
The ShowAssemblyInfo method writes the full name of the assembly, which includes the simple name, version, culture, and public key token (that is, the digital signature), followed by some assembly attribute information to the output.txt file. Because the assembly was not digitally signed, PublicKeyToken is null. The ShowAssemblyInfo method then writes out details on the three attributes for the product name, company name, and copyright to the output.txt file.
Assembly: MyClassLibrary, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null AssemblyProductAttribute: Reflection Example Class Library AssemblyCompanyAttribute: Fictitious Company Corporation AssemblyCopyright: Copyright (c) 2001, Fictitious Company Corporation
In the ShowTypeInfo method, we use the assembly's GetTypes method to return an array of the assembly's types. Because there are no functions outside of classes and there are no global variables in .NET programs, getting all the types in an assembly will allow us to get all the metadata for all the code in the assembly. The Type class represents all the types possible in a .NET program, including classes, structs, interfaces, values, arrays, and enumerations. For each type found, the ShowTypeInfo method displays the name of the type as well as the name of its base type, followed by the attributes applied to each of the types. It then calls out to other methods that drill down further: ShowMethodInfo, ShowFieldInfo, ShowPropertyInfo, and ShowEventInfo.
static void ShowTypeInfo(Assembly *assembly) { Type *types [] = assembly->GetTypes(); for(int i=0; i<types->Length; i++) { Console::WriteLine("Type"); Console::WriteLine( " Name: {0}", types[i]->Name); if(types[i]->BaseType != 0) Console::WriteLine( " BaseType: {0}", types[i]->BaseType->Name); else Console::WriteLine(" BaseType: None"); Console::WriteLine( " Attributes: {0,8:X}", ExplodeTypeAttributes(types[i]->Attributes)); ShowMethodInfo(types[i]); ShowFieldInfo(types[i]); ShowPropertyInfo(types[i]); ShowEventInfo(types[i]); } }
The ShowMethodInfo method is shown in Listing 3. The Type::GetMethods method overload used here takes a BindingFlags parameter to control which methods we are actually interested in. For each method found, the method return type, method name, and method attributes are displayed. The ShowParameterInfo method is then called to drill down further into the details of each of the method's parameters.
Listing 3: The ShowMethodInfo Method
static void ShowMethodInfo(Type *type) { MethodInfo *methods [] = type->GetMethods( (BindingFlags)( BindingFlags::Instance | BindingFlags::Static | BindingFlags::Public | BindingFlags::NonPublic)); for(int i=0; i<methods->Length; i++) { Console::WriteLine(" Method"); Console::WriteLine( " ReturnType: {0}", methods[i]->ReturnType); Console::WriteLine( " Name: {0}", methods[i]->Name); Console::WriteLine( " Attributes: {0,8:X}", ExplodeMethodAttributes( methods[i]->Attributes)); ShowParameterInfo(methods[i]); } }
The other methods, ShowFieldInfo, ShowPropertyInfo, and ShowEventInfo, are not shown here. They work in similar ways to the ShowMethodInfo just described, but you might want to study them in the solution code provided to better understand their operation.
The data shown in Listing 4 is only a small part of the reflection information written to output.txt. Please see the actual output.txt file for the full story.
Listing 4: Reflection Program Output
Type Name: Class1 BaseType: Object Attributes: BeforeFieldInit NestedAssembly NestedFamORAssem NestedPrivate Public Serializable VisibilityMask Method ReturnType: System.Void Name: Finalize Attributes: Family FamORAssem HideBySig MemberAccessMask Public Virtual Method ReturnType: System.Int32 Name: GetHashCode Attributes: Assembly FamANDAssem Family FamORAssem HideBySig MemberAccessMask NewSlot Public Virtual VtableLayoutMask Method ReturnType: System.Boolean Name: Equals Attributes: Assembly FamANDAssem Family FamORAssem HideBySig MemberAccessMask NewSlot Public Virtual VtableLayoutMask Parameter Type: System.Object Name: obj Attribute: 00000000 Method ReturnType: System.String Name: ToString Attributes: Assembly FamANDAssem Family FamORAssem HideBySig MemberAccessMask NewSlot Public Virtual VtableLayoutMask Method ReturnType: System.Void Name: Method1A Attributes: Assembly FamANDAssem Family FamORAssem HideBySig ...