Last time we talked about how to inject code at the beginning of a method. Today I will post how to inject code at the end of a method and I will cover some more complex scenarios as well.

So lets start with sample program and then I will explain it in details.

using System;
using Telerik.CodeWeaver.Hook;
 
namespace ConsoleApplication2
{
    sealed class MyClass
    {
        public void SayHello() { Console.WriteLine("Hello!"); }
 
        public int SayHello(string name)
        {
            Console.WriteLine("Hello {0}!", LastName = name);
            return (name ?? string.Empty).Length;
        }
 
        public string LastName { get; set; }
 
        public int Increment10(ref int i)
        {
            Console.WriteLine("LastName={0}", LastName);
            Console.WriteLine("i={0}", i = i + 10);
            return 0;
        }
 
        public int ThrowException(ref int i)
        {
            throw new NotImplementedException();
        }
    }
 
    class Program
    {
        [WeaverInject]
        delegate int MyPrologueDelegate(ref int i,
                            MyClass target,
                            out bool skipOldMethodBody);
 
        [WeaverInject]
        delegate int MyEpilogueDelegate(ref int i,
                            MyClass target,
                            int ret);
 
        static int Incerement10Prologue(ref int i,

                            MyClass target,

                            out bool skipOldMethodBody)
        {
            skipOldMethodBody = false;
            i = 100;
            target.LastName = "John Smith";
            return 0; // ignored because of skipOldMethodBody = false;
        }
 
        static int ThrowExceptionEpilogue(  ref int i,
                               MyClass target,
                               int ret)
        {
            Console.WriteLine("from epilogue: current i={0}", i);
            Console.WriteLine("from epilogue: current return value={0}", ret);
 
            return 2 * (i += 10);
        }
 
        static void Main(string[] args)
        {
            Weaver<MyClass>
                .AtEndOfAction(_ => _.SayHello())
                .Inject(() =>
                {
                    Console.WriteLine("Hello again!");
                    return true; // return value is ignored
                });
 
            var c = new MyClass();
 
            c.SayHello();
 
            Weaver<MyClass>
                .AtEndOfFunc<string, int>((_, name) => _.SayHello(name))
                .Inject((name, ret) =>
                {
                    Console.WriteLine("Hello again {0}!", name);
                    return ret * ret;
                });
 
            Console.WriteLine(c.SayHello("John"));
 
            Weaver<MyClass>
                .AtStartOfFunc<int, int>((_, i) => _.Increment10(ref i))
                .InjectDelegate<MyPrologueDelegate>(Incerement10Prologue);
 
            Weaver<MyClass>
                .AtEndOfFuncSafe<int, int>((_, i) => _.ThrowException(ref i))
                .InjectDelegate<MyEpilogueDelegate>(ThrowExceptionEpilogue);
 
            Console.WriteLine("LastName={0}", c.LastName);
 
            int val = 0;
            int returnValue = c.Increment10(ref val);
 
            Console.WriteLine("val={0} returnValue={1}", val, returnValue);
 
            returnValue = c.ThrowException(ref val);
            Console.WriteLine("val={0} returnValue={1}", val, returnValue);
        }
    }
}

I defined MyClass type that contains the target methods. The type is sealed and does not implements interfaces. The interesting part of this program is in Main method so lets look there. The first statement  is:

 

Weaver<MyClass>
    .AtEndOfAction(_ => _.SayHello())
    .Inject(() =>
    {
        Console.WriteLine("Hello again!");
        return true; // the return value is ignored
    });

As we know from my previous post we define the target type via Weaver<> generic class. In this first scenario the target method SayHello has no return value (void) so I use AtEndOfAction method otherwise I would use AtEndOfFunc method as we will see in the next scenario. Because SayHello has simple signature I prefer to use lambda to define the code that should be executed at the end. This is very similar as the injecting code via AtStartOfAction method described in my previous post. Looking closely we see that the lambda returns boolean again. This is odd! While in the case of AtStartOfAction the lambda return value has meaning (of workflow control flag) this time the return value has no meaning. This is a known issue and it will be fixed in upcoming releases.

So far we saw how we can inject code at the end of a method that has no return value. As you can guess there is a method AtStartOfFunc that will allow us to inject code at the end of a method that has return value. Lets look at the next statement.

Weaver<MyClass>
    .AtEndOfFunc<string, int>((_, name) => _.SayHello(name))
    .Inject((name, ret) =>
    {
        Console.WriteLine("Hello again {0}!", name);
        return ret * ret;
    });

In this scenario the signature of the target method is:

int SayHello(string name)

so we have to use AtEndOfFunc<string, int>(...) generic method. We define the type parameters as we do with System.Func<...> framework delegate. Compared to AtStartOfFunc scenario this time Inject method accepts slightly different lambda. There is one more parameter that I named “ret”. This parameter holds the return value of target method. In some special cases the target method does not set return value. In these cases “ret” parameter has a default value. I will cover this topic later in this post.

Basically we have access to all parameters of the target method and its return value as well. In this particular case we overwrite the return value of the target method.

So far I used lambda expressions to define method prologue/epilogue. I think it is convenient for simple scenarios. However this is not always possible. In case of methods with ref/out parameters is not possible to use lambda expressions. In case of generic methods it is not possible as well. Looking closely in weaver API you will see we defined AtStartOfXYZ/AtEndOfXYZ overloads that accept up to 9 parameters. So even in a case where we want to inject prologue/epilogue in a method with 10 or more parameters we cannot use lambdas.

The solution is InjectDelegate<TDelegate>(...) method instead Inject(...) method. Lets see this in action. In our next scenario the target method signature is:

int Increment10(ref int I)

The delegate signature for prologue method should be:

[WeaverInject]
delegate int MyPrologueDelegate(ref int i, MyClass target, out bool skipOldMethodBody);

We see a couple of new thing here. First there is a custom attribute applied on the delegate. For now we will ignore it. In the upcoming JustMock release this attribute is removed.

Lets focus on the delegate signature. It resembles the signature of the target method. However there are two additional parameters at the end. The first one has type MyClass. Probably you already guess it :) In case of instance method this argument hold the instance reference. In case of a static method this parameter holds null. This is a very power feature as it allows to have access of the context of the target method. The second parameter should be no surprise. We need it for a flow control support.

Lets see our prologue injection implementation:

static int Incerement10Prologue(ref int i,
                    MyClass target,
                    out bool skipOldMethodBody)
{
    skipOldMethodBody = false;
    i = 100;
    target.LastName = "John Smith";
    return 0; // ignored because of skipOldMethodBody = false;
}

and the way we define it for the target method.

Weaver<MyClass>
      .AtStartOfFunc<int, int>((_, i) => _.Increment10(ref i))
      .InjectDelegate<MyPrologueDelegate>(Incerement10Prologue);

The code looks straightforward. It looks even cleaner than the lambda version. As we can see we are able to change the state of the object instance through “target” parameter.

In the last scenario the target method is

public int ThrowException(ref int i)
{
     throw new NotImplementedException();
}

The delegate signature for epilogue method should be:

[WeaverInject]
delegate int MyEpilogueDelegate(ref int i, MyClass target, int ret);

Again, ignore the custom attribute and lets focus on the delegate signature. There are two additional parameters. The first one we have already described. Lets look at the second one. As we want to inject code at the end of the target method we do not need flow control flag. However we need a parameter for the return value of the target method. This is the role of the last parameter. In case the target method has no return value (void) there is only one additional parameter.

Lets look closely at the target method. We see there is no return statement. Something more – the target method just throws an exception. How can we execute epilogue then? The answer is AtEndOfFuncSafe method.

Weaver<MyClass>
    .AtEndOfFuncSafe<int, int>((_, i) => _.ThrowException(ref i))
    .InjectDelegate<MyEpilogueDelegate>(ThrowExceptionEpilogue);

Basically AtEndOfFuncSafe will wrap the target method in try{...}catch{}finally{...} and will put the epilogue code in finally clause.

There are limitations with AtEndOfXYZ methods. In some cases C# and VB.NET compilers emit MSIL instructions that require a heavy processing how exactly to inject the epilogue code. Thus for performance reasons AtEndOFXYZ methods make assumptions about the method work flow. We've tested the epilogue functionality with a lot of assemblies and it worked quite stable but there's a slight possibility that there could be issues, for example with heavily obfuscated assemblies. Should you find such issues, please let us know.

So far we covered how to inject prologue and epilogue code in non-generic methods. We saw how to inject epilogue code in methods that throw exceptions as well. Keep in mind that weaver API is still evolving and there are expected changes. Feel free to let me know your opinion about current weaver API.

Next time: injecting prologue/epilogue in generic methods.


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.