Search code examples
c#reflectiondelegatesdynamicmethod

Create weak delegate of Dynamic Method(ref object,object[] arr)


Source code:https://www.pastefile.com/4mzhyg

I'm trying to create delegate of format:

    public delegate TReturn MethodCallerR<TTarget, TReturn>(ref TTarget target, object[] args);

 /// <summary>
    /// Generates a strongly-typed open-instance delegate to invoke the specified method
    /// </summary>
    public static MethodCallerR<TTarget, TReturn> DelegateForCallR<TTarget, TReturn>(this MethodInfo method) {

        int key = GetKey<TTarget, TReturn>(method, kMethodCallerName);
        Delegate result;
        if (cache.TryGetValue(key, out result))
            return (MethodCallerR<TTarget, TReturn>)result;

        return GenDelegateForMember<MethodCallerR<TTarget, TReturn>, MethodInfo>(
                method, key, kMethodCallerName, GenMethodInvocationR<TTarget>,
                typeof(TReturn), typeof(TTarget).MakeByRefType(), typeof(object[]));
    }

weak type function:

    public static MethodCallerR<object, object> DelegateForCallR(this MethodInfo method) {
        return DelegateForCallR<object, object>(method);
    }

delegate creator:

   static TDelegate GenDelegateForMember<TDelegate, TMember>(TMember member, int key, string dynMethodName,
            Action<TMember> generator, Type returnType, params Type[] paramTypes)
    where TMember : MemberInfo
    where TDelegate : class {
        var dynMethod = new DynamicMethod(dynMethodName, returnType, paramTypes, true);

        emit.il = dynMethod.GetILGenerator();
        generator(member);

        var result = dynMethod.CreateDelegate(typeof(TDelegate));
        cache[key] = result;
        return (TDelegate)(object)result;
    }

and IL code generator:

 static void GenMethodInvocationR<TTarget>(MethodInfo method) {



        var weaklyTyped = typeof(TTarget) == typeof(object);



        // push arguments in order to call method
        var prams = method.GetParameters();
        var imax = prams.Length;
        for (int i = 0; i < imax; i++) {

            emit.ldarg1()        // stack<= paramsValuesArray[] //push array
            .ldc_i4(i)        // stack<= index push(index)
            .ldelem_ref();    // stack[top]<=paramsValuesArray[i]

            var param = prams[i];
            var dataType = param.ParameterType;

            if (dataType.IsByRef)
                dataType = dataType.GetElementType();

            emit.unbox_any(dataType);

            emit.declocal(dataType);

            emit.stloc(i);

        }



        if (!method.IsStatic)
        {
            var targetType = weaklyTyped ? method.DeclaringType : typeof(TTarget);
            emit.ldarg0();  //stack[top]=target;
            emit.ldind_ref();//stack[top]=ref target;
            if (weaklyTyped)
                emit.unbox_any(targetType); //stack[top]=(TargetType)target;
        }


        //load parms from local 'list' to evaluation 'steak'
        for (int i = 0; i < imax; i++) {
            var param = prams[i];

            emit.ifbyref_ldloca_else_ldloc(i, param.ParameterType);
        }

        // perform the correct call (pushes the result)
        emit.callorvirt(method);


        //check of ref and out params and
        for (int i = 0; i < prams.Length; i++) {

            var paramType = prams[i].ParameterType;
            if (paramType.IsByRef)
            {
                var byRefType = paramType.GetElementType();
                emit.ldarg1() // stack<= paramsValuesArray[]
                .ldc_i4(i) // stack<= i //push(index)
                .ldloc(i); // stack<= list[i] //push local list element at 'i' on steak
                if (byRefType.IsValueType)
                    emit.box(byRefType);   // if ex. stack[top] =(object)stack[top]
                emit.stelem_ref(); //  // paramsValuesArray[i]= pop(stack[top]);
            }
        }

        if (method.ReturnType == typeof(void))
            emit.ldnull();
        else if (weaklyTyped)
            emit.ifvaluetype_box(method.ReturnType);

        emit.ret();


    }

Example for stuct I'm using is Vector3 with Set method:

  public struct Vector3{
    public float x;
    public float y;
    public float z;

    public Vector3(float x,float y,float z){
        this.x=x;
        this.y=y;
        this.z=z;
    }

    public void Set(float x,float y,float z){
        this.x=x;
        this.y=y;
        this.z=z;
    }
}

so I'm doing:

   object vector3Obj=new Vector3(4,5,6);
    MethodInfo method=typeof(Vector3).GetMethod("Set");
      MethodCallerR<object,object> m = methodInfo.DelegateForCallR();
             m(ref vector3Obj,new object[]{1f,2f,3f});
    Console.Write(vector3.x);

Never get to the executing delegate, but it blows when it calls Delegate.CreateDelegate:

see line: dynMethod.CreateDelegate(typeof(TDelegate));

with error:

InvalidProgramException: Invalid IL code in (wrapper dynamic-method) object:MC<> (object&,object[]): IL_004f: call 0x00000009 refereeing that actual IL code has error at emit.call(method), but when I use helper function:

    FastReflection.GenDebugAssembly<object>("my.dll",null,null,methodInfo,vector3Obj.GetType(),new Type[]{typeof(float),typeof(float),typeof(float)});

which will generate my.dll and open with ILSpy I can see that method from same IL code is generated just fine.

    public static object MethodCallerR(ref object ptr, object[] array)
{
    float num = (float)array[0];
    float num2 = (float)array[1];
    float num3 = (float)array[2];
    ((Vector3)ptr).Set(num, num2, num3);
    return null;
}

Solution

  • I found solution. First although generated method was correct wasn't setting reference passed. So I tried to generated another method.

    public static object MethodCallerR(ref object ptr, object[] array)
    {
        float num = (float)array[0];
        float num2 = (float)array[1];
        float num3 = (float)array[2];
        Vector3 vector = (Vector3)ptr;
        vector.Set(num, num2, num3);
        ptr = vector;
        return null;
    }
    

    So I changed IL code to:

          static void GenMethodInvocationR<TTarget>(MethodInfo method) {
    
    
    
    
    
            //this version winxalex generates more optimized code
            //Generated
            //            public static object MethodCallerR(ref object ptr, object[] array)
            //            {
            //                float num = (float)array[0];
            //                float num2 = (float)array[1];
            //                float num3 = (float)array[2];
            //                Vector3 vector = (Vector3)ptr;
            //                vector.Set(num, num2, num3);
            //                ptr = vector;
            //                return null;
            //            }
            //
            var weaklyTyped = typeof(TTarget) == typeof(object);
            var targetType = weaklyTyped ? method.DeclaringType : typeof(TTarget);
            var isNotStatic = !method.IsStatic;
            LocalBuilder targetLocal = null;
    
            // push arguments in order to call method
            var prams = method.GetParameters();
            var imax = prams.Length;
            for (int i = 0; i < imax; i++) {
    
                emit.ldarg1()        // stack<= paramsValuesArray[] //push array
                .ldc_i4(i)        // stack<= index push(index)
                .ldelem_ref();    // stack[top]<=paramsValuesArray[i]
    
                var param = prams[i];
                var dataType = param.ParameterType;
    
                if (dataType.IsByRef)
                    dataType = dataType.GetElementType();
    
                emit.unbox_any(dataType);
    
                emit.declocal(dataType);
    
                emit.stloc(i);
    
            }
    
    
    
            if (isNotStatic)
            {
    
                emit.ldarg0();  //stack[top]=target;
                emit.ldind_ref();//stack[top]=ref target;
                if (weaklyTyped)
                    emit.unbox_any(targetType); //stack[top]=(TargetType)target;
    
                targetLocal = emit.declocal(targetType); //TargetType tmpTarget; list[0]=tmpTarget;
    
                emit.stloc(targetLocal)     //list[0]=stack.pop();
                .ifclass_ldloc_else_ldloca(targetLocal, targetType);
            }
    
    
            //load parms from local 'list' to evaluation 'steak'
            for (int i = 0; i < imax; i++) {
                var param = prams[i];
    
                emit.ifbyref_ldloca_else_ldloc(i, param.ParameterType);
            }
    
            // perform the correct call (pushes the result)
            emit.callorvirt(method);
    
    
            if (isNotStatic && targetType.IsValueType) {
                emit.ldarg0().ldloc(targetLocal).box(targetType).stind_ref();
            }
    
    
            //check of ref and out params and
            for (int i = 0; i < prams.Length; i++) {
    
                var paramType = prams[i].ParameterType;
                if (paramType.IsByRef)
                {
                    var byRefType = paramType.GetElementType();
                    emit.ldarg1() // stack<= paramsValuesArray[]
                    .ldc_i4(i) // stack<= i //push(index)
                    .ldloc(i); // stack<= list[i] //push local list element at 'i' on steak
    
                    if (byRefType.IsValueType)
                        emit.box(byRefType);   // if ex. stack[top] =(object)stack[top]
                    emit.stelem_ref(); //  // paramsValuesArray[i]= pop(stack[top]);
                }
            }
    
            if (method.ReturnType == typeof(void))
                emit.ldnull();
            else if (weaklyTyped)
                emit.ifvaluetype_box(method.ReturnType);
    
            emit.ret();
    
    
    
            //vexe orignial modified by winxalex to use reference of object (ref obj) and to return value to reference
    
            //GENERATES
            //            public static object MethodCallerR(ref object ptr, object[] array)
            //            {
            //                Vector3 vector = (Vector3)ptr;
            //                float num = (float)array[0];
            //                float arg_56_1 = num;
            //                float num2 = (float)array[1];
            //                float arg_56_2 = num2;
            //                float num3 = (float)array[2];
            //                vector.Set(arg_56_1, arg_56_2, num3);
            //                ptr = vector;
            //                return null;
            //            }
    
    
            //            var weaklyTyped = typeof(TTarget) == typeof(object);
            //            var targetType = weaklyTyped ? method.DeclaringType : typeof(TTarget);
            //            // push target if not static (instance-method. in that case first arg is always 'this')
            //            if (!method.IsStatic)
            //            {
            //
            //                emit.declocal(targetType); //TargetType tmpTarget; list[0]=tmpTarget;
            //                emit.ldarg0();  //stack[0]=target;
            //                emit.ldind_ref();//stack[top]=ref target;
            //                if (weaklyTyped)
            //                    emit.unbox_any(targetType); //stack[0]=(TargetType)target;
            //                emit.stloc0()     //list[0]=stack.pop();
            //                .ifclass_ldloc_else_ldloca(0, targetType);
            //                // if (type.IsValueType) stack[0]=list[0].address, else stack[0]=list[0];
            //                // if (type.IsValueType) emit.ldloca(idx); else emit.ldloc(idx); return this;
            //            }
            //
            //            // if method wasn't static that means we declared a temp local to load the target
            //            // that means our local variables index for the arguments start from 1
            //            int localVarStart = method.IsStatic ? 0 : 1;
            //
            //            // push arguments in order to call method
            //            var prams = method.GetParameters();
            //            for (int i = 0, imax = prams.Length; i < imax; i++) {
            //                emit.ldarg1()        // stack<= paramsValuesArray //push array
            //                .ldc_i4(i)        // stack<= index push index
            //                .ldelem_ref();    // pop array, index and push array[index]
            //
            //                var param = prams[i];
            //                var dataType = param.ParameterType;
            //
            //                if (dataType.IsByRef)
            //                    dataType = dataType.GetElementType();
            //
            //                var tmp = emit.declocal(dataType);
            //                emit.unbox_any(dataType)
            //                .stloc(tmp)
            //                .ifbyref_ldloca_else_ldloc(tmp, param.ParameterType);
            //
            ////v2
            //
            //
            ////                emit.unbox_any(dataType);
            ////
            ////                emit.declocal(dataType);
            ////                emit.stloc(i+localVarStart)
            ////                .ifbyref_ldloca_else_ldloc(i+localVarStart, param.ParameterType);
            //
            //
            //            }
            //
            //            // perform the correct call (pushes the result)
            //            emit.callorvirt(method);
            //
            //
            //            if (!method.IsStatic && targetType.IsValueType)
            //                emit.ldarg0().ldloc0().box(targetType).stind_ref();
            //
            //
            //            for (int i = 0; i < prams.Length; i++) {
            //                var paramType = prams[i].ParameterType;
            //                if (paramType.IsByRef)
            //                {
            //                    var byRefType = paramType.GetElementType();
            //                    emit.ldarg1() // stack<= params[]
            //                    .ldc_i4(i) // stack<= i
            //                    .ldloc(i + localVarStart); // stack<= list[i+localVarStart]
            //                    if (byRefType.IsValueType)
            //                        emit.box(byRefType);   // if ex. stack =(object)stack[top]
            //                    emit.stelem_ref(); //  // stack:paramsValuesArray[i]= list[i+localVarStart];
            //                }
            //            }
            //
            //            if (method.ReturnType == typeof(void))
            //                emit.ldnull();
            //            else if (weaklyTyped)
            //                emit.ifvaluetype_box(method.ReturnType);
            //
            //            emit.ret();
    
    
        }