Click here to Skip to main content
16,018,904 members
Articles / Mobile Apps / Windows Mobile

Injecting Method Calls to Existing Methods of an Assembly

Rate me:
Please Sign up or sign in to vote.
5.00/5 (22 votes)
24 Apr 2012CPOL7 min read 68.9K   1.3K   43   19
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 many 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:

Image 1

  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:

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

The Dummy class is shown below:

VB
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:

Image 2

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:

Image 3

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:

VB
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:

Image 4

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

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

Image 5

  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:

Image 6

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:

Image 7

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.

VB
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.

VB
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)


Written By
Software Developer (Senior)
Brazil Brazil
Coffe-addicted software developer since her 14's

Comments and Discussions

 
QuestionExcellent Article , Thanks !!! Pin
Member 124748114-Dec-16 3:35
Member 124748114-Dec-16 3:35 
QuestionCan you help me expanding on this? Pin
Jason Vetter21-Jan-14 2:46
Jason Vetter21-Jan-14 2:46 
QuestionHi Pin
Kushan Randima26-Jul-13 20:53
Kushan Randima26-Jul-13 20:53 
QuestionNice use of Cecil Pin
Sacha Barber18-Jul-13 8:15
Sacha Barber18-Jul-13 8:15 
QuestionA little step further Pin
mamporrero20-Sep-12 4:12
mamporrero20-Sep-12 4:12 
QuestionFILTER Pin
AnnieCalvert15-Jul-12 20:12
AnnieCalvert15-Jul-12 20:12 
AnswerRe: FILTER Pin
Ana Carolina Zambon23-Jul-12 3:19
Ana Carolina Zambon23-Jul-12 3:19 
GeneralMy vote of 3 Pin
Brian G012830-May-12 8:19
Brian G012830-May-12 8:19 
QuestionThings you can do but shouldnt Pin
Brian G012830-May-12 8:16
Brian G012830-May-12 8:16 
GeneralMy vote of 5 Pin
Monjurul Habib10-May-12 19:26
professionalMonjurul Habib10-May-12 19:26 
GeneralRe: My vote of 5 Pin
Ana Carolina Zambon11-May-12 1:02
Ana Carolina Zambon11-May-12 1:02 
GeneralMy vote of 5 Pin
Marcelo Ricardo de Oliveira2-May-12 12:37
Marcelo Ricardo de Oliveira2-May-12 12:37 
GeneralRe: My vote of 5 Pin
Ana Carolina Zambon2-May-12 12:44
Ana Carolina Zambon2-May-12 12:44 
GeneralGreat job, Carolina, As a Brazilian, I´m proud of you! Pin
Gerson Freire1-May-12 15:50
Gerson Freire1-May-12 15:50 
GeneralRe: Great job, Carolina, As a Brazilian, I´m proud of you! Pin
Ana Carolina Zambon1-May-12 19:22
Ana Carolina Zambon1-May-12 19:22 
QuestionGreat article but I wonder about the wisdom of using method injection Pin
Patrick.Farry30-Apr-12 5:58
Patrick.Farry30-Apr-12 5:58 
AnswerRe: Great article but I wonder about the wisdom of using method injection Pin
Ana Carolina Zambon9-May-12 8:28
Ana Carolina Zambon9-May-12 8:28 
GeneralMy vote of 5 Pin
Pete O'Hanlon24-Apr-12 1:31
mvePete O'Hanlon24-Apr-12 1:31 
GeneralRe: My vote of 5 Pin
Ana Carolina Zambon24-Apr-12 2:00
Ana Carolina Zambon24-Apr-12 2:00 

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

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