IL 为用户定义的函数生成代理<输入,返回>

Posted

技术标签:

【中文标题】IL 为用户定义的函数生成代理<输入,返回>【英文标题】:IL Generate Proxy to User Define Func<TInput, TReturn> 【发布时间】:2020-03-14 10:39:22 【问题描述】:

我正在尝试动态生成一个程序集来调用用户创建的函数。

private MethodBuilder DefineGetMethod<TInput, TReturn>(
     TypeBuilder tb, MethodDescriptor methodInfo, Func<TInput, TReturn> dynamicMethod)

    //Define the function
    var dynamicMethodBuilder = tb.DefineMethod(methodInfo.MethodName,
              MethodAttributes.Public,
              methodInfo.ReturnType, methodInfo.InputParameters.Select(x => x.Type).ToArray());

    //Define the labels for the method inputs
    for(var i = 0; i < methodInfo.InputParameters.Length; i++ )
    
        // Position 0 is the return value, 1 is the 1st param, 2 is 2nd, etc.
        var position = 1 + i;
        var inputParam = methodInfo.InputParameters[i];
        dynamicMethodBuilder.DefineParameter(position, ParameterAttributes.None, inputParam.Name);
    

    var ilGenerator = dynamicMethodBuilder.GetILGenerator();

    //Loads arg1
    ilGenerator.Emit(OpCodes.Ldarg_1);

    //Not sure how to pass the arg1 to the method body to return
    var ilMethodBody = dynamicMethod.Method.GetMethodBody().GetILAsByteArray();

    //Generates return
    ilGenerator.Emit(OpCodes.Ret);


编辑 我反编译了现有代码,以与反编译的现有代码类似的方式调用该方法,但我仍然无法让它工作

// Argument 1 of dynamic method is argument array.
myMethodIL.Emit(OpCodes.Ldarg_1);
myMethodIL.Emit(OpCodes.Callvirt, method.Method);
myMethodIL.Emit(OpCodes.Stloc_0);
myMethodIL.Emit(OpCodes.Ldloc_0);
myMethodIL.Emit(OpCodes.Ret);

如何将加载的参数传递给ilMethodBody 并返回?

【问题讨论】:

ilGenerator.Emit(OpCodes.Callvirt, dynamicMethod.Method) 怎么样? 哦。看起来很有希望,那会传递参数吗?今晚晚些时候我会测试它 【参考方案1】:

编辑 - 约翰尼 5

原来你可以对现有的方法信息做一个跳转语句:

myMethodIL.Emit(OpCodes.Jmp, method.Method);
myMethodIL.Emit(OpCodes.Ret);

首先,您必须确保TReturn 等于methodInfo.ReturnTypeTInput 等于methodInfo.InputParameters 中的第一个。

如果dynamicMethod 是一个静态方法委托,那就很简单了:

private MethodBuilder DefineGetMethod<TInput, TReturn>(TypeBuilder tb, MethodDescriptor methodInfo, Func<TInput, TReturn> dynamicMethod)

    var dynamicMethodBuilder = tb.DefineMethod(methodInfo.MethodName,
                MethodAttributes.Public | MethodAttributes.HideBySig,
                methodInfo.ReturnType, methodInfo.InputParameters.Select(x => x.Type).ToArray());

    var ilGenerator = dynamicMethodBuilder.GetILGenerator();

    ilGenerator.Emit(OpCodes.Ldarg_1);
    ilGenerator.Emit(OpCodes.Call, dynamicMethod.Method);
    ilGenerator.Emit(OpCodes.Ret);

    return dynamicMethodBuilder;

但是如果dynamicMethod 可能是一个实例方法(lambda 是一个实例方法),那就很难了。调用实例方法需要先push实例入栈,但Emit只允许push常量值如int、string。

我只能想到一种方式,声明一个字段来存储dynamicMethod,通过reflect构建类型后设置字段值:

private Type DefineGetMethod<TInput, TReturn>(TypeBuilder tb, MethodDescriptor methodInfo, Func<TInput, TReturn> dynamicMethod)

    var fieldBuilder = tb.DefineField("_func", dynamicMethod.GetType(), FieldAttributes.Private | FieldAttributes.Static);

    var dynamicMethodBuilder = tb.DefineMethod(methodInfo.MethodName,
                MethodAttributes.Public | MethodAttributes.HideBySig,
                methodInfo.ReturnType, methodInfo.InputParameters.Select(x => x.Type).ToArray());

    var ilGenerator = dynamicMethodBuilder.GetILGenerator();

    // load static field _func onto stack
    ilGenerator.Emit(OpCodes.Ldsfld, fieldBuilder);
    // load arg1 onto stack
    ilGenerator.Emit(OpCodes.Ldarg_1);
    // call _func.Invoke(..)
    ilGenerator.Emit(OpCodes.Callvirt, dynamicMethod.GetType().GetMethod("Invoke"));
    ilGenerator.Emit(OpCodes.Ret);

    var type = tb.CreateType();
    var field = type.GetField("_func", BindingFlags.NonPublic | BindingFlags.Static);
    // store dynamicMethod into static field _func
    field.SetValue(null, dynamicMethod);
    return type;

编辑

测试代码:

class Program

    static void Main(string[] args)
    
        OnlyStaticFunc();
        StaticField();
    

    static void OnlyStaticFunc()
    
        Func<string, int> func = int.Parse;

        var assemblyName = new AssemblyName("StaticFuncTest");
        var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name);
        var typeBuilder = moduleBuilder.DefineType("Abc", TypeAttributes.Public);
        var methodBuilder = typeBuilder.DefineMethod("Execute", MethodAttributes.Public | MethodAttributes.HideBySig, typeof(int), new[]  typeof(string) );

        var il = methodBuilder.GetILGenerator();
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Call, func.Method);
        il.Emit(OpCodes.Ret);

        var type = typeBuilder.CreateType();
        var abc = Activator.CreateInstance(type);
        var value = ((dynamic)abc).Execute("123");
        Console.WriteLine($"only static func: value");
    

    static void StaticField()
    
        Func<string, int> func = s => int.Parse(s);

        var assemblyName = new AssemblyName("StaticFieldTest");
        var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
        var moduleBuilder = assemblyBuilder.DefineDynamicModule(assemblyName.Name);
        var typeBuilder = moduleBuilder.DefineType("Abc", TypeAttributes.Public);
        var fieldBuilder = typeBuilder.DefineField("_func", func.GetType(), FieldAttributes.Private | FieldAttributes.Static);
        var methodBuilder = typeBuilder.DefineMethod("Execute", MethodAttributes.Public | MethodAttributes.HideBySig, typeof(int), new[]  typeof(string) );

        var il = methodBuilder.GetILGenerator();
        il.Emit(OpCodes.Ldsfld, fieldBuilder);
        il.Emit(OpCodes.Ldarg_1);
        il.Emit(OpCodes.Callvirt, func.GetType().GetMethod("Invoke"));
        il.Emit(OpCodes.Ret);

        var type = typeBuilder.CreateType();
        var field = type.GetField("_func", BindingFlags.NonPublic | BindingFlags.Static);
        field.SetValue(null, func);
        var abc = Activator.CreateInstance(type);
        var value = ((dynamic)abc).Execute("456");
        Console.WriteLine($"static field: value");
    

【讨论】:

谢谢,今晚有机会测试后,我会将其标记为完成 这不是我想要的,但它确实有效,所以我不能抱怨 :) 我知道怎么做,我编辑了你的答案,所以现在你有 3 种不同的方法来完成它 :D 新知识,明白了

以上是关于IL 为用户定义的函数生成代理<输入,返回>的主要内容,如果未能解决你的问题,请参考以下文章

Autofac高级用法之动态代理

如何编辑dll文件

VBA 用户定义函数返回奇怪或没有结果

SQL自定义函数返回记录

hive常用函数

为啥原始类型和用户定义类型在从函数返回为“const”时表现不同?