ILSpy, how to resolve dependencies?
Asked Answered
R

4

6

I want to disassemble an entire .NET assembly with ILSpy.

I used this code as base:
http://skysigal.xact-solutions.com/Blog/tabid/427/entryid/2488/Default.aspx

And it works fine, just when I have an assembly that references Npgsql.dll (or any other non-gac assembly), then I get an AssemblyResolutionException.

Failed to resolve assembly: 'Npgsql, Version=2.0.11.92, Culture=neutral, PublicKeyToken=5d8b90d52f46fda7'

I know how I can get the referenced assemblies, but how can I add them to ast ?

    // SqlWebAdmin.Models.Decompiler.DecompileAssembly("xy.dll");
    public static string DecompileAssembly(string pathToAssembly)
    {
        //Assembly assembly = Assembly.LoadFrom(pathToAssembly);
        System.Reflection.Assembly assembly = System.Reflection.Assembly.ReflectionOnlyLoadFrom(pathToAssembly);
        //assembly.GetReferencedAssemblies();

        //assembly.GetReferencedAssemblies(assembly);
        Mono.Cecil.AssemblyDefinition assemblyDefinition =
            Mono.Cecil.AssemblyDefinition.ReadAssembly(pathToAssembly);



        ICSharpCode.Decompiler.Ast.AstBuilder astBuilder = new ICSharpCode.Decompiler.Ast.AstBuilder(new ICSharpCode.Decompiler.DecompilerContext(assemblyDefinition.MainModule));
        astBuilder.AddAssembly(assemblyDefinition);


        //new Helpers.RemoveCompilerAttribute().Run(decompiler.CompilationUnit);
        using (System.IO.StringWriter output = new System.IO.StringWriter())
        {
            astBuilder.GenerateCode(new ICSharpCode.Decompiler.PlainTextOutput(output));
            string result = output.ToString();
            return result;
        }

        return "";
    } // End Function DecompileAssembly
Radome answered 31/12, 2011 at 17:28 Comment(2)
//assembly.GetReferencedAssemblies(); in the original code from your link was GetReferencedAssemblies(assembly);, did you leave it out because they don't define what GetReferencedAssemblies is in the article? That code would probably help you.Meerkat
@M.Babcock: True but this code is nowhere to find. And in ObjectManager, there is no method GetReferencedAssemblies that takes assembly as parameter...Radome
C
14

You need to tell Cecil, the underlying metadata reader that ILSpy is using, where your assemblies are. You can write:

var resolver = new DefaultAssemblyResolver();
resolver.AddSearchDirectory("path/to/my/assemblies");

var parameters = new ReaderParameters
{
    AssemblyResolver = resolver,
};

var assembly = AssemblyDefinition.ReadAssembly(pathToAssembly, parameters);

This is the most natural way to tell Cecil where to resolve referenced assemblies. This way you can remove the line where you load the assembly using System.Reflection, and only use the ILSpy stack.

Candis answered 2/1, 2012 at 18:20 Comment(1)
There is another angle to this problem, @JB Evain. If I am decompiling a DLL on a system where the referred assemblies are not present, the decompilation fails (with exception.) At least, i would like to see the decompile code, for whatever has been resolved. I am posting an additional solution, but yours is ideal.Microwave
H
4

This is improved @Nayan answer. If you want to ignore missing assemblies, copy this class:

using Mono.Cecil;

public class IgnoringExceptionsAssemblyResolver : DefaultAssemblyResolver
{
    public override AssemblyDefinition Resolve(AssemblyNameReference name)
    {
        try
        {
            return base.Resolve(name);
        }
        catch
        {
            return null;
        }
    }
}

and use it like that:

var assembly = AssemblyDefinition.ReadAssembly(path, new ReaderParameters() {
    AssemblyResolver = new IgnoringExceptionsAssemblyResolver()
});
Hudspeth answered 29/1, 2016 at 9:31 Comment(1)
Yes, this is much cleaner. I was a rookie, when I wrote that answer :)Microwave
M
2

In addition to what JB Evain suggested, this code will help in avoiding the exception. All you have to do is handle the exception in resolver.

Not the best way, I admit. But it works for this scenario: "If I am decompiling a DLL on a system where the referred assemblies are not present, the decompilation fails (with exception.) At least, i would like to see the decompile code, for whatever has been resolved."

using System;
using System.Collections.Generic;
using Mono.Cecil;

public class MyAssemblyResolver : BaseAssemblyResolver
{
    private readonly IDictionary<string, AssemblyDefinition> cache;
    public MyAssemblyResolver()
    {
        this.cache = new Dictionary<string, AssemblyDefinition>(StringComparer.Ordinal);
    }
    public override AssemblyDefinition Resolve(AssemblyNameReference name)
    {
        if (name == null)
            throw new ArgumentNullException("name");
        AssemblyDefinition assemblyDefinition = null;
        if (this.cache.TryGetValue(name.FullName, out assemblyDefinition))
            return assemblyDefinition;
        try  //< -------- My addition to the code.
        {
            assemblyDefinition = base.Resolve(name);
            this.cache[name.FullName] = assemblyDefinition;
        }
        catch { } //< -------- My addition to the code.
        return assemblyDefinition;
    }
    protected void RegisterAssembly(AssemblyDefinition assembly)
    {
        if (assembly == null)
            throw new ArgumentNullException("assembly");
        string fullName = assembly.Name.FullName;
        if (this.cache.ContainsKey(fullName))
            return;
        this.cache[fullName] = assembly;
    }
}

And use it like this:

var rp = new Mono.Cecil.ReaderParameters() { AssemblyResolver = new MyAssemblyResolver() };
var assemblyDefinition = Mono.Cecil.AssemblyDefinition.ReadAssembly(assemblyStream, rp);
var astBuilder = new ICSharpCode.Decompiler.Ast.AstBuilder(
     new ICSharpCode.Decompiler.DecompilerContext(assemblyDefinition.MainModule));
astBuilder.AddAssembly(assemblyDefinition);




I would actually like to see an enhancement in the decompiler: it currently ignores the ReaderParameters that user sets, in DefaultAssemblyResolver class.

Usage:

var rp = new Mono.Cecil.ReaderParameters();
var assemblyDefinition = Mono.Cecil.AssemblyDefinition.ReadAssembly(assemblyStream, rp);

Current DefaultAssemblyResolver code:

public override AssemblyDefinition Resolve(AssemblyNameReference name)
{
    if (name == null)
    {
        throw new ArgumentNullException("name");
    }
    AssemblyDefinition assemblyDefinition;
    if (this.cache.TryGetValue(name.FullName, out assemblyDefinition))
    {
        return assemblyDefinition;
    }
    assemblyDefinition = base.Resolve(name); // <---------
// Is the `ReaderParameters` object set by user, used to resolve in `base` class?

    this.cache[name.FullName] = assemblyDefinition;
    return assemblyDefinition;
}
Microwave answered 6/8, 2013 at 12:33 Comment(2)
Brilliant, this allowed me to display source code without the referenced assemblies.Huesman
@Nayan: This can be achieved much easier. Look at my answer. Copying source code of base classes is not the best way to enhance them.Hudspeth
M
1

Based on the Mono.Cecil source, I would guess that you could probably handle this using the Mono.Cecil.DefaultAssemblyResolver class.

Instead of this code:

Mono.Cecil.AssemblyDefinition assemblyDefinition =
    Mono.Cecil.AssemblyDefinition.ReadAssembly(pathToAssembly);

try this:

Mono.Cecil.AssemblyDefinition assemblyDefinition =
    new Mono.Cecil.DefaultAssemblyResolver().Resolve(System.Reflection.AssemblyName.GetAssemblyName(pathToAssembly).ToString());

EDIT

While my original suggestion may or may not work (I've never done it, so no guarantees), you may want to look into the Mono.Addins.CecilReflector.dll assembly from the Mono.Addins project to help mitigate these sort of problems. It is also based on Mono.Cecil (just as ILSpy is) so even though the general premise that Mono.Addins is an extensibility library doesn't meet your needs it may contain some code use for your purposes or at least learn from.

Meerkat answered 1/1, 2012 at 0:14 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.