Search code examples
c#.netcilmono.cecil

Reference assembly by Mono.Cecil


For example I have some dll SomeLib with the following classes:

public class Class1
{
    public Class2 GetClass2() => new Class2();
}

public class Class2
{
    public int Prop1 { get; set; } = 5;
}

I read it by Mono.Cecil:

var sourceType = typeof(Class1);
var assemblyDefinition = AssemblyDefinition.ReadAssembly(sourceType.Module.FullyQualifiedName);

Now I have Class1 and Class2 in the loaded assembly. I would like to preserve Class1 in this "Mono.Cecil" assembly but delete Class2, just load Class2 from existing SomeLib.dll by reference.

Here is my attempt. It prints 5. But if I uncomment //Delete Class2 section then it fails with exception.

static void Main(string[] args)
{
    var sourceType = typeof(Class1);
    var assemblyDefinition = AssemblyDefinition.ReadAssembly(sourceType.Module.FullyQualifiedName);

    using (var stream = new MemoryStream())
    {
        var type = assemblyDefinition.MainModule.GetType(sourceType.FullName, true);
        type.Name += "Custom";
        assemblyDefinition.Name.Name += "Custom";

        // Delete Class2
        //var class2Type = assemblyDefinition.MainModule.Types.Single(t => t.FullName == typeof(Class2).FullName);
        //assemblyDefinition.MainModule.Types.Remove(class2Type);

        // Try to reference Class2's dll, SomeLib.dll
        assemblyDefinition.MainModule.AssemblyReferences.Add(AssemblyNameReference.Parse(typeof(Class2).Assembly.FullName));

        assemblyDefinition.Write(stream);

        var assembly = Assembly.Load(stream.ToArray());
        var newType = assembly.GetType(sourceType.FullName + "Custom");
        var instance = Activator.CreateInstance(newType);

        var method = newType.GetMethod(nameof(Class1.GetClass2));
        var class2 = method.Invoke(instance, new object[0]);
        var prop = class2.GetType().GetProperty(nameof(Class2.Prop1));
        Console.WriteLine(prop.GetValue(class2));
    }
    Console.ReadLine();
}

Solution

  • Just to put the right answer - it is possible. Like Evk said you can just replace all the references to Class2, something like this

    _typeRef = assemblyDefinition.MainModule.ImportReference(typeof(Class2));
    
    static void Visit(MethodDefinition method)
    {
        if (method.ReturnType.FullName == _typeRef.FullName)
        {
            method.ReturnType = _typeRef;
        }
    
        for (var i = 0; i < method.Parameters.Count; i++)
        {
            if (method.Parameters[i].ParameterType.FullName == _typeRef.FullName)
            {
                method.Parameters[i].ParameterType = _typeRef;
            }
        }
    
        foreach (var cmd in method.Body.Instructions)
        {
            if (cmd.OpCode == OpCodes.Call || cmd.OpCode == OpCodes.Callvirt)
            {
                var methodDefinition = ((MethodReference) cmd.Operand).Resolve();
                if (methodDefinition.Module == _typeRef.Module)
                {
                    Visit(methodDefinition);
                }
            }
        }
    }
    

    But, of course, you have to check all the types and members