In my previous post, i enhanced the proxy to support generic calls. In order to minimize IL emit and move more parts to managed code, there is a better way to process the return value rather doing the checks against runtime method and see whether the method’s return type is a value or not for generic calls to un-box the object form that is returned from the interceptor.
Therefore, i replaced the return value processing from my previous with the following
- if (methodInfo.ReturnType != typeof(void))
- {
- ilGenerator.Emit(OpCodes.Ldloc, locMethodInvocation);
- ilGenerator.Emit(OpCodes.Callvirt, typeof(MethodInvocation).GetMethod("get_ReturnValue"));
-
- if (!methodInfo.ReturnType.IsGenericParameter)
- {
- if (methodInfo.ReturnType.IsPrimitive || methodInfo.ReturnType.IsValueType)
- {
- ilGenerator.Emit(OpCodes.Unbox, methodInfo.ReturnType);
- ilGenerator.Emit(OpCodes.Ldobj, methodInfo.ReturnType);
- }
- else if (methodInfo.ReturnType.IsValueType)
- {
- ilGenerator.Emit(OpCodes.Unbox, methodInfo.ReturnType);
- }
- }
- else
- {
- ilGenerator.Emit(OpCodes.Unbox_Any, methodInfo.ReturnType);
- }
- }
Here, for generic return there is a different IL instruction to perform the un-boxing and for that we don’t need the runtime type [done in previous post]. The instruction is same as doing
T ret = (T)returnValue; // where returnValue is in object form
To make the basic proxy look more pretty, i added a silverlight project and added the Xunit tests from C# library that asserts the proxy is compatible in silverlight runtime with no pitfalls.
The Xunit tests are ported to silverlight using the Xunit Light wrapper by Jason Jarett that works seamlessly on top Microsoft Sliverlight Unit Testing Framework and just with an F5 gives you a nice output which can easily be registered by adding the following line in your App.xaml.cs.
- //*******************
- // Register the XUnitTestProvider with the Microsoft.Silverlight.Testing framework.
- UnitTestSystem.RegisterUnitTestProvider(new Microsoft.Silverlight.Testing.UnitTesting.Metadata.XunitLight.XUnitTestProvider());
- //*******************
- this.RootVisual = UnitTestSystem.CreateTestPage();
Finally, its still a simple proxy, plenty of things are missing and i will cover them up overtime but gives a way to get your feet wet with MSIL. You can download the latest proxy here
The link to the previous post follows A basic proxy for intercepting method calls (Part – 3).
Enjoy!!
In my previous posts, I showed how to create a proxy that can delegate calls. I further modified it to support argument list from original method and handled scenarios for void and non-void calls. In this post, i will further enhance it to introduce generic calls.
Basically proxy overrides the virtual or interface calls dynamically and to consider generic methods we just need to set few things on top of the original interception process. Before we begin, let’s just consider a simple method, may be that will make things more clear in terms of visualization.
- public virtual TRet Echo<T, TRet>(T arg1)
- {
- return default(TRet);
- }
To make the proxy to support this type of calls , before we wrap the method and register it with the user provided interceptor we just need to add the following lines
- if (methodInfo.IsGenericMethod)
- {
- Type [] genParams = methodInfo.GetGenericArguments();
-
- string[] genericParamNames = new string[genParams.Length];
-
- int index = 0;
-
- foreach(Type genParam in genParams)
- {
- genericParamNames[index++] = genParam.Name;
- }
-
- methodBuilder.DefineGenericParameters(genericParamNames);
- }
So, MethodBuilder has a magic function named DefineGenericParamters that actually marks the method as generic with specified generic arguments, similar to do it inside <.. > by hands.
Hmm..Looks like we are almost done, but looking at the method we can see that it can return any value. In that case, we have to make sure that the return value is unboxed properly [you can have a look at the previous post, if not already]. Therefore, we need to get the runtime reference of the method that will actually contain user provided types rather the generic ones. In order to get the runtime method reference, we need to consider two things more specifically
- Nothing special , just get the reference though RuntimeMethodHandle :-|
- If the method uses any generic type that is actually defined by the class then you should consider the RuntimeTypeHandle as well.
Considering this, our code for getting runtime method reference will look like:
- LocalBuilder locMethod = ilGenerator.DeclareLocal(typeof(MethodInfo));
-
- ilGenerator.Emit(OpCodes.Ldtoken, method);
- if (target.IsGenericType)
- {
- ilGenerator.Emit(OpCodes.Ldtoken, target);
- ilGenerator.Emit(OpCodes.Call, typeof(MethodBase).GetMethod("GetMethodFromHandle", new[]
- {
- typeof(RuntimeMethodHandle),
- typeof(RuntimeTypeHandle)
- }));
- }
- else
- {
- ilGenerator.Emit(OpCodes.Call, typeof(MethodBase).GetMethod("GetMethodFromHandle", new[]
- {
- typeof(RuntimeMethodHandle)
- }));
- }
- ilGenerator.Emit(OpCodes.Castclass, typeof(MethodInfo));
- ilGenerator.Emit(OpCodes.Stloc, locMethod);
In the previous post, i did a check for return type like
-
- if (methodInfo.ReturnType.IsValueType || methodInfo.ReturnType.IsEnum)
- {
- // unboxing action
- }
Now, we just need to replace this with the following for our new runtime method.
- Label lblExit = ilGenerator.DefineLabel();
- Label lblUnBox = ilGenerator.DefineLabel();
- LocalBuilder locReturnType = ilGenerator.DeclareLocal(typeof(Type));
-
- ilGenerator.Emit(OpCodes.Ldloc, locMethodInvocation);
- ilGenerator.Emit(OpCodes.Callvirt, typeof(MethodInvocation).GetMethod("get_ReturnValue"));
-
- // store the return type to an intermidiate variable.
- ilGenerator.Emit(OpCodes.Ldloc, locRuntimeMethod);
- ilGenerator.Emit(OpCodes.Callvirt, typeof(MethodInfo).GetMethod("get_ReturnType"));
- ilGenerator.Emit(OpCodes.Stloc, locReturnType);
-
- ilGenerator.Emit(OpCodes.Ldloc, locReturnType);
- ilGenerator.Emit(OpCodes.Callvirt, typeof(Type).GetMethod("get_IsValueType"));
- ilGenerator.Emit(OpCodes.Brtrue,lblUnBox);
- ilGenerator.Emit(OpCodes.Ldloc, locReturnType);
- ilGenerator.Emit(OpCodes.Callvirt, typeof(Type).GetMethod("get_IsEnum"));
- ilGenerator.Emit(OpCodes.Brtrue, lblUnBox);
-
- ilGenerator.Emit(OpCodes.Br, lblExit);
-
- ilGenerator.MarkLabel(lblUnBox);
-
- // unbox.
- ilGenerator.Emit(OpCodes.Unbox, methodInfo.ReturnType);
- ilGenerator.Emit(OpCodes.Ldobj, methodInfo.ReturnType);
-
- ilGenerator.MarkLabel(lblExit);
Looks cryptic , but looking closely you will see that there are just two labels and depending on the boolean result it jumps to the un-boxing block and finally for types like string it just jumps to the end.
Tip 1 : Opcodes.BrTrue/BrFalse conditionally takes the IP(instruction pointer) to a particular memory location defined by MarkLabel() based on the value available on top of evaluation stack.
Finally, we can intercept our target method to see everything works (interception code remains same from previous post)
- int result2 = test.Echo<int, int>(10);
-
- if (result2 != 1)
- throw new Exception("result should equal 10");
The latest version can be downloaded from here and happy Coding!!
---
Links to the previous from which the current post is derived.
A basic proxy (Part 2)
A basic proxy (Part 1) I think, it would be nice if i named the series as building a dynamic proxy and adventures with IL :-).