CInject – Extending code injection with plugins

If you are new to CInject, please consider reading this article

Plugins? Yes, you can now create CInject plugins to customize CInject for your needs.  To develop a plugin, you do not need to master C# language, or understand Reflection, or Mono Cecil.  All you need to know is concepts of inheritance, and a little about working of CInject.  So lets not beat around the bush and get to the point.

How do I create a plugin?

To create your own plugin, create a Class Library project in Visual Studio 2010.  Let’s name the project as CInject.Plugin.Sample. The project output will be an assembly whose name should match the format

CInject.Plugin.*.dll

 

image

 

So lets right click the Visual Studio Project and click on Properties. In the tab “Application”, change the Assembly Name to CInject.Plugin.SamplePlugin. Next, we need to add reference to some assemblies

Adding Project References

  • CInject.PluginInterface.dll – This assembly is shipped with CInject and exposes interface IPlugin that we need to implement
  • System.ComponentModel.Composition.Codeplex – This assembly can be downloaded from CodePlex (MEF), or from the CInject package.
  • System.Drawing – This assembly is required for implementing Plugin Menus.
  • System.Windows.Form – This assembly is required as CInject is a Windows Application

image

Implemeting IPlugin

 

Let’s create a class called SamplePlugin.cs.  The name of the class has nothing to do with the name of the plugin so you can name it as you want.  This class needs to be decorated with Export attribute of MEF framework (found in namespace System.ComponentModel.Composition).  The Export attribute has 2 constructors, but for CInject we will use the constructor that takes one parameter i.e. Type.  So we decorate it with

[Export(typeof(IPlugin))]

Since we are developing the plugin for CInject, the class needs to implement the interface IPlugin and should have a valid Name, Version, Menu and an implementation of OnMessageReceived and HandleError.  Let’s understand the significance of the each member of IPlugin interface

  • Name – This is the name of the plugin [To be used in the next release]
  • Version – This is the version of the plugin [To be used in the next release]
  • Menu – If you want your plugin to display a Menu you can implement it; othewise, you can choose to return a NULL value (as shown in the code below)
  • OnStart – This method is called when the plugin is loaded into memory
  • OnClose – This method is called when the plugin is unloaded from the memory. This method should clean up the resources from the memory to allow successful closure of the application
  • OnMessageReceived – This method receives the events & messages from the CInject application. This method should ideally contain logic to handle the events and perform some plugin related logic
  • HandleError – Any technical unhandled errors raised while execution of OnMessageReceived, OnStart, and OnClose will be caught in HandleError method. This method should not throw any exception and should be written very carefully

 

SamplePlugin code
  1. using CInject.PluginInterface;
  2. using System.ComponentModel.Composition;
  3. namespace CInject.Plugin.Sample
  4. {
  5.     [Export(typeof(IPlugin))]
  6.     public class SamplePlugin : IPlugin
  7.     {
  8.         public string Name
  9.         {
  10.             get { return “SamplePlugin”; }
  11.         }
  12.         public string Version
  13.         {
  14.             get { return “1.0”; }
  15.         }
  16.         public System.Windows.Forms.ToolStripMenuItem Menu
  17.         {
  18.             get
  19.             {
  20.                 return null; // NULL – plugin does not have any menu
  21.             }
  22.         }
  23.         public void OnMessageReceived(EventType eventType, Message message)
  24.         {
  25.             // Implement logic for Message Received from CInject Application            
  26.         }
  27.         public void HandleError(System.Exception exception)
  28.         {
  29.             // This should never throw any exception            
  30.         }
  31.         public void OnStart()
  32.         {
  33.             // Should read configuration, initialize variables, etc
  34.         }
  35.         public void OnClose()
  36.         {
  37.             // Should clean up the memory resources
  38.         }
  39.     }
  40. }

 

Handling the events & messages

 

There are multiple events that CInject application raises. Let’s have a quick look at them

  • ProcessStart – Called when overall processing starts
  • MethodInjectionStart – Called before an assembly method is injected
  • MethodInjectionComplete – Called after an assembly method is injected
  • ProcessComplete – Called when overall processing completes
  • TargetAssemblyLoaded – Called when a target assembly is loaded
  • InjectionAssemblyLoaded – Called when an injection assembly is loaded
  • ApplicationStarted – Called when CInject application has started
  • ApplicationClosing – Called when CInject application is closing
  • Error – Called when an error occurs within CInject application.  This is different from HandleError that handles the unhandled exceptions raised by plugin code

 

OnMessageReceived code
  1. public void OnMessageReceived(EventType eventType, PluginInterface.Message message)
  2.         {
  3.             switch (eventType)
  4.             {
  5.                 case EventType.ApplicationClosing:
  6.                 case EventType.ApplicationStarted:
  7.                     break;
  8.                     // Here Error property will be null
  9.                     // Only Result will be non-null  
  10.                     MessageBox.Show(“I received a injection related message: “ + message.Result);
  11.                 case EventType.TargetAssemblyLoaded:
  12.                 case EventType.InjectionAssemblyLoaded:
  13.                     break;
  14.                     // Here Error property will be null
  15.                     // Only Result will be non-null with injector/target assembly path
  16.                     MessageBox.Show(“I received a injection related message: “ + message.Result);
  17.                 case EventType.MethodInjectionStart:
  18.                 case EventType.MethodInjectionComplete:
  19.                     // Here Error property will be null
  20.                     // Expect Target, Injector will be non-null
  21.                     MessageBox.Show(“I received a injection related message: “ + message.ToString());
  22.                     break;
  23.                 case EventType.ProcessComplete:
  24.                 case EventType.ProcessStart:
  25.                     // Here Error property will be null
  26.                     // Expect only the Result to be non-null
  27.                     MessageBox.Show(“I received a process related message: “ + message.Result);
  28.                     break;
  29.                 case EventType.Error:
  30.                     MessageBox.Show(“I received an error message: “ + message.Error);
  31.                     break;
  32.             }
  33.         }

 

Compiling and deploying

You can now compile the class library to create a plugin named CInject.Plugin.SamplePlugin.dll and deploy it in Plugins sub-folder in the CInject application.  The CInject application automatically scans for

  • Assemblies that match the name pattern CInject.Plugin.AnyNameHere.dll
  • Searches for IPlugin implementation(s) with Export attribute within the searched assembly CInject.Plugin.AnyNameHere.dll
  • Loads them in memory and raises events OnStart, OnMessageReceived (with EventType ApplicationStarted) in same order
  • Unloads them from memory when application is closed and raises events OnClose, OnMessageReceived (with EventType ApplicationClosing) in same order

 

image

Related Posts: