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

CCR Unplugged

, 21 Aug 2009 CPOL
Rate this:
Please Sign up or sign in to vote.
Utilizing the CCR to manage and execute plug-ins.

Introduction

Recently, I had to come up with a solution to a typical problem. Build "something" that has the ability to grow over time, can be easily maintained by future developers, and utilizes CPU resources effectively. Well, that said, I think most of everything we write should probably have these considerations. However, not all challenges are the same, so in this case, I dug up the CCR again; it's never failed me in the past, so why should it now?

I've written this article on the premise that "intermediate" developers will be reading this, therefore, I've left out a lot of code description simply for the reason that it would be unnecessary for me to describe each part. You should have a decent understanding of Predicates and Lambda to understand sections of this example. If you're a learn by example type, then you should probably be OK.

Background

The goal of the project was to create something similar to a WISE process. There had to be a "core" that has the ability to manage new code parts and allocate them appropriately without recompiling the core. Therefore, by utilizing "plugin" techniques and coordinating their execution via the CCR, this could be accomplished quite easily.

The following article deals with the conceptual parts in their basic form, stripped down from the original project in order to demonstrate an effective solution.

Using the Code

I wanted to start by reviewing the sections of the solution.

PluginDirector2.gif

In order to create plug-ins, we'll need two main things. First and foremost is our interface so we can identify the loaded DLLs we find in out Plugin directory. Secondly, we'll create some event arguments to help us determine whether or not our plug-in failed or succeeded.

Make note, in the IPlugin interface, we have to implement an Event (ProcessComplete) used to "callback" to somewhere to inform us the plug-in has exited; not really necessary for this demo; however, in production cases, we probably want to keep an audit trail of events in our application.

We also have our main method "BeginProcess". This is what our "PluginMonitor" will call the moment the monitor is called because of the availability of a new thread. You should probably not treat this as an initialization method or constructor since the "state" of the plug-in will be monitored immediately upon thread attachment.

The final method "Done" should be used to dispose of any object and, if one exists, call the method connected to ProcessComplete. As mentioned before, it's not necessary, but it's probably good practice.

Now, let's do a quick review of the main classes that will do all the work.

PluginDirector.gif

  • PluginProvider: We need an object that will collect all the plug-ins for us. It's pretty straightforward, and can be extracted, modified, and used in anything you might want to incorporate this technique in. It assumes we have a folder called "Plugins" where we will drop all of our DLLs that have inherited the IPlugin interface.
  • PluginMonitor: We will also need something we can wrap around the plug-in to monitor it on the allocated thread. We don't want to do this as part of the PluginProcessor simply because we want to separate the objects based on their duties.
  • PluginProcessor: This is where we want to drop all the CCR logic; it's probably pretty similar to my previous article of creating a threaded sequential logging solution. I wanted to utilize the easiest implementation for this example to get you started, and I will probably revisit this in the future to advance this code a little more. Notice, we have another event "AddProcess". I included this to help us keep track of which plug-ins were being executed simply for helping us understand and see what is happening during runtime.

A quick look at the common interface reveals something pretty standard. Later, I will create a test plug-in we can use to test the "plugin director". We'll simply just copy and paste multiple versions of the same plug-in in the plugins directory.

IPlugin

using System;

namespace Common
{
    public interface IPlugin
    {
        event EventHandler ProcessComplete;
    
        string PluginName { get; set; }

        string PluginDescription { get; set; }

        int PercentComplete { get; set; }
    
        void BeginProcess();

        void Done(PluginEventArgs evenArgs);
    }

    public class PluginEventArgs : EventArgs
    {
        public bool Success { get; set; }
        public Exception Error { get; set; }
    }
}

Now, let's take a look at the PluginProvider. This object simply loads all the appropriate DLLs in the Plugins directory and returns a List of type IPlugin.

PluginProvider

using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using Common;

namespace Director
{
    public static class PluginProvider
    {

        public static List<IPlugin> GetPlugins()
        {
            return FindPlugIns(LoadPlugInAssemblies());
        }

        private static List<Assembly> LoadPlugInAssemblies()
        {
            var dInfo = new DirectoryInfo(Path.Combine(
               Environment.CurrentDirectory, "Plugins"));
            var files = dInfo.GetFiles("*.dll");

            var plugInAssemblyList = new List<Assembly>();

            foreach (var file in files)
                plugInAssemblyList.Add(Assembly.LoadFile(file.FullName));

            return plugInAssemblyList;
        }

        private static List<IPlugin> FindPlugIns(IEnumerable<Assembly> assemblies)
        {
            var availableTypes = new List<Type>();

            foreach (var currentAssembly in assemblies)
                availableTypes.AddRange(currentAssembly.GetTypes());

            var pluginList = availableTypes.FindAll(t => 
              new List<Type>(t.GetInterfaces()).Contains(typeof(IPlugin)));

            return pluginList.ConvertAll(t => Activator.CreateInstance(t) as IPlugin);
        }
    }
}

For those not familiar with Lambda, here's an example of how it can be utilized. In some ways, it reduces readability, but it sure does cut down on the amount of typing you do! Smile | :)

First, we use "FindAll" to gather all of the "IPlugin" types, then we use ConvertAll to return the assembly objects as IPlugin types.

var pluginList = availableTypes.FindAll(t => 
         new List<Type>(t.GetInterfaces()).Contains(typeof(IPlugin)));
return pluginList.ConvertAll(t => Activator.CreateInstance(t) as IPlugin);

And, the final piece of the core "Director" modules is the PluginProcessor. This section deals strictly with the CCR. Please review my previous article on the CCR to help understand the following objects.

Please note, in this example, I am not using "background threading" (Dispatcher object); therefore, you'll need to call the Dispose method in order to exit your application properly.

If you wish to keep track of the initiated plug-ins, make sure to hookup "AddProcess".

PluginProcessor

using System;
using System.Collections.Generic;
using System.Threading;
using Microsoft.Ccr.Core;

namespace Director
{
    public class PluginProcessor : IDisposable
    {
        //Create Dispatcher
        private static readonly Dispatcher MainDispatcher = 
           new Dispatcher(2, ThreadPriority.Normal, false, "ProcessPool");

        //Create Dispatch Queue
        private static DispatcherQueue _mainDispatcherQueue;

        //Message Port
        private static readonly Port<PluginMonitor> MainProcessPort = 
                                new Port<PluginMonitor>();

        //Dispose flag 
        private bool _disposed;

        //Custom Events
        public delegate void ProcessDelegate(PluginMonitor processMonitor);

        public event ProcessDelegate AddProcess;

        public PluginProcessor(IEnumerable<PluginMonitor> processes)
        {
            //Queue all jobs
            foreach (var process in processes)
                MainProcessPort.Post(process);
            
            _mainDispatcherQueue = new DispatcherQueue(
                     "MainDispatcherQueue", MainDispatcher);
            Arbiter.Activate(_mainDispatcherQueue, 
                             Arbiter.Receive(true, MainProcessPort, StartProcess));
        }

        private void StartProcess(PluginMonitor pluginMonitor)
        {
            if (AddProcess != null)
                AddProcess(pluginMonitor);

            pluginMonitor.BeginProcess();
        }

        private void Dispose(bool disposing)
        {
            if (!_disposed)
            {
                if (disposing)
                {
                    MainDispatcher.Dispose();
                    _mainDispatcherQueue.Dispose();
                }
                _disposed = true;
            }
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }
}

Now, all we need is to create a plug-in for ourselves. The following code basically does nothing important, but it emulates output and progress at different intervals. I feel a little obligated to explain some parts of this code simply because some parts just seem silly, but, they do have a true reason. At the beginning of this plug-in, you'll notice something like this:

_pluginID = Convert.ToUInt16(Guid.NewGuid().ToString()[0]);
_random = new Random(_pluginID);

The reason this is there is to help create a truly new "seed" value for the randomizer, simply because you'll end up having a number of plug-ins completing at the same time. Not like it's a big deal, but it's good to see the results of a variable "alive" time for the plug-in. The following code is a complete implementation of an IPlugin inherited class. We just need to take the compiled DLL from the bin and drop it in the Plugins directory of the main application before we execute it. As I mentioned, it really does nothing, it simply creates a variable random length timer to simulate the complete processing time. It changes the description state field to simulate something happening. All for show.

using System;
using System.Threading;
using Common;

namespace MyPlugin
{
    class TestPlugin : IPlugin
    {
        public event EventHandler ProcessComplete;
        private readonly int _pluginID;
        private readonly Random _random;
        private int _completed;
        private Timer _tmr;

        public TestPlugin()
        {
            _pluginID = Convert.ToUInt16(Guid.NewGuid().ToString()[0]);
            _random = new Random(_pluginID);
        }

        public  void BeginProcess()
        {
            _tmr = new Timer(Counter, null, 0, _random.Next(5, 20));
        }

        public  string PluginName
        {
            get { return String.Format("Plugin ID : {0}", _pluginID); }
            set{}
        }

        public  int PercentComplete
        {
            get { return _completed; }
            set {}
        }

        public string PluginDescription { get; set; }
        
        private void Counter(object sender)
        {
            _completed += 1;

            switch (_completed)
            {
                case 1:
                    PluginDescription = "Initializing plugin.";
                    break;
                case 20:
                    PluginDescription = "Gathering plugin information.";
                    break;
                case 40:
                    PluginDescription = "Examining registry";
                    break;
                case 60:
                    PluginDescription = "Examining files.";
                    break;
                case 80:
                    PluginDescription = "Compiling restore point.";
                    break;
                case 90:
                    PluginDescription = "Cleaning up temporary files.";
                    break;
            }

            if (_completed < 100) return;

            _tmr.Dispose();
            Done(new PluginEventArgs { Error = null, Success = true });
        }
        
        public void Done(PluginEventArgs evenArgs)
        {
            if (ProcessComplete != null)
                ProcessComplete(this, evenArgs);
        }
    }
}

And finally, the implementation of the core to some application. In this case, I've created a small console application. First, we call the Provider to give us a list of eligible plug-in objects. We need to create a List of <PluginMonitor> by passing off each "plug-in" to a monitor.

var processMonitors = new List<PluginMonitor>();
var plugins = PluginProvider.GetPlugins();

foreach (var p in plugins)
    processMonitors.Add(new PluginMonitor(p));

Once that is complete, we need to pass all of these ProcessMonitors to the PluginProcessor; it will invoke the CCR and handle all the threading. We also want to hook up the "AddProcess" so we can watch each plug-in execute.

_processor = new PluginProcessor(processMonitors);
_processor.AddProcess += PluginMonitorAddProcess;

Here is the full listing of the implementation:

using System;
using System.Collections.Generic;
using Director;

namespace Interface
{
    class Program
    {
        static void Main(string[] args)
        {
            new MainApp();
        }
    }

    public class MainApp
    {
        private readonly PluginProcessor _processor;

        public MainApp()
        {
            var processMonitors = new List<PluginMonitor>();
            var plugins = PluginProvider.GetPlugins();

            foreach (var p in plugins)
                processMonitors.Add(new PluginMonitor(p));

            _processor = new PluginProcessor(processMonitors);
            _processor.AddProcess += PluginMonitorAddProcess;
        }

        static void PluginMonitorAddProcess(PluginMonitor pluginMonitor)
        {
            Console.WriteLine(string.Format("{0} plugin started.", 
                                            pluginMonitor.PluginName));
        }
    }
}

Running the Code Example

After you compile the application, you need to copy your DLL from the TestPlugin into the folder called Plugins in the same directory the main EXE is located. If the folder does not exit, create it.

You should get something similar to this:

Output.gif

You'll need to review the code to determine the meaning of the output. In general, it is showing, among other things, the current "state and percentage complete" of each plug-in. Graphically, this process looks much nicer, but, hopefully you can take this to the next level.

I hope this article helps you think of new innovations, or sparks your creative side to improve past, present, and future code projects!

Points of Interest

You may want to play with the "Dispatcher" object in the PluginProcessor to get a better understanding of the distribution of processes. Try updating the IPlugin interface to incorporate things like dependency and sorting for execution.

Happy CCRing!

License

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

Share

About the Author

James Cann
Technical Lead Ernst & Young LLP (Canada)
Canada Canada
No Biography provided

Comments and Discussions

 
GeneralExcellent Pinmemberlagrange.c21-Aug-09 23:54 
QuestionWhat's a CCR? PinmemberPIEBALDconsult21-Aug-09 6:55 
AnswerRe: What's a CCR? PinmemberJames Cann21-Aug-09 7:06 
GeneralRe: What's a CCR? PinmemberAndre De Beer22-Aug-09 1:18 
GeneralRe: What's a CCR? PinmemberJames Cann23-Aug-09 14:09 
QuestionRe: What's a CCR? PinmemberPatrick Blackman12-Nov-09 4:36 
AnswerRe: What's a CCR? PinmemberJames Cann12-Nov-09 5:41 
GeneralRe: What's a CCR? PinmemberPatrick Blackman12-Nov-09 15:51 

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 | Terms of Use | Mobile
Web04 | 2.8.141223.1 | Last Updated 21 Aug 2009
Article Copyright 2009 by James Cann
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid