Last time we saw how to inject code in properties and static methods (including ones defined in static types). So far we are able to inject code in most common scenarios. In rare cases there is a need to isolate methods from mscorlib. In this post I will show you how do to this.

Let's start with a demo program that I will explain in details.

using System;
using System.IO;
using System.Threading;
 
using Telerik.CodeWeaver.Hook;
 
namespace ConsoleApplication5
{
    [WeaverMsCorlib]
    class Program
    {
        static void DeleteFile(string path)
        {
            new FileInfo(path).Delete();
        }
 
        static void ThreadSleep(int milliseconds)
        {
            Thread.Sleep(milliseconds);
        }
 
        static int RandomNext(int maxValue)
        {
            return new Random().Next(maxValue);
        }
 
        static void Main(string[] args)
        {
            Weaver<FileInfo>
                .ForAction(_ => _.Delete())
                .StartReplace(fi =>
                {
                    Console.WriteLine("Delete operation for {0}", fi.Name);
                });
 
            DeleteFile(typeof(Program).Assembly.Location);
 
            Weaver<Random>
                .ForFunc<int, int>((_, maxValue) => _.Next(maxValue))
                .StartReplace((rnd, maxValue) => 0);
 
            Console.WriteLine("(Not) Random value = {0}", RandomNext(1000));
 
            Weaver<Thread>
                .ForStaticAction<int>(milliseconds => Thread.Sleep(milliseconds))
                .StartReplace(milliseconds =>
                {
                    Console.WriteLine("Sleep for {0}", milliseconds);
                });
 
            ThreadSleep(Timeout.Infinite);
 
            Weaver<DateTime>
                .ForStaticGet<DateTime>(() => DateTime.Now)
                .StartReplace(() => new DateTime(1900, 1, 1));
 
            Console.WriteLine("Now {0}", DateTime.Now);
        }
    }
}

The first thing we notice is that our class that makes usage of mscorlib methods is decorated with WeaverMsCorlib attribute.

[WeaverMsCorlib]
class Program {…}

This attribute can be applied on types only. When JIT compilation starts JustMock profiler component looks at the declaring type and checks if there is WeaverMsCorlib attribute. In case there is one JustMock replaces all method calls with the corresponding binding (in next paragraph I will explain what binding is). This is different from AtStartOf.../AtEndOf... methods where actual code injection is performed. Let's see it in action. For this scenario I will isolate FileInfo.Delete() method.

Weaver<FileInfo>
    .ForAction(_ => _.Delete())
    .StartReplace(fi =>
    {
        Console.WriteLine("Delete operation for {0}", fi.Name);
    });

As usual we define the target type via Weaver<...> generic type. Then there is a difference – we use ForAction(...) method to define our target method. The analogy with AtStartOfAction(...) should be obvious. Yet another difference – instead Inject(...) method we use StartReplace(...) method to define the method replacement. The whole method chain statement is called method binding. As I mentioned earlier method bindings are used during JIT compilation. That's the reason to use DeleteFile helper method.

DeleteFile(typeof(Program).Assembly.Location);

When JIT compilation starts method binding has already taken place and JustMock profiler component knows how to replace FileInfo.Delete() method. There are a few exceptions from that rule and I will tell you more after a few paragraphs. In general the following code fragment is incorrect.

Weaver<FileInfo>
    .ForAction(_ => _.Delete())
    .StartReplace(fi =>
    {
        Console.WriteLine("Delete operation for {0}", fi.Name);
    });
new FileInfo(typeof(Program).Assembly.Location).Delete();

This is because there is no time to create a binding for the target method. As far as you have extra frame in your call stack JustMock will be able to replace the target method defined in mscorlib.

In this scenario the target method FileInfo.Delete() is an instance one and JustMock provides support to access the instance object. There is one caveat. If you want to call the actual target method (fi.Delete()) then you should be more careful. Don't forget that the lambda you provide to StartReplace(...) will be compiled to a method. Be careful if this method resides in a type that is decorated with WeaverMsCorlib attribute. If this is the case there is a risk for infinite recursion and StackOverflowException. There is an easy solution – define your helper methods in a separate type or define your target method replacements elsewhere.

Let's see the second scenario.

Weaver<Random>
    .ForFunc<int, int>((_, maxValue) => _.Next(maxValue))
    .StartReplace((rnd, maxValue) => 0);
 
Console.WriteLine("(Not) Random value = {0}", RandomNext(1000));

Here we substitute the call to Random.Next(int) with a function that returns zero. We use ForFunc(...) to specify the target method. The analogy with AtStartOfFunc(...) should be obvious.

The third scenario demonstrates how to isolate static method from mscorlib.

Weaver<Thread>
    .ForStaticAction<int>(milliseconds => Thread.Sleep(milliseconds))
    .StartReplace(milliseconds =>
    {
        Console.WriteLine("Sleep for {0}", milliseconds);
    });
 
ThreadSleep(Timeout.Infinite);

In that case we use ForStaticAction(...) to define our target method Thread.Sleep(int). In this case StartReplace(...) method is slightly different – as the target method is static there is no support for the object instance.

Our last example is more interesting.

Weaver<DateTime>
    .ForStaticGet<DateTime>(() => DateTime.Now)
    .StartReplace(() => new DateTime(1900, 1, 1));
 
Console.WriteLine("Now {0}", DateTime.Now);

The interesting part is not how we define the target property (again the analogy with AtStartOfStaticGet(...) should be obvious) but in the lack of helper method. DateTime.Now is use right after the method biding statement. There is no extra frame in the call stack. This is one of the exceptions I mentioned earlier. Actually this code fragment is not exception from the rule. JustMock has a few predefined method bindings and DateTime.Now is one of them. These predefined method bindings call the original implementation. The code fragment above “overwrites” the default binding for DateTime.Now property.

So far we saw how to isolate methods from mscorlib. We learned that JustMock replaces the method invocation rather to perform actual code injection. This is the reason for the extra frame in the call stack. We know we have to use ForAction/ForFunc + StartReplace(...) instead of AtStartOfAction/AtStartOfFunc + Inject(...) method set.

As you know weaver API is still in progress and we expect some changes. We consider to unify the API for non-mscorlib methods with the one for mscorlib methods. Instead of using two different method sets (ForAction+ForFunc/AtStartOfAction+AtStartOfFunc) there will be only one (probably AtStartOfAction/AtStartOfFunc). The semantics will be different in case of mscorlib methods but it seems this functionality is used mostly by experienced developers so the trade-off is fair. We consider to change the actual method names (and their signatures) as well. If you have suggestion please let me know. Any feedback is appreciated. Another major change we consider is removing all predefined method bindings. We could keep the extra call stack frame approach and simple syntax or we could remove the frame but use more heavier (in a way) syntax. The decision is not easy but so far we consider the easier syntax of greater importance. Please feel free to share what you prefer.

So far we saw the most common scenarios for using JustMock weaver library. There are other interesting things about it and I will keep posting. Next releases could introduce AOP functionality for example. Though JustMock is version 1.0 there is a functionality to offer and potential to grow. Feel free to suggest any feature you like to see in next release.

Stay tuned.


About the Author

Mehfuz Hossain

works as the Technical Advisor in DevTools Division . He is passionate playing around with the latest bits. He has been a Microsoft MVP, author of OS projects like LinqExtender and LINQ to flickr and most recently lighter (nodejs + mongo blogging engine). Prior to working at Telerik , Mehfuz worked as a core member in many high volume web applications including Pageflakes that is acquired by Live Universe in 2008. He is a frequent blogger and was also a contributor and site developer at dotnetslackers.com.

@mehfuzh

Comments

Comments are disabled in preview mode.