【问题标题】:How to inject call to System.Object.Equals with Mono.Cecil?如何使用 Mono.Cecil 注入对 System.Object.Equals 的调用?
【发布时间】:2011-05-21 07:24:13
【问题描述】:

使用 Mono.Cecil 我想重写以下属性:

public string FirstName
{
    get { return _FirstName; }
    set
    {
        _FirstName = value;
    }
}

到这里:

public string FirstName
{
    get { return _FirstName; }
    set
    {
        if (System.Object.Equals(_FirstName, value))
        {
            return;
        }
        _FirstName = value;
    }
}

这只是重写内容的一个简单介绍,但这是我遇到问题的地方。

使用反射器我可以看到以下代码根据需要重写属性,除了对 System.Object.Equals() 的调用。如果期望 IL 代码是:

call bool [mscorlib]System.Object::Equals(object, object)

但它被写成:

call instance void RewriteSharp.Person::.ctor()

编写对 System.Object.Equals 的调用的代码是:

setMethodWriter.InsertBefore(
    firstExistingInstruction, 
    setMethodWriter.Create(OpCodes.Call, objectEqualsMethodReference));

用于初始化objectEqualsMethodReference的方法是:

private static MethodReference GetSystemObjectEqualsMethodReference(
    AssemblyDefinition assembly
)
{

    var typeReference = assembly.MainModule.GetTypeReferences()
        .Single(t => t.FullName == "System.Object");

    var typeDefinition = typeReference.Resolve();

    var methodDefinition = typeDefinition.Methods.Single(
                            m => m.Name == "Equals"
                                && m.Parameters.Count == 2
                                && m.Parameters[0].ParameterType.Name == "Object"
                                && m.Parameters[1].ParameterType.Name == "Object"
    );

    return methodDefinition;
}

在我看来 setMethodWriter.Create() 或 GetSystemObjectEqualsMethodReference() 是不正确的,没有多少调试可以解决问题。

正在编写的属性和重写该属性的代码具有相同的框架目标。 3.5 和 4.0 都失败了。

我正在使用主分支 https://github.com/jbevain/cecil 构建 Mono.Cecil。

完整的代码清单

using Mono.Cecil;
using Mono.Cecil.Cil;
using System;
using System.Linq;

namespace RewriteNotifyPropertyChanged
{
class Program
{
static void Main(string[] args)
{
    var rewrite = "..\\RewriteSharp.dll";
    var rewritten  = "..\\RewritenSharp.dll";

    var typeName = "Person";
    var propertyName = "FirstName";

    var assembly = AssemblyDefinition.ReadAssembly(rewrite);
    var typeDefinition = assembly.MainModule.Types.Single(t => t.Name == typeName);
    var propertyDefintion = typeDefinition.Properties
        .Single(p => p.Name == propertyName);

    var setMethodWriter = propertyDefintion.SetMethod.Body.GetILProcessor();
    var backingFieldReference = GetBackingFieldReference(typeDefinition, propertyName);
    var objectEqualsMethodReference = GetSystemObjectEqualsMethodReference(assembly);
    var firstExistingInstruction = setMethodWriter.Body.Instructions[0];

    setMethodWriter.InsertBefore(
        firstExistingInstruction, 
        setMethodWriter.Create(OpCodes.Ldarg_0));

    setMethodWriter.InsertBefore(
        firstExistingInstruction, 
        setMethodWriter.Create(OpCodes.Ldfld, backingFieldReference));

    setMethodWriter.InsertBefore(
        firstExistingInstruction, 
        setMethodWriter.Create(OpCodes.Ldarg_1));

    setMethodWriter.InsertBefore(
        firstExistingInstruction, 
        setMethodWriter.Create(OpCodes.Call, objectEqualsMethodReference));

    setMethodWriter.InsertBefore(
        firstExistingInstruction, 
        setMethodWriter.Create(OpCodes.Brfalse_S, firstExistingInstruction));

    setMethodWriter.InsertBefore(
        firstExistingInstruction, 
        setMethodWriter.Create(OpCodes.Ret));

    assembly.Write(rewritten, new WriterParameters { WriteSymbols = true });

    Console.WriteLine("Done.");
    Console.ReadKey();
}

private static MethodReference GetSystemObjectEqualsMethodReference(
    AssemblyDefinition assembly
)
{

    var typeReference = assembly.MainModule.GetTypeReferences()
        .Single(t => t.FullName == "System.Object");

    var typeDefinition = typeReference.Resolve();

    var methodDefinition = typeDefinition.Methods.Single(
                            m => m.Name == "Equals"
                                && m.Parameters.Count == 2
                                && m.Parameters[0].ParameterType.Name == "Object"
                                && m.Parameters[1].ParameterType.Name == "Object"
    );

    return methodDefinition;
}

private static FieldReference GetBackingFieldReference(
    TypeDefinition typeDefinition, 
    string propertyName
)
{
    var fieldName = "_" + propertyName;
    var fieldReference = typeDefinition.Fields.Single(f => f.Name == fieldName);

    return fieldReference;
}
}
}

【问题讨论】:

  • 这不会解决您所描述的问题,但它仍然是一个问题 - 您需要取出 ldarg_0 指令之一。
  • 我可以发誓我之前在 Reflector 中看到了 2 条 ldarg_0 指令 :-)。谢谢。

标签: c# mono.cecil


【解决方案1】:

Cecil 与 System.Reflection 不同,它区分了引用和定义,它们的作用域是每个模块。这意味着您不能简单地使用您自己的另一个模块中的 MethodDefinition。您必须创建对它的正确引用。这是一个在 Cecil 术语中称为 importing 的过程。

具体来说,GetSystemObjectEqualsMethodReference 返回一个在 corlib 中定义的方法,你需要在你的模块中创建一个对它的引用:

替换:

var objectEqualsMethodReference = GetSystemObjectEqualsMethodReference(assembly);

作者:

var objectEqualsMethodReference = assembly.MainModule.Import (GetSystemObjectEqualsMethodReference(assembly));

修复 IL 应该可以让它工作。

另外,当我在做的时候,方法:

private static MethodReference GetSystemObjectEqualsMethodReference(AssemblyDefinition assembly)
{
    var typeReference = assembly.MainModule.GetTypeReferences()
        .Single(t => t.FullName == "System.Object");

    var typeDefinition = typeReference.Resolve();

    var methodDefinition = typeDefinition.Methods.Single(
                            m => m.Name == "Equals"
                                && m.Parameters.Count == 2
                                && m.Parameters[0].ParameterType.Name == "Object"
                                && m.Parameters[1].ParameterType.Name == "Object"
    );

    return methodDefinition;
}

最好写成:

private static MethodReference GetSystemObjectEqualsMethodReference(AssemblyDefinition assembly)
{
    var @object = assembly.MainModule.TypeSystem.Object.Resolve ();

    return @object.Methods.Single(
        m => m.Name == "Equals"
            && m.Parameters.Count == 2
            && m.Parameters[0].ParameterType.MetadataType == MetadataType.Object
            && m.Parameters[1].ParameterType.MetadataType == MetadataType.Object);
}

assembly.Write(rewritten, new WriterParameters { WriteSymbols = true });

如果你在阅读程序集的时候不通过new ReaderParameters { ReadSymbols = true } 没有多大意义。

【讨论】:

  • 完美。谢谢你。我一路上尝试过导入,但显然不正确。
【解决方案2】:

您可以查看 KindOfMagic codeplex 项目。

它的作用几乎相同,但稍微好一点——它不调用 Object.Equals(),而是在目标类型上定义的相等运算符。

http://kindofmagic.codeplex.com

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-04-12
    • 1970-01-01
    • 2012-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-19
    相关资源
    最近更新 更多