Ways to use .Net Reflector #3: Wrap it

by Jason Haley 26. October 2007 15:28

In this entry you will learn how to create a quick and dirty Reflector knock off by wrapping it.  The purpose of creating such an application is to get a better understanding of how the different objects in the Reflector.CodeModel namespace interact to create the output of a disassembled assembly. 

NOTE: The wrapping of Reflector is NOT supported and NOT recommended for anything other than learning purposes, like this entry.

Download the Code

Background
In writing the code for the Enums Addin's Enum Viewer control, I did quite a bit of poking around the ReflectorAddIns source code.  One day I noticed line 15 in the CodeModelExample.cs class:

IServiceProvider serviceProvider = new ApplicationManager(null);

IServiceProvider (in case you don't know) is where all addins get their services to access Reflector functionality.  With addins, ServiceProvider is passed as a parameter in the Load() method of the IPackage interface that all addins implement - but if you can create an instance from the ApplicationManager ... that means you can use Reflector as a library to build your own disassembler UI.  Of course who would bother with doing that?  However, if you want a good understanding of the how Reflector uses the different objects (and interfaces) in the Reflector.CodeModel namespace to create the disassembler output - creating such an application should help you understand what goes on under the hood.

The functionality of the sample Reflector Wrapper application is limited, since the main point is to get a better understanding of the objects necessary to disassemble an assembly and to write out the disassembly text to the UI - not create a full blown Reflector knock off.  The sample application you will learn how to build, is a very simple WinForms application with the following functionality:

  • Allows the user to load an assembly
  • Disassemble and translate it to in any of the languages shipped with Reflector
  • View a dump of the CodeModel (a listing of all the types, methods, fields, IL, etc.)
  • If the user changes the language choice, show the disassembly in the newly chosen language

image

Prerequisites
Before starting, you need to download the latest ReflectorAddins source code from the CodePlex site.  Once you have the code downloaded, extract the zip somewhere on your drive.  We will be using some of the code from the CodeModelExample and ClassViewer projects.

Getting the Project Started
Open Visual studio and create a new WinForms application with a single form and set a reference to Reflector.exe.  In the sample code included with this entry, I've created a Form like the one shown below.  The File menu has Open and Exit menu items.  Besides the File menu, there is a ComboBox (for the languages), 2 Buttons and a RichTextBox (for the disassembler output.image  As you can see, the code for the event handlers is pretty simple, the majority of the logic is in he ShowCodeModel() and Disassemble() methods.

			private void btnCodeModel_Click(object sender, EventArgs e)
			
			{
			
			    ShowCodeModel();
			
			}
			
			 
			
			private void btnDisassemble_Click(object sender, EventArgs e)
			
			{
			
			    Disassemble();
			
			}
			
			 
			
			private void cboLanguages_SelectedIndexChanged(object sender, EventArgs e)
			
			{
			
			    Disassemble();
			
			}
			
			 
			
			private void exitToolStripMenuItem_Click(object sender, EventArgs e)
			
			{
			
			    Application.Exit();
			
			}
			
			 
			
			private void openToolStripMenuItem_Click(object sender, EventArgs e)
			
			{
			
			    OpenFileDialog ofd = new OpenFileDialog();
			
			    if (ofd.ShowDialog() == DialogResult.OK)
			
			    {
			
			        currentAssemblyPath = ofd.FileName;
			
			    }
			
			}
			

At this point the project won't compile due to some things missing, so go ahead and stub out the ShowCodeModel() and Disassmble() methods.   You'll also need to add the following using statements and member variables:

using Reflector;
using Reflector.CodeModel;
 
IServiceProvider serviceProvider;
ILanguageManager languageManager;
string currentAssemblyPath;

Now the application should compile ... but of course it doesn't do anything yet.  So just to get things going, add the following to the form's constructor:

serviceProvider = new ApplicationManager(null);
languageManager = (ILanguageManager)serviceProvider.GetService(typeof(ILanguageManager));
 
List<string> languageNames = new List<string>();
foreach (ILanguage language in languageManager.Languages)
{
    languageNames.Add(language.Name);
}
this.cboLanguages.DataSource = languageNames;

Now you should be able to start debugging the application and see the languages combo box populated with the language translators that we will be able to use from Reflector. 

I won't go into what ApplicationManager does - because I don't know ... it's obfuscated, but it does implement System.IServiceProvider - which is all we need to know.  As you can see from the image below, the IServiceProvider only has one method: GetService().  Once we have a reference to an imageIServiceProviderimage implementation, we can access the services that Reflector offers to its addins.  In order to get a listing of languages offered by Reflector (or registered into Reflector), you need to use the Reflector.ILanguageManager interface. If you look through the ReflectorsAddin solution code from the CodePlex site, that you downloaded earlier, you will see a few implementations of ILanguage (DelphiLanguage, MCppLanguage, PowerShellLanguagae and Xaml) - these are great reference implementations for you to look at if you feel up to the challenge of writing your own ILanguage implementation (that means you'll have to implement an implementation of ILanguageWriter too - which is a pretty hefty job).  The RegisterLanuage() and UnRegisterLanguage() methods are used by language addins, so I won't mention them in this entry.  The ActiveLanguage property does exactly what it sounds like, returns the ILanguage implementation of the active language (which I didn't use in the sample application - I handled that with the language combo box).  The Languages property, supplies a collection of all the currently registered Languages in the service provider ... which is all we are interested in for this blog entry.  If you had a language you wanted loaded that is not in Reflector.exe, you'll need to register it using the RegisterLanguage() method before you can use it. 

The ShowCodeModel method
First let's look at the ShowCodeModel() implementation, since it is a good introduction to the CodeModel and its hierarchy.  If you look in the ReflectorAddins solution, find the CodeModelExample project and the CodeModelExample.cs file.  In the Main() method, you'll see the code that inspired this entry.  The CodeModelExample is a simple console application that loads an assembly and enumerates its types, methods and IL code.  To load an assembly into Reflector, you need to have an implementation of the IAssemblyManager, which you can get from the IServiceProvider.  The IAssemblyManager.LoadFile() method will take an assembly file path and return an IAssembly object.  As illustrated in the image below, once you have the IAssembly object, you will have all the modules, types, methods and IL instructions in the assembly.

image

Below is my adaptation of the CodeModelExample.Main() for the WinForm applications.  The code was almost completely taken from Lutz's example, I just changed the output to be a StringBuilder and removed some things that I didn't need.

private void ShowCodeModel()
{
    this.Cursor = Cursors.WaitCursor;
    StringBuilder output = new StringBuilder();
 
    IAssemblyManager assemblyManager = (IAssemblyManager)serviceProvider.GetService(typeof(IAssemblyManager));
 
    if (File.Exists(currentAssemblyPath))
    {
        IAssembly assembly = assemblyManager.LoadFile(currentAssemblyPath);
        output.AppendLine("Assembly: " + assembly.ToString());
 
        foreach (IModule module in assembly.Modules)
        {
            output.AppendLine(("Module: " + module.Name));
 
            foreach (ITypeDeclaration typeDeclaration in module.Types)
            {
                output.AppendLine(("Type: " + typeDeclaration.Namespace + "." + typeDeclaration.Name));
 
                foreach (IMethodDeclaration methodDeclaration in typeDeclaration.Methods)
                {
                    output.AppendLine(("Method: " + methodDeclaration));
 
                    IMethodBody methodBody = methodDeclaration.Body as IMethodBody;
                    if (methodBody != null)
                    {
                        foreach (IInstruction instruction in methodBody.Instructions)
                        {
                            output.Append("L" + instruction.Offset.ToString("X4", CultureInfo.InvariantCulture));
                            output.Append(": ");
                            output.Append(InstructionTable.GetInstructionName(instruction.Code));
 
                            if (instruction.Value != null)
                            {
                                output.Append(" ");
 
                                if (instruction.Value is string)
                                {
                                    output.Append("\"");
                                }
 
                                output.Append(instruction.Value.ToString());
 
                                if (instruction.Value is string)
                                {
                                    output.Append("\"");
                                }
                            }
 
                            output.AppendLine();
                        }
                    }
                }
            }
        }
    }
 
    this.richTextBox1.Clear();
    this.richTextBox1.Text = output.ToString();
 
    this.Cursor = Cursors.Default;
}

Once you have the ShowCodeModel() method implemented, you need to add the InstrutionTable.cs file from the CodeModelExample to your project, in order for the proper instructions to be output. Now if you compile and run the application on itself (File->Open and find the exe, then click on the CodeModel button), you'll see some output that looks something like the table below, which is the output of walking through the IAssembly object hierarchy:

Assembly: ReflectorWrapper, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Module: ReflectorWrapper.exe
Type: .<Module>
Type: ReflectorWrapper.WrapperForm
Method: WrapperForm.Dispose(Boolean) : Void
L0000: nop
L0001: ldarg.1
L0002: brfalse.s 15
....

The Disassemble method
In the ShowCodeModel() method above, you loaded an assembly into an IAssembly implementation and displayed its properties in the rich text box, which only required the IServiceProvider, IAssemblyManager.  In order to disassemble and translate an assembly into a higher level language, you'll need a few more objects - most having to do with the language translation.  If you look in the ReflectorAddins solution at the ClassView project's ClassViewWindow.cs file, you'll see the majority of the code you need to implement the Disassemble() method code.  The ClassViewWindow.Translate() method, shows how to use most of the needed objects:

Interfaces need to Disassemble How do you get it?
Reflector.ILanguageManager Accessible from IServiceProvider
Reflector.CodeModel.ILangauge Accessible from ILanguageManager.Languages
Reflector.CodeModel.ILanguageWriterConfiguration Have to implement
Reflector.CodeModel.IVisibilityConfiguration Accessible from IServiceProvider
Reflector.CodeModel.IFormatter Have to implement
Reflectror.CodeModel.ILanguageWriter Accessible from ILanguage
Reflector.ITranslatorManager Accessible from IServiceProvider
Reflector.CodeModel.IAssemblyResolver Have to implement

As noted in the table above, we nee to implement three objects: ILanguageWriterConfiguration, IFormatter, IAssemblyResolver ... Lutz has actually provided an implementation of ILanguageWriterConfiguration and IFormatter (for RichTextBoxes) in the ClassView project.  You can add the ClassView file RichTextFormtter.cs to your WinForm application to get the IFormatter implementation.  The ILanguageWriterConfiguration implementation is in the ClassViewWindow.cs file at the bottom - you'll need to make sure this gets added to your implementation.  This only leaves the IAssemblyResolver implementation that we need to create.  This is needed because we are not using the Reflector application (just the addin functionality), a custom IAssemblyResolver is necessary to tell the disassembler where to look when it comes to a referenced assembly.  In the sample application, I've provided a quick and dirty (and maybe not the most efficient) implementation in the CustomAssemblyResolver.cs file.  As you can see in the code below, all this class does is load the assembly the disassembler is asking to resolve and return an IAssembly (using the IAssemblyManager).

public class CustomAssemblyResolver : IAssemblyResolver
{
    IAssemblyManager assemblyManager;
 
    public CustomAssemblyResolver(IAssemblyManager assemblyManager)
    {
        this.assemblyManager = assemblyManager;
    }
 
    public IAssembly Resolve(IAssemblyReference value, string localPath)
    {
        Assembly assembly = Assembly.Load(value.ToString());
        return assemblyManager.LoadFile(assembly.Location);
    }
}

The implementation of the Disassemble() method has an event flow like this:

  1. Get an IAssemblyManager from the IServiceProvider
  2. Set the IAssemblyManager.Resolver property to use an instance of the CustomAssemblyResolver
  3. Get an ITranslatorManager from the IServiceProvider
  4. Create an instance of the IFormatter (RichTextForatter)
  5. Get an IVisibilityConfiration from the IServiceProvider
  6. Create an instance for the ILanguageWriterConfiguration (LanguageWriterConfiguration)
  7. Set the ILanguageWriterConfiguration Visibility property to the object from #5 above
  8. Set some configuration strings (the ones implemented below have been copied from the ClassView addin)
  9. Create an ILanguageWriter from the active language, passing the objects from #4 and #6
  10. Load an assembly using the IAssemblyManager from #1
  11. Pass IAssembly to the ILanguageWriter from #9
  12. Loop through all IModules in the IAssembly.Modules collection
  13. Pass IModule to the ILanguageWriter from #9
  14. Loop though all ITypeDeclarations in the IModule.Types collection
  15. Pass ITypeDeclaration to ILanguageWriter from #9
  16. Loop through all IFieldDeclarations in the ITypeDeclaraion.Fields collection
  17. Pass IFieldDelaration to ILanaguageWriter from #9
  18. Loop through all IMethodDeclarations in the ITypeDeclaration.Methods collection
  19. Pass IMethodDeclaration to the ILanguageWriter from #9
  20. Loop through all IPropertyDeclarations in the ITypeDeclaration.Properties collection
  21. Pass IPropertyDeclaration to the ILanguageWriter from #9
  22. Loop through all IEventDeclarations in the ITypeDeclaration.Events collection
  23. Pass IEventDeclaration to the ILanguageWriter from #9
  24. Unload assembly from IAssemblyManager

Now that we've covered the flow of logic, here is the method implementation from the sample application:

private void Disassemble()
{
    this.Cursor = Cursors.WaitCursor;
 
    IAssemblyManager assemblyManager = (IAssemblyManager)serviceProvider.GetService(typeof(IAssemblyManager));
    assemblyManager.Resolver = new CustomAssemblyResolver(assemblyManager);
 
    ITranslatorManager translatorManager = (ITranslatorManager)serviceProvider.GetService(typeof(ITranslatorManager));
 
    RichTextFormatter formatter = new RichTextFormatter();
    IVisibilityConfiguration visibilityConfiguration = (IVisibilityConfiguration)serviceProvider.GetService(typeof(IVisibilityConfiguration));
    LanguageWriterConfiguration configuration = new LanguageWriterConfiguration();
    configuration.Visibility = visibilityConfiguration;
    configuration["ShowCustomAttributes"] = "true";
    configuration["ShowMethodDeclarationBody"] = "true";
 
    ILanguageWriter writer = languageManager.Languages[this.cboLanguages.SelectedIndex].GetWriter(formatter, configuration);
 
    if (File.Exists(currentAssemblyPath))
    {
        IAssembly assembly = assemblyManager.LoadFile(currentAssemblyPath);
 
        writer.WriteAssembly(assembly);
 
        foreach (IModule module in assembly.Modules)
        {
            writer.WriteModule(module);
            foreach (ITypeDeclaration typeDeclaration in module.Types)
            {
                writer.WriteTypeDeclaration(typeDeclaration);
                foreach (IFieldDeclaration fieldDeclaration in typeDeclaration.Fields)
                { 
                    IFieldDeclaration fieldDeclaration2 = translatorManager.CreateDisassembler(null, null).TranslateFieldDeclaration(fieldDeclaration);
                    writer.WriteFieldDeclaration(fieldDeclaration2);
                }
 
                foreach (IMethodDeclaration methodDeclaration in typeDeclaration.Methods)
                {
                    IMethodDeclaration methodDeclaration2 = translatorManager.CreateDisassembler(null, null).TranslateMethodDeclaration(methodDeclaration);
                    writer.WriteMethodDeclaration(methodDeclaration2);
                }
 
                foreach (IPropertyDeclaration propertyDeclaration in typeDeclaration.Properties)
                {
                    IPropertyDeclaration propertyDeclaration2 = translatorManager.CreateDisassembler(null, null).TranslatePropertyDeclaration(propertyDeclaration);
                    writer.WritePropertyDeclaration(propertyDeclaration2);
                }
 
                foreach (IEventDeclaration eventDeclaration in typeDeclaration.Events)
                {
                    IEventDeclaration eventDeclaration2 = translatorManager.CreateDisassembler(null, null).TranslateEventDeclaration(eventDeclaration);                           
                    writer.WriteEventDeclaration(eventDeclaration2);
                }
            }
        }
 
        assemblyManager.Unload(assembly);
 
    }
    this.richTextBox1.Clear();
    this.richTextBox1.Rtf = formatter.ToString();
 
    this.Cursor = Cursors.Default;

If you've added the method implementation, the RichTextFormatter and the LanguageWriterConfiguration to your project, it should compile.  When you run it, it should look something like the screen shot at the beginning of this entry.

Conclusion
You can learn a lot about how Reflector performs its actions under the hood by creating a wrapper of the disassembler.  And if you are going to be writing any addins - get to know the ReflectorAddins solution code! 

Comments (0) | Post RSSRSS comment feed |

Categories:
Tags:

Comments are closed