In my previous post , i introduced a basic proxy that intercepts methods. But what is missing in the proxy is that it does not consider method arguments and can not handle return types. In this post, i will enhance the proxy to support exactly those.
First of all, i modified the IIntercept.Intercept() to accept IInvocaiton interface. The interface is pretty simple and it consists of an arguments array and a method that will be used to set the return value from interceptor.
- /// <summary>
- /// Contains Invocation details.
- /// </summary>
- public interface IInvocation
- {
- /// <summary>
- /// Gets the invocation arguments.
- /// </summary>
- object[] Arguments { get; }
- /// <summary>
- /// Sets the return value.
- /// </summary>
- /// <param name="value"></param>
- void SetReturn(object value);
- }
Internally, the interface is implemented with a class named MethodInovcation , where the arguments and return type is passed through the constructor. As, arguments will be passed dynamically during method call, I first declared a LocalBuilder that will actually take the arguments and wrap it around an array of objects which will be then passed to the constructor. Therefore, i created an extension method that will emit the necessary IL from method’s argument types.
- LocalBuilder args = ilGenerator.DeclareLocal(typeof(object[]));
-
- ilGenerator.Emit(OpCodes.Ldc_I4_S, parameterTypes.Length);
- ilGenerator.Emit(OpCodes.Newarr, typeof(object));
- ilGenerator.Emit(OpCodes.Stloc, args);
-
- if (parameterTypes.Length > 0)
- {
- ilGenerator.Emit(OpCodes.Ldloc, args);
-
- for (int index = 0; index < parameterTypes.Length; index++)
- {
- ilGenerator.Emit(OpCodes.Ldc_I4_S, index);
- ilGenerator.Emit(OpCodes.Ldarg, index + 1);
-
- Type parameterType = parameterTypes[index];
-
- if (parameterType.IsValueType || parameterType.IsGenericParameter)
- ilGenerator.Emit(OpCodes.Box, parameterType);
-
- ilGenerator.Emit(OpCodes.Stelem_Ref);
- ilGenerator.Emit(OpCodes.Ldloc, args);
- }
-
- ilGenerator.Emit(OpCodes.Stloc, args);
- }
The OpCodes.NewArray instruction declares an array from the target with the length pushed on the top of evaluation(EV) stack. The most interesting part in this code is how to take an argument and assign that to our array . OpCodes.Stelem_Ref actually assigns the value from the top of evaluation stack to the index that is mentioned through Opcodes.Ldc_I4_S where OpCodes.Larg [ 1 .. n ] pushes the argument to the top of EV stack.Outside the declaring of LocalBuilder through extension looks like:
- LocalBuilder locParameters = ilGenerator.DeclareParameters(paramTypes);
Here, we also need to declare a variable that will contain the instance of MethodInvocation
- LocalBuilder locMethodInvocation = ilGenerator.DeclareLocal(typeof (MethodInvocation));
Just before Intereptor.Intercept() is called (Let’s assume it from previous post), the following code is added.
- ilGenerator.Emit(OpCodes.Ldloc, locParameters);
- ilGenerator.Emit(OpCodes.Ldtoken, methodInfo.ReturnType);
- ilGenerator.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null);
- ilGenerator.Emit(OpCodes.Newobj, typeof(MethodInvocation).GetConstructor( new []
- {
- typeof(object[]), typeof(Type)
- }));
- ilGenerator.Emit(OpCodes.Stloc, locMethodInvocation);
- ilGenerator.Emit(OpCodes.Ldloc, locMethodInvocation);
Another interesting part is how we dynamically pass the method’s return value from interceptor. OpCodes.Ldtoken loads the type’s token from which by calling GetTypeFromHandle the type is resolved in runtime and passed to the constructor.If you ever used an IL dissembler to look under the hood, you find it pretty common. Once, the extra lines are added to existing basic proxy , its time to check if things are doing just fine. To simplify, method will be called with an integer which will be returned by the interceptor.
- var proxy = new Proxy(typeof(TestClass));
- var test = (TestClass) proxy.Create(new BasicInterceptor());
-
- int result = test.TestCall(1);
-
- if (result != 1)
- throw new Exception("result should equal 1");
Thus, the existing basic interceptor will covert into following :
- public class BasicInterceptor : IInterceptor
- {
- public void Intercept(IInvocation invocation)
- {
- System.Console.WriteLine("Intercepted");
-
- invocation.SetReturn(invocation.Arguments[0]);
- }
- }
We are setting the return from the argument that is passed in. As previously the MethodInvocation is assigned to a local variable,after the interception the value is get and unboxed(value type/enum should be unboxed from object).
- if (methodInfo.ReturnType != typeof(void))
- {
- ilGenerator.Emit(OpCodes.Ldloc, locMethodInvocation);
- ilGenerator.Emit(OpCodes.Callvirt, typeof(MethodInvocation).GetMethod("get_ReturnValue"));
-
- if (methodInfo.ReturnType.IsValueType ||
- methodInfo.ReturnType.IsEnum)
- {
- ilGenerator.Emit(OpCodes.Unbox, methodInfo.ReturnType);
- ilGenerator.Emit(OpCodes.Ldobj, methodInfo.ReturnType);
- }
- }
The snippet is placed just before OpCodes.Ret. The OpCodes.Callvirt calls the ReturnValue property from MehodInvocation where the value is set and pushes on the top of EV stack. OpCodes.Ret returns with whatever the value is on the top of EV stack. The top has to be empty for void calls therefore an extra check is added.
Also, for value types we just can’t put null on EV stack, we will definitely end up with an “JIT encountered and internal limitation” error. Therefore, return value in MethodInvocation should go through the following check which is equivalent to default(value) call.
- if (returnValue == null && returnType.IsValueType)
- {
- returnValue = Activator.CreateInstance(returnType);
- }
Finally, there are plenty of things that are missing in the proxy comparing to real ones but gives out a good example for playing with MSIL. The updated proxy can be downloaded from here .
Happy coding.
Edit : Removed the download , please check the latest post for one.
In this post, i am going to show how you can write your own proxy for delegating calls. This just shows a way how you can handle it on your own but for complex interceptions its always wise to use alpha/beta/tested solutions. The post is more of an under the hood or aims to solve simple interception tasks where you might not need a full featured dynamic proxy or can be useful in building something that requires similar techniques which can move you further.
Let’s start by considering a simple class
- public class TestClass
- {
- public virtual void TestCall()
- {
- throw new Exception("Failed.");
- }
- }
Here, during the creation of proxy i will hook the method with an interceptor that will alternate the execution process from original code. At a glace, our interceptor looks like:
- public interface IInterceptor
- {
- void Intercept();
- }
As it shows, we are going to do a fairly basic implementation.To start, let’s create an implementation on it named BasicInterceptor where inside the Intercept call it just prints a line.
- public class BasicInterceptor : IInterceptor
- {
- public void Intercept()
- {
- System.Console.WriteLine("Intercepted");
- }
- }
Once, we are done with our proxy the result code will look something like below:
- var proxy = new Proxy(typeof(TestClass));
- var test = (TestClass) proxy.Create(new BasicInterceptor());
-
- test.TestCall();
And the above test.TestCall() call Instead of throwing an exception, it will print our expected line. We can here see that I first created the proxy with the given type/class then during the instantiation i hooked that up with our interceptor. Hence, on my first step I created a class deriving from the specified type and expanded its constructor(s) to take an IInterceptor implementation as parameter.
Accordingly, I first created a TypeBuilder instance from System.Refleciton.Emit which is pretty much common in all kind and that looks like:
- AssemblyName assemblyName = new AssemblyName("BasicProxy");
- AssemblyBuilder createdAssembly = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run);
- // define module
- this.moduleBuilder = createdAssembly.DefineDynamicModule(assemblyName.Name);
That is followed by:
- this.typeBuilder =
- this.moduleBuilder.DefineType(target.FullName, TypeAttributes.Class | TypeAttributes.Public, target);
In the proxy , I defined a field named “interceptor” that will be assigned with the passed-in user-defined implementation.
- this.fldInterceptor = this.typeBuilder.DefineField("interceptor", typeof (IInterceptor), FieldAttributes.Private);
Now, for each constructor let’s expand it to have IInterceptor as first argument, call the corresponding base and set it to our defined field.
- Type[] parameters = new Type[1];
-
- ParameterInfo[] parameterInfos = constructor.GetParameters();
- parameters = new Type[parameterInfos.Length + 1];
-
- parameters[0] = typeof(IInterceptor);
-
-
- for (int index = 1; index < parameterInfos.Length; index++)
- {
- parameters[index] = parameterInfos[index].ParameterType;
- }
-
- ConstructorBuilder constructorBuilder =
- typeBuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, parameters);
- ILGenerator generator = constructorBuilder.GetILGenerator();
-
-
-
- generator.Emit(OpCodes.Ldarg_0);
-
- for (int index = 1; index < parameters.Length; index++)
- {
- generator.Emit(OpCodes.Ldarg, index + 1);
- }
-
- generator.Emit(OpCodes.Call, constructor);
-
- generator.Emit(OpCodes.Ldarg_0);
- generator.Emit(OpCodes.Ldarg_1);
- generator.Emit(OpCodes.Stfld, fldInterceptor);
- generator.Emit(OpCodes.Ret);
Tip 1 : OpCodes.Ldarg_0 is like “this” context, which is not needed for static calls. If your are calling a field of the class or referring method arguments, always start with LdArgs_0 or you might end up with “JIT encountered an internal limitation” error.
Tip 2: In MSIL every method must terminate with OpCodes.Ret regardless its void or not. The difference between void and non-void calls is that the top of evaluation stack is not empty.
Now, as we set it up , we need to hook the interceptor in each method that is marked as virtual. First, let’s create the target method attribute :
- const MethodAttributes targetMethodAttributes =
- MethodAttributes.Public | MethodAttributes.Virtual | MethodAttributes.HideBySig;
Next, for each method that is not final hook the IInterceptor.Intercept() call.
- if (methodInfo.IsVirtual)
- {
- Type[] paramTypes = GetParameterTypes(methodInfo.GetParameters());
-
- MethodBuilder methodBuilder =
- typeBuilder.DefineMethod(methodInfo.Name,targetMethodAttributes, methodInfo.ReturnType, paramTypes);
-
- ILGenerator ilGenerator = methodBuilder.GetILGenerator();
-
- ilGenerator.Emit(OpCodes.Ldarg_0);
- ilGenerator.Emit(OpCodes.Ldfld, fldInterceptor);
- ilGenerator.Emit(OpCodes.Callvirt, typeof(IInterceptor).GetMethod("Intercept"));
-
- ilGenerator.Emit(OpCodes.Ret);
- }
Again, before loading the interceptor field on to the evaluation stack , I used Ldarg_0 or “this” equivalent . If you notice, you will see that I have used OpCodes.Callvirt instead of OpCodes.Call. This will actually call the base method rather the implemented. This difference is important if you need to distinguish between base and implemented calls. Unless otherwise, it is better to go with OpCodes.Callvirt that will do the work for you if “base” is something not in your consideration.
Finally, you need to create the type and instance that will be initiated by Proxy.Create() call.
- Type proxy = this.typeBuilder.CreateType();
- return Activator.CreateInstance(proxy, args);
That’s it, we ended up with a simple proxy. In the next post, i will enhance it with parameters consideration and show how to handle return value from proxy.
Happy new year !!
Edit : Removed the download , please check the latest post for one.