Click here to Skip to main content
Click here to Skip to main content

Injecting method calls to existing methods of an assembly

, 24 Apr 2012 CPOL
Rate this:
Please Sign up or sign in to vote.
This is a demonstration of how to use the basics of Mono.Cecil by adding method calls to existing assemblies.

Introduction

Since 2008 I became a frequent reader of The Code Project's articles and I have learned a lot from other .NET experts since then.  

This is my first article in this site and my chance to share my knowledge by contributing to the Dev Community. 

Feel free to give me positive or negative feedback through the “Comments and Discussions” section. I would love to hear from you what you have to say about the article and get a chance to learn a bit more.

Background

In 2009/2010, I created a business component for allowing access control, user authentication, and resource protection (Business Objects and UI Objects/Forms) on the client application that was integrated with that component.  

One of the features that this component had was to allow logging the methods that were executed at runtime by a logged on user, for auditing purposes.

The developer using the component did not need to write much lines of code for allowing this feature to work.  

In order to allow the developer to write less code I created an attribute that when added to a method would start logging the Beginning and Ending of the execution of that method and persist the logging information to a database.

The attribute itself does not do much, it was basically used to mark a method that should receive the code injection in the post-compiling – assembly modification – phase.

Overview

The source code that is available for download has been changed in order to make it possible to generalize the way the injected method calls are handled in the "client-code" and not simply logging the beginning and ending of a method call decorated with CodeInjectionAttribute.

In order to implement the code injection / assembly emitting, I evaluated the classes of the namespace System.Reflection.Emit, however I had some difficulties making changes on the target assembly, since in order to modify it, we first need to load it to an AppDomain which in turn causes the loaded assembly to be in use / allocated, making it impossible to change (save) it to disk.

In order to get around this problem, I decided to use Mono.Cecil which is easier to use due to its object model rather than Reflection.Emit and works like a charm for modifying assemblies on disk.   

I apologize to the C# lovers for having source code available only in VB.NET, but I’ve been developing only with VB.NET for the last 6 years and I'm more fluent in VB.NET. When I get a chance I will port it to C# and will make it available for downloading.

I once again apologize for the code comments. There are mixed comments in both English and Portuguese (my native language). As it was initially a commercial component, in order to make it easier to document using SandCastle, code in which visibility is public has comments in English, otherwise in Portuguese. 

The Visual Studio Solution 

The source code of the Visual Studio solution that is available for download is divided into three projects: 

  1. CodeInjection.Framework: The framework that is responsible for injecting method calls to the target methods of an assembly.
  2. CodeInjection.App: Windows Forms app that references the CodeInjection.Framework classes in order to inject method calls into target-methods of a target assembly that exists on disk.
  3. DummyConsoleApp: Console application that will be used in this article as a sample of the target assembly in which some of its methods will be code injected.

You should compile all the mentioned projects.

Using the code 

Let’s take a look at what we should do on methods of a target application in order to prepare it to be code injected. Open the “DummyConsoleApp” project, the only thing this app does is instantiate an object from the Dummy class and invoke Dummy.DoSomething(), as you can see below:

Module Module1
    Sub Main()
        Dim obj As New Dummy
        obj.DoSomething()
        Console.ReadKey()
    End Sub
End Module

The Dummy class is shown below:

CodeInjection.Framework.Attributes.CodeInjectionAttribute(GetType(DummyHandler))
  Public Class Dummy
    CodeInjection.Framework.Attributes.CodeInjectionAttribute()
    Public Sub DoSomething()
        Console.WriteLine("Doing something...")
        DoingOtherThing()
    End Sub
    Private Sub DoingOtherThing()
        Console.WriteLine("Doing other thing...")
        ImDone()
    End Sub
 
    CodeInjection.Framework.Attributes.CodeInjectionAttribute()
    Private Sub ImDone()
        Console.WriteLine("I'm Done!")
    End Sub
End Class

Please note that the Dummy class and the DoSomething() and ImDone() methods are decorated with the CodeInjectionAttribute attribute.

The attribute indicates which methods will be targeted for code injection.

Handling target-methods execution

That is the part of the source code that I've changed since in the original version when a target-method was executed, the beginning and ending information of its execution was persisted to a database for auditing purposes.

In order to provide more flexibility, I have created an overloaded constructor for the CodeInjection attribute which allows you to inform what is the System.Type that implements IMethodHandler interface that will be responsible for handling method calls injected on a target-method of a specific method or class. 

The IMethodHandler interface is shown below:

As the names suggest, each method of this interface is called when a target-method starts and finishes its execution, making it possible to implement any logic you need, like log information to a database or trigger other methods.

In order to find the System.Type that implements IMethodHandler, the framework firstly searches in the CodeInjectionAttribute in the target-method, if it is not found, then the framework searches in the CodeInjectionAttribute of the class where the method resides. When a System.Type is found, it creates an instance of it in order to use it. As you can see in the sequence diagram below:

In the DummyConsoleApp project I have created a class named DummyHandler that implements IMethodHandler which simply writes messages to the console. As you can see below:

Public Class DummyHandler
    Implements CodeInjection.Framework.IMethodHandler
 
    Public Sub MethodBegin(context As CodeInjection.Framework.IInvocationContext) _
           Implements CodeInjection.Framework.IMethodHandler.MethodBegin
        Console.WriteLine(String.Format("Begin of method {0}", context.Name))
    End Sub
 
    Public Sub MethodEnd(context As CodeInjection.Framework.IInvocationContext) _
           Implements CodeInjection.Framework.IMethodHandler.MethodEnd
        Console.WriteLine(String.Format("End of method {0}", context.Name))
    End Sub
End Class

Using CodeInjection.App to inject code on target-methods

In order to make it easier to inject code, I have created a little windows form application named CodeInjection.App which uses CodeInjection.Framework to inject code on target-methods of an assembly on disk.

If we execute the existing DummyConsoleApp from the command prompt, we will get the output below:  

Note that nothing special has happened to the Dummy class code.

Now let's code-inject the target-methods by using CodeInjection.App:

  1. Open the Windows Forms application CodeInjection.App.
  2. Click Browse and go to the folder where the DummyConsoleApp assembly that we just executed is located.
  3. Click the Inject button.

Now try to execute the DummyConsoleApp console application again from the command prompt and take a look at the output:

By looking at the console output we can see that the methods marked with the CodeInjectionAttribute attribute are now doing extra things, which means the code injection has been done. 

If we open this assembly in .NET Reflector and compare it to the original code, we can see the differences: 

Behind the scenes

We already know that classes and methods that will be target of code injection must have CodeInjectionAttribute added to them, but what is the code that effectively injects external method calls to a target method and how does it work?

In order to make it easier for implementing code-injection, I've used Mono.Cecil created by Jean Baptiste Evain, which you can download from here.

In my implementation, the injection of method calls happens in the CodeInjection.Framework.CodeInector class and the external method calls that are injected to a target method are located in the CodeInjection.Framework.Helpers class.

CodeInjection.App executes code injection by providing a path on disk of the assembly to be modified in the constructor of the class CodeInjection.Framework.CodeInjector.

There are some events in this class, however they are necessary only if you need to notify the caller about the progress of the code injection, as you can see in the frmProgress form of the project CodeInjection.App.

The CodeInjector.InjectBeginAndEndMethodCalls() method is used to get the references to the following framework methods:

  • Helpers.OnMethodBegin()
  • Helpers.OnMethodEnd()
  • Helpers.GetMethodContext()

These methods are external to the target-assembly in which its target-methods will be injected.

Private Sub InjectBeginAndEndMethodCalls(ByVal assembly As Mono.Cecil.AssemblyDefinition, _
                                        ByVal targetMethod As Mono.Cecil.MethodDefinition)
    Dim worker = targetMethod.Body.GetILProcessor
    Dim getMethodContext As Mono.Cecil.MethodReference = a_
        ssembly.MainModule.Import(_MethodGetMethodContext)
    Dim beforeExecuteMethodCallStatic As Mono.Cecil.MethodReference = _
        assembly.MainModule.Import(_MethodBeforeExecuteStatic)
    Dim afterExecuteMethodCallStatic As Mono.Cecil.MethodReference = _
        assembly.MainModule.Import(_MethodAfterExecuteStatic)

    InjectCodeOnMethod(beforeExecuteMethodCallStatic, 0, targetMethod, getMethodContext)
    InjectCodeOnMethod(afterExecuteMethodCallStatic, _
        targetMethod.Body.Instructions.Count - 1, targetMethod, getMethodContext)
End Sub

Once the references to the framework methods are obtained, InjectCodeOnMethod() is called twice.

Firstly for injecting the Helpers.OnMethodBegin() method call at the beginning of a target-method body and lastly for injecting Helpers.OnMethodEnd() at the ending of the target-method’s body.

Private Shared Sub InjectCodeOnMethod(ByVal BeginOrEndMethodRef As Mono.Cecil.MethodReference,
                       ByVal positionOnTargetMethod As Integer,
                       ByVal targetmethod As Mono.Cecil.MethodDefinition,
                       ByVal getContextMethodRef As Mono.Cecil.MethodReference)
    Dim IL = targetmethod.Body.GetILProcessor
    Dim GetContextmethodInjection As Instruction = IL.Create(OpCodes.Call, getContextMethodRef)
    Dim BeginOrEndMethodInjection As Instruction = IL.Create(OpCodes.Call, BeginOrEndMethodRef)

    Dim targetMethodBodyInstruction As Instruction = targetmethod.Body.Instructions(positionOnTargetMethod)
    IL.InsertBefore(targetMethodBodyInstruction, GetContextmethodInjection)
    IL.InsertAfter(GetContextmethodInjection, BeginOrEndMethodInjection)
End Sub

Glossary of terms

  • Framework: Set of classes in which its purpose is to promote the code injection on target assemblies.
  • Destination Assembly or Target-Assembly: The assembly in which its methods marked with the CodeInjectionAttribute attribute will be the target of modification.
  • Target-method or Destination-method: Methods marked with the CodeInjectionAttribute attribute that will be modified by the framework in order to add a framework method call at the beginning and ending of the method’s body.   

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Carolina Zambon
Software Developer (Senior)
Brazil Brazil
I am a software developer since I was 13.
In the 90s I started developing for my own BBS (Bulletin Board System) which was run under IBM OS/2 and I needed to extend it in order go provide more services for the paid users.
Since them I started to get to know other programming languages like Basic, C, Clipper, Clipper Summer, DBase, PPLC, ASP3, Visual Basic, SQL and .NET
Despite knowing other languages​​, I am a big fan and enthusiastic in .NET development, specially the intrinsics of Reflection.
I spent the last couple of years focused on creating backoffice/backstage applications.
I'm a N-tier application developer, but I am focused on the development of components and frameworks for business rules and data access.

Comments and Discussions

 
QuestionCan you help me expanding on this? PinmemberJason Vetter21-Jan-14 2:46 
QuestionHi PinmemberKushan Randima26-Jul-13 20:53 
QuestionNice use of Cecil PinmvpSacha Barber18-Jul-13 8:15 
QuestionA little step further Pinmembermamporrero20-Sep-12 4:12 
QuestionFILTER PinmemberAnnieCalvert15-Jul-12 20:12 
AnswerRe: FILTER PinmemberCarolina Zambon23-Jul-12 3:19 
GeneralMy vote of 3 PinmemberBrian G012830-May-12 8:19 
QuestionThings you can do but shouldnt PinmemberBrian G012830-May-12 8:16 
GeneralMy vote of 5 PinmemberMonjurul Habib10-May-12 19:26 
GeneralRe: My vote of 5 PinmemberCarolina Zambon11-May-12 1:02 
GeneralMy vote of 5 PinmvpMarcelo Ricardo de Oliveira2-May-12 12:37 
GeneralRe: My vote of 5 PinmemberCarolina Zambon2-May-12 12:44 
GeneralGreat job, Carolina, As a Brazilian, I´m proud of you! PinmemberGerson Freire1-May-12 15:50 
GeneralRe: Great job, Carolina, As a Brazilian, I´m proud of you! PinmemberCarolina Zambon1-May-12 19:22 
QuestionGreat article but I wonder about the wisdom of using method injection PinmemberPatrick.Farry30-Apr-12 5:58 
AnswerRe: Great article but I wonder about the wisdom of using method injection [modified] PinmemberCarolina Zambon9-May-12 8:28 
GeneralMy vote of 5 PinprotectorPete O'Hanlon24-Apr-12 1:31 
GeneralRe: My vote of 5 PinmemberCarolina Zambon24-Apr-12 2:00 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web01 | 2.8.141015.1 | Last Updated 24 Apr 2012
Article Copyright 2012 by Carolina Zambon
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid