首页 新闻 会员 周边

请教对用C# Emit对IL操作熟悉的朋友(或熟悉AOP织入实现的朋友), 如何用ILGenerator.Emit(OpCodes.xxxx, yyyy)的方式动态生成目标代码. (问题见内容)

0
悬赏园豆:100 [待解决问题]

Hi各位好, 如下是我的问题:

问题背景:
我想用Aop的方式对目标对象的方法做拦截, 并覆盖该方法, 对其做Task包装使原方法形成一个任务(Task)在独立的线程中执行, 覆盖后的方法返回Task或Task<xxx>, 下见代码:

被拦截的目标对象类:
public class TaskEntity
{
public TaskEntity()
{
Console.WriteLine("实例化'TaskEntity'对象");
}

    //[JL_Intercept]
    public virtual void TaskMethod()
    {
        for (int i = 0; i < 300; i++)
        {
            Console.WriteLine($"TaskMethod -> i = {i}");
        }
    }

    [JL_Intercept]
    public virtual string TaskMethod2()
    {
        for (int i = 0; i < 300; i++)
        {
            Console.WriteLine($"TaskMethod2 -> j = {i}");
        }
        return "完成";
    }
}

希望被动态生成代码(下称 代理类)如下:

public class TaskModeExpand_No3 //: ITaskEntity
{
private StandardInterceptor Interceptor;
public TaskEntity SourceInstance { get; private set; }

public TaskModeExpand_No3(TaskEntity m_TaskEntity)
{
  this.Interceptor = new StandardInterceptor();
  this.SourceInstance = m_TaskEntity;
}

public Task TaskMethod()
{
  var m_MethodInfo = this.SourceInstance.GetType().GetMethod("TaskMethod");

  this.Interceptor.Before(this.SourceInstance, null);

  Task m_Result = Task.Run(() =>
  {
    m_MethodInfo.Invoke(this.SourceInstance, null);
  });

  this.Interceptor.After(this.SourceInstance, null, null);

  return m_Result;
}

///// <summary>
///// 测试方法2
///// </summary>
///// <returns></returns>
//public Task<string> TaskMethod2()
//{
//  this.Interceptor.Before(this.SourceInstance, null);

//  Task<string> m_Result = Task.Run<string>(() =>
//  {
//    return this.SourceInstance.TaskMethod2();
//  });

//  this.Interceptor.After(this.SourceInstance, null, null);

//  return m_Result;
//}

}

其中定义的 public Task TaskMethod() 方法就是覆盖了TaskEntity中TaskMethod方法(此处熟悉AOP的朋友可以会说你这样覆盖不是标准C#语法上的覆盖, 是的 我这里没有用标准覆盖应为新方法的返回值统一被调整为Task或Task<xxx>, 已与原方法返回值不同也用不了标准覆盖), 并在原方法外包装了一个Task来达到目的.
根据代理类(class TaskModeExpand_No3)的代码使用ildasm工具对其还原IL编码见下图:


图1


图2

根据ildasm中展示的Pubilc Task TaskMethod()方法内容, 用 方式做如下代码还原:

  var ilOfMethod = methodBuilder.GetILGenerator();

  ilOfMethod.Emit(OpCodes.Newobj, nestedTypeCtor);
  ilOfMethod.Emit(OpCodes.Stloc_0);
  ilOfMethod.Emit(OpCodes.Ldloc_0);
  ilOfMethod.Emit(OpCodes.Ldarg_0);
  ilOfMethod.Emit(OpCodes.Stfld, m_FB_CThis);
  ilOfMethod.Emit(OpCodes.Nop);
  ilOfMethod.Emit(OpCodes.Ldloc_0);
  ilOfMethod.Emit(OpCodes.Ldarg_0);
  ilOfMethod.Emit(OpCodes.Call, propertyBeProxy.GetMethod);
  ilOfMethod.Emit(OpCodes.Callvirt, typeof(object).GetMethod("GetType"));
  ilOfMethod.Emit(OpCodes.Ldstr, m_MethodName);
  ilOfMethod.Emit(OpCodes.Callvirt, typeof(Type).GetMethod("GetMethod", new Type[] { typeof(string) }));
  ilOfMethod.Emit(OpCodes.Stfld, m_FB_MethodInfo);
  ilOfMethod.Emit(OpCodes.Ldarg_0);
  ilOfMethod.Emit(OpCodes.Ldfld, fieldInterceptor);
  ilOfMethod.Emit(OpCodes.Ldarg_0);
  ilOfMethod.Emit(OpCodes.Call, propertyBeProxy.GetMethod);
  ilOfMethod.Emit(OpCodes.Ldnull);
  ilOfMethod.Emit(OpCodes.Callvirt, InterceptType.GetMethod("Before"));
  ilOfMethod.Emit(OpCodes.Nop);
  ilOfMethod.Emit(OpCodes.Ldloc_0);
  ilOfMethod.Emit(OpCodes.Ldftn, nestedTypeMethod);

  ilOfMethod.Emit(OpCodes.Newobj, typeof(Action).GetConstructor(Type.EmptyTypes));

  ilOfMethod.Emit(OpCodes.Call, typeof(Task).GetMethod("Run", new Type[] { typeof(Action) }));    // BindingFlags.Public | BindingFlags.Static
  ilOfMethod.Emit(OpCodes.Stloc_1);
  ilOfMethod.Emit(OpCodes.Ldarg_0);
  ilOfMethod.Emit(OpCodes.Ldfld, fieldInterceptor);
  ilOfMethod.Emit(OpCodes.Ldarg_0);
  ilOfMethod.Emit(OpCodes.Call, propertyBeProxy.GetMethod);
  ilOfMethod.Emit(OpCodes.Ldnull);
  ilOfMethod.Emit(OpCodes.Ldnull);
  ilOfMethod.Emit(OpCodes.Callvirt, InterceptType.GetMethod("After"));
  ilOfMethod.Emit(OpCodes.Nop);
  ilOfMethod.Emit(OpCodes.Ldloc_1);
  ilOfMethod.Emit(OpCodes.Stloc_2);
  
  var methodresult = ilOfMethod.DeclareLocal(m_ReturnType);
  ilOfMethod.Emit(OpCodes.Br_S, methodresult);
  ilOfMethod.Emit(OpCodes.Ldloc_2);
  ilOfMethod.Emit(OpCodes.Ret);

经反复验证, 问题只有一处 ilOfMethod.Emit(OpCodes.Newobj, typeof(Action).GetConstructor(Type.EmptyTypes)); 对应IL语句 IL_0043: newobj instance void [mscorlib]System.Action::.ctor(object, native int) , 此处因Action并没有构造函数的语法, 导致typeof(Action).GetConstructor(Type.EmptyTypes)返回为null, 但根据IL语句显示instance void [mscorlib]System.Action::.ctor(object, native int), 意思是此处需要实例化一个Aciton, 本人摸索良久未能实现, 恳请各路大神伸出援手, 愿奉上我全部的积分, 非常感谢!

补充, 拦截器类:
public class StandardInterceptor
{
public StandardInterceptor()
{
Console.WriteLine("实例化'StandardInterceptor'对象");
}

    public void Before(object m_CurrObj, MethodInfo m_TagMethodInfo)
    {
        Console.WriteLine($"执行 {m_TagMethodInfo?.Name} 的 Before 方法 . . .");
    }

    public void After(object m_CurrObj, MethodInfo m_TagMethodInfo, Task<object> m_ReturnValue)
    {
        Console.WriteLine($"执行 {m_TagMethodInfo?.Name} 的 After 方法, 返回值{m_ReturnValue}");
    }
}
蓝调大仙的主页 蓝调大仙 | 初学一级 | 园豆:101
提问于:2021-02-26 10:37
< >
分享
所有回答(2)
0

你可以用 Castle 库的 DynamicProxy 动态拦截。

darklx | 园豆:206 (菜鸟二级) | 2021-02-26 10:54

请教下大概怎么实现, 谢谢!

支持(0) 反对(0) 蓝调大仙 | 园豆:101 (初学一级) | 2021-02-26 11:24
1

使用 IL.Emit(OpCodes.Newobj, typeof(Action).GetConstructors()[0]) 方式可以获得该构造函数实例.

蓝调大仙 | 园豆:101 (初学一级) | 2021-03-01 10:30
清除回答草稿
   您需要登录以后才能回答,未注册用户请先注册