【问题标题】:Calling a method of existing object using IL Emit使用 IL Emit 调用现有对象的方法
【发布时间】:2023-03-08 00:01:01
【问题描述】:

我正在尝试编写基于属性的拦截器(类似于DynamicProxy)。这个想法是,基于某些自定义属性,将调用该属性内的方法,即

  • 在调用实际方法之前调用属性类中的方法。
  • 调用实际方法。

我可以使用MethodBuilderTypeBuilder 覆盖现有方法。但是,我不知道如何调用 Attribute 中的方法。

我的代码:

static void CreateMethods<T>(TypeBuilder tb)
        {
            foreach (var methodToOverride in typeof(T).GetMethods(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly))
            {
                var attribute = (ProxyMethod)methodToOverride.GetCustomAttribute(typeof(ProxyMethod));
                if (attribute == null)
                    continue;

                MethodBuilder methodBuilder = tb.DefineMethod(
                    methodToOverride.Name,
                    MethodAttributes.Public
                    | MethodAttributes.HideBySig
                    | MethodAttributes.NewSlot
                    | MethodAttributes.Virtual
                    | MethodAttributes.Final,
                    CallingConventions.HasThis,
                    methodToOverride.ReturnType,
                    Type.EmptyTypes
                );

                ILGenerator il = methodBuilder.GetILGenerator();

                il.Emit(OpCodes.Ldstr, "The I.M implementation of C"); //step1
                il.Emit(OpCodes.Call, typeof(Console).GetMethod("WriteLine", new Type[] { typeof(string) })); //step1

                il.Emit(OpCodes.Ldarg_0);
                il.Emit(OpCodes.Call, methodToOverride);
                il.Emit(OpCodes.Ret);

                tb.DefineMethodOverride(methodBuilder, methodToOverride);
            }
        }

我认为我应该做的是将attribute 加载到堆栈上,然后通过发出对MethodInfo 的调用来调用attribute.attributeMethod()。但是,无论在哪里,我都可以找到使用OpCodes.NewObj 创建对象的新实例的示例。我不想用这个,因为属性可能有参数。

我想不出任何调用属性类中的方法(这将取代 step1 注释)。

编辑:基于 cmets,我试图在 IL 中移动代码的 GetCustomAttribute 部分。这就是我现在拥有的

il.Emit(OpCodes.Ldtoken, methodToOverride);
il.Emit(OpCodes.Ldtoken, typeof(ProxyMethod));
il.Emit(OpCodes.Call, typeof(Attribute).GetMethod("GetCustomAttribute", new [] { typeof(MemberInfo), typeof(Type) }));

这对我来说是一个错误。有什么建议吗?

【问题讨论】:

  • 您还需要发出加载属性的代码,因为您不能将属性实例传递给生成的代码。请记住,当该代码运行时您的代码不会,因此只能直接传递独立于您的代码的简单原语和元数据,而不是本地创建的对象。 (由于属性是从元数据实例化的,理论上可以以某种方式发出实例化它所需的数据而不是获取属性,但我认为这没有任何好处。)
  • 你能举个例子吗?如果我将代码的属性部分移至 IL,我仍然需要将 typeof(ProxyMethod) 放在堆栈顶部。我也不知道该怎么做。
  • 这更容易:如果您调查编译器为此类代码(例如使用 LINQPad)发出的 IL,您会发现您需要一个 ldtoken 操作码。 (事实上​​,通过研究此方法的编译代码,您可以了解如何在生成的代码中移动它的一部分。此外,尽可能依赖调用非生成代码,例如在众所周知的静态方法中,作为您需要生成更容易的代码。)
  • @JeroenMostert 你能看看编辑吗?

标签: c# reflection .net-core cil


【解决方案1】:

动态代码生成总是有点烦人。让我们先编写一些辅助方法来摆脱所有.GetMethod 的东西:

static class Method {
    public static MethodInfo Of<TResult>(Expression<Func<TResult>> f) => ((MethodCallExpression) f.Body).Method;
    public static MethodInfo Of<T>(Expression<Action<T>> f) => ((MethodCallExpression) f.Body).Method;
    public static MethodInfo Of(Expression<Action> f) => ((MethodCallExpression) f.Body).Method;
}

现在假设我们有 ProxyMethodAttribute 和它的方法 MyMethod -- 这是我们如何检索它并调用它:

il.Emit(OpCodes.Ldtoken, methodToOverride);
il.Emit(OpCodes.Call, Method.Of(() => MethodBase.GetMethodFromHandle(default(RuntimeMethodHandle))));
il.Emit(OpCodes.Ldtoken, typeof(ProxyMethodAttribute));
il.Emit(OpCodes.Call, Method.Of(() => Type.GetTypeFromHandle(default(RuntimeTypeHandle))));
il.Emit(OpCodes.Ldc_I4_1);
il.Emit(OpCodes.Call, Method.Of(() => Attribute.GetCustomAttribute(default(MemberInfo), default(Type), default(bool))));
il.Emit(OpCodes.Callvirt, Method.Of((ProxyMethodAttribute a) => a.MyMethod()));

注意我们如何需要调用GetXXXFromHandle 来从生成的元数据令牌中生成实际实例,以及我们如何需要更复杂地调用Attribute.GetCustomAttributeMethodInfo.GetCustomAttribute 实际上并不存在,这是作为扩展实现的方法。)

【讨论】:

  • 感谢杰伦的回复。你能解释一下 IL 代码在做什么吗?
  • 基本上无非就是调用GetCustomAttribute(methodToOverride, typeof(ProxyMethodAttribute), true) 来获取methodToOverride 上的ProxyMethodAttribute,然后在返回的属性实例上调用MyMethod。唯一棘手的是Get...FromHandle 调用,我们需要从metadata tokens 获取方法和类型句柄。我们得到这些而不是实例的原因与我们不能传递属性实例的原因相同——生成代码不能将对象实例传递给生成的代码。
  • 我的意思是,我无法弄清楚这里的调用发出了什么。例如,我知道你在第一行加载MethodInfo,但我不知道你在第二行做什么。同样,对于所有“电话”,我不明白您在做什么。另一方面,您能解释一下GetXXXFromHandle 的实际作用吗? AFAIK,他们将IntPtrs 和Handles 转换为正确的方法。我不明白它在当前上下文中是如何使用的。
  • 第一行加载MethodInfo,它加载基于MethodInfo的元数据令牌。您不能将这些标记传递给方法,因为您实际上需要 MethodInfoType 实例(GetCustomAttribute 需要),这就是 GetXXX 方法产生的。它是“MethodInfo(生成代码)->ldtoken &lt;mdtoken&gt;->GetMethodFromHandle->MethodInfo(生成代码)”。我们不能直接发出代码来加载调用代码中的MethodInfo,因为该对象没有(直接)IL 表示(不像整数1)。
  • 注意它可能看起来像Call 允许我们直接传递MethodInfo,但这只是因为Call 实际上采用元数据令牌,而不是MethodInfo。我们必须跳过这些环节,因为我们需要调用 GetCustomAttribute,它需要一个实际的方法和类型,而不是元数据令牌。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多