Mehfuz's WebLog

Live crazy, think different!

Sponsors

News

Passionate about cutting edge technologies and facinated by the modern web and phone revolution.Currently working at Telerik Corporation, the leading .net component vendor.
Follow me


Articles


Projects

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 :-).

Posted: Feb 22 2010, 03:37 AM by mehfuzh | with 6 comment(s) |
Filed under: ,

Comments

Jaco Pretorius said:

Might want to include links to part 1 and 2...

# February 23, 2010 2:52 AM

mehfuzh said:

@ Roy  -> you are right. The goal of the post is to build a simpler proxy itself and while doing so .. i am diving into in the interesting details of IL emitting that might be useful for others buiding some xyz tool.

# February 26, 2010 12:24 PM

Dmitry said:

Shouldn't the interceptor expose the MethodInfo instance so you can call the method?

# February 28, 2010 12:44 PM

mehfuzh said:

Awesome Dmtry, i was just waiting for that comment. yes it should, it is a basic proxy remember ;)

# February 28, 2010 2:26 PM