Search code examples
c#reflectionsystem.reflectionreflection.emittypebuilder

C# Reflection emit invalid program when calling emited method from generic type that implements interface


I am generating a proxy dynamically in C# to wrap over a derived type from an interface, intercepting the methods from the interface and calling another method with the intercepted parameters.

My problem is that for some methods on my generated type, I get an invalid program exception. But for other methods implemented from the interface it works. I use the same code to generate the method implementations of the interface:

var invokeMethod = typeof(ServiceInterceptor).GetMethod(nameof(Invoke), BindingFlags.Instance | BindingFlags.NonPublic) ?? throw new InvalidOperationException("Could not get current method");
var getCurrentMethod = typeof(MethodBase).GetMethod(nameof(MethodBase.GetCurrentMethod), BindingFlags.Static | BindingFlags.Public) ?? throw new InvalidOperationException("Could not get current method");
var interfaceMethods = GetAllInterfaceMethods(interfaceType);
var objectGetTypeMethod = typeof(ServiceInterceptor).GetType().GetMethod(nameof(GetType), BindingFlags.Public | BindingFlags.Instance) ?? throw new InvalidOperationException("Could not get get type object method");
var objectTypeIsValueTypeGetMethod = typeof(ServiceInterceptor).GetType().GetProperty(nameof(Type.IsValueType), BindingFlags.Public | BindingFlags.Instance)?.GetMethod ?? throw new InvalidOperationException("Could not get the method is value type");

foreach (var interfaceMethod in interfaceMethods)
{
    var interfaceMethodTypeBuilder = serviceInterceptorTypeBuilder.DefineMethod(interfaceMethod.Name, interfaceMethod.Attributes & ~MethodAttributes.Abstract, interfaceMethod.CallingConvention);
    builtMethods.Add(interfaceMethodTypeBuilder);
    var interfaceMethodArgumentsType = new List<Type>();
    var interfaceMethodGenericArguments = interfaceMethod.GetGenericArguments();
    var interfaceMethodGenericParametersMap = new Dictionary<Type, GeneraticParameterTypeWithInitialization>();
    if (serviceInterceptorGenericParametersMap != null && serviceInterceptorGenericParametersMap.Any())
    {
        foreach (var parameterMap in serviceInterceptorGenericParametersMap)
        {
            interfaceMethodGenericParametersMap[parameterMap.Key] = parameterMap.Value;
        }
    }
    if (interfaceMethodGenericArguments.Any())
    {
        var genericMethodParameters = interfaceMethodTypeBuilder.DefineGenericParameters(interfaceMethodGenericArguments.Select(arg => arg.Name).ToArray());
        for (int i = 0; i < genericMethodParameters.Length; i++)
        {
            interfaceMethodGenericParametersMap[interfaceMethodGenericArguments[i]] = new GeneraticParameterTypeWithInitialization { GenericTypeParameterBuilder = genericMethodParameters[i] };
        }
        foreach (var interfaceMethodGenericArgument in interfaceMethodGenericArguments)
        {
            foreach (var contraint in interfaceMethodGenericArgument.GetGenericParameterConstraints())
            {
                var replacedConstraint = ReplaceGenericArgumentsAndConstraintsFromType(contraint, interfaceMethodGenericParametersMap);
                if (replacedConstraint.IsInterface)
                {
                    interfaceMethodGenericParametersMap[interfaceMethodGenericArgument].GenericTypeParameterBuilder.SetInterfaceConstraints(replacedConstraint);
                    continue;
                }
                interfaceMethodGenericParametersMap[interfaceMethodGenericArgument].GenericTypeParameterBuilder.SetBaseTypeConstraint(replacedConstraint);
                interfaceMethodGenericParametersMap[interfaceMethodGenericArgument].GenericTypeParameterBuilder.SetGenericParameterAttributes(contraint.GenericParameterAttributes & ~GenericParameterAttributes.Covariant & ~GenericParameterAttributes.Contravariant);
            }
        }
    }
    var interfaceParameters = interfaceMethod.GetParameters();
    var interfaceMethodParameters = interfaceParameters.Select(x => ReplaceGenericArgumentsFromType(x.ParameterType, serviceInterceptorGenericParametersMap)).ToArray();
    interfaceMethodTypeBuilder.SetParameters(interfaceMethodParameters);
    interfaceMethodTypeBuilder.SetReturnType(ReplaceGenericArgumentsFromType(interfaceMethod.ReturnType, serviceInterceptorGenericParametersMap));

    var ilGenerator = interfaceMethodTypeBuilder.GetILGenerator();
    var localOjectParamList = ilGenerator.DeclareLocal(typeof(object[]));
    var localObjectParam = ilGenerator.DeclareLocal(typeof(object));
    var localMethodInfo = ilGenerator.DeclareLocal(typeof(MethodInfo));
    ilGenerator.EmitWriteLine("Calling generated interface method");
    ilGenerator.Emit(OpCodes.Call, getCurrentMethod);
    ilGenerator.Emit(OpCodes.Castclass, typeof(MethodInfo));
    ilGenerator.Emit(OpCodes.Stloc, localMethodInfo);
    if (!interfaceMethod.GetParameters().Any())
    {
        ilGenerator.Emit(OpCodes.Ldarg_0);
        ilGenerator.Emit(OpCodes.Ldloc, localMethodInfo);
        ilGenerator.Emit(OpCodes.Ldnull);
        ilGenerator.Emit(OpCodes.Callvirt, invokeMethod);
        ilGenerator.Emit(OpCodes.Ret);
        continue;
    }

    ilGenerator.Emit(OpCodes.Ldc_I4, interfaceParameters.Length);
    ilGenerator.Emit(OpCodes.Newarr, typeof(object));
    ilGenerator.Emit(OpCodes.Stloc, localOjectParamList);

    for (short parameterIndex = 0; parameterIndex < interfaceParameters.Length; parameterIndex++)
    {
        var loadArgumentLabel = ilGenerator.DefineLabel();
        var loadNextArgumentLabel = ilGenerator.DefineLabel();
        
        ilGenerator.Emit(OpCodes.Ldtoken, interfaceMethodParameters[parameterIndex]);
        ilGenerator.Emit(OpCodes.Call, getTypeFromHandle);
        ilGenerator.Emit(OpCodes.Callvirt, objectTypeIsValueTypeGetMethod);
        ilGenerator.Emit(OpCodes.Brfalse, loadArgumentLabel);
        ilGenerator.EmitWriteLine("Detected value type, boxing it");
        ilGenerator.Emit(OpCodes.Ldarg, (short)(parameterIndex + 1));
        ilGenerator.Emit(OpCodes.Box, interfaceMethodParameters[parameterIndex]);
        ilGenerator.Emit(OpCodes.Stloc, localObjectParam);
        ilGenerator.Emit(OpCodes.Ldloc, localOjectParamList);
        ilGenerator.Emit(OpCodes.Ldc_I4, (int)parameterIndex);
        ilGenerator.Emit(OpCodes.Ldloc, localObjectParam);
        ilGenerator.Emit(OpCodes.Stelem_Ref);
        ilGenerator.Emit(OpCodes.Br, loadNextArgumentLabel);
        ilGenerator.MarkLabel(loadArgumentLabel);
        ilGenerator.EmitWriteLine("Detected reference type,putting it directly in array");
        ilGenerator.Emit(OpCodes.Ldloc, localOjectParamList);
        ilGenerator.Emit(OpCodes.Ldc_I4, (int)parameterIndex);
        ilGenerator.Emit(OpCodes.Ldarg, (short)(parameterIndex + 1));
        ilGenerator.Emit(OpCodes.Stelem_Ref);

        ilGenerator.MarkLabel(loadNextArgumentLabel);
    }

    ilGenerator.Emit(OpCodes.Ldarg_0);
    ilGenerator.Emit(OpCodes.Ldloc, localMethodInfo);
    ilGenerator.Emit(OpCodes.Ldloc, localOjectParamList);
    ilGenerator.Emit(OpCodes.Callvirt, invokeMethod);
    ilGenerator.Emit(OpCodes.Ret);
}

I also make sure to replace the types on interface methods with the generic parameter types on my new dynamically created type.

I don't know why the code to generate method implementation from the interface works for a method , that return something and has a simple parameter and for a method that doesn't return anything and has a parameter of the generic type it doesn't work. It's almost the same code generated. Maybe I need to do something special for methods that return void?


Solution

  • Ensure that for methods returning void, a Ret instruction (indicating the end of the method) is emitted right after the Callvirt instruction. I hope this should resolve your issue.