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?
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.