A basic proxy for intercepting method calls (Part – 3)

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.

  1. public virtual TRet Echo<T, TRet>(T arg1)
  2. {
  3.     return default(TRet);
  4. }

 

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

  1. if (methodInfo.IsGenericMethod)
  2. {
  3.     Type [] genParams =  methodInfo.GetGenericArguments();
  4.  
  5.     string[] genericParamNames = new string[genParams.Length];
  6.  
  7.     int index = 0;
  8.  
  9.     foreach(Type genParam  in genParams)
  10.     {
  11.         genericParamNames[index++] = genParam.Name;
  12.     }
  13.  
  14.     methodBuilder.DefineGenericParameters(genericParamNames);
  15. }

 

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

  1. Nothing special , just get the reference though RuntimeMethodHandle :-|
  2. 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:

  1. LocalBuilder locMethod = ilGenerator.DeclareLocal(typeof(MethodInfo));
  2.  
  3. ilGenerator.Emit(OpCodes.Ldtoken, method);
  4. if (target.IsGenericType)
  5. {
  6.     ilGenerator.Emit(OpCodes.Ldtoken, target);
  7.     ilGenerator.Emit(OpCodes.Call, typeof(MethodBase).GetMethod("GetMethodFromHandle", new[]
  8.     {
  9.            typeof(RuntimeMethodHandle),
  10.            typeof(RuntimeTypeHandle)
  11.     }));
  12. }
  13. else
  14. {
  15.     ilGenerator.Emit(OpCodes.Call, typeof(MethodBase).GetMethod("GetMethodFromHandle", new[]
  16.     {
  17.            typeof(RuntimeMethodHandle)
  18.     }));
  19. }
  20. ilGenerator.Emit(OpCodes.Castclass, typeof(MethodInfo));
  21. ilGenerator.Emit(OpCodes.Stloc, locMethod);

 

In the previous post, i did a check for return type like

  1.  
  2. if (methodInfo.ReturnType.IsValueType || methodInfo.ReturnType.IsEnum)
  3. {
  4.     // unboxing action
  5. }

Now, we just need to replace this with the following for our new runtime method.

  1. Label lblExit = ilGenerator.DefineLabel();
  2. Label lblUnBox = ilGenerator.DefineLabel();
  3. LocalBuilder locReturnType = ilGenerator.DeclareLocal(typeof(Type));
  4.  
  5. ilGenerator.Emit(OpCodes.Ldloc, locMethodInvocation);
  6. ilGenerator.Emit(OpCodes.Callvirt, typeof(MethodInvocation).GetMethod("get_ReturnValue"));
  7.  
  8. // store the return type to an intermidiate variable.
  9. ilGenerator.Emit(OpCodes.Ldloc, locRuntimeMethod);
  10. ilGenerator.Emit(OpCodes.Callvirt, typeof(MethodInfo).GetMethod("get_ReturnType"));
  11. ilGenerator.Emit(OpCodes.Stloc, locReturnType);
  12.  
  13. ilGenerator.Emit(OpCodes.Ldloc, locReturnType);
  14. ilGenerator.Emit(OpCodes.Callvirt, typeof(Type).GetMethod("get_IsValueType"));
  15. ilGenerator.Emit(OpCodes.Brtrue,lblUnBox);
  16. ilGenerator.Emit(OpCodes.Ldloc, locReturnType);
  17. ilGenerator.Emit(OpCodes.Callvirt, typeof(Type).GetMethod("get_IsEnum"));
  18. ilGenerator.Emit(OpCodes.Brtrue, lblUnBox);
  19.  
  20. ilGenerator.Emit(OpCodes.Br, lblExit);
  21.  
  22. ilGenerator.MarkLabel(lblUnBox);
  23.  
  24. // unbox.
  25. ilGenerator.Emit(OpCodes.Unbox, methodInfo.ReturnType);
  26. ilGenerator.Emit(OpCodes.Ldobj, methodInfo.ReturnType);
  27.  
  28. 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)

  1. int result2  = test.Echo<int, int>(10);
  2.  
  3. if (result2 != 1)
  4.     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 :-).

4 Comments

Comments have been disabled for this content.