Mobile Development – Compact Framework: Managed Extension Framework (MEF)

Hello

this post describes a way to load class libraries (DLLs) dynamically on demand in your compact framework application.

What is it good for?

Hardware abstraction. Load only matching Libraries.

Organize your development for different devices. You will be able to only load the right hardware (device) dependent libraries. You are able to use a hardware abstraction layer.

Normally, you add a reference to an assembly during development in Visual Studio. Now, when the application starts on the device, it will try to load this assembly. If it contains hardware dependent code, like a barcode scanner library, the load of your application may fail.

Using the techniques described here, you can avoid this and direct your app to only load device matching libraries.

Plugin usage

You can write a Compact Framework application that will load and work with plugins. These reside in DLLs and can be added or removed on demand.

You can for example write an application launcher that will list the installed extensions. In example for a Transport and Logistic application you can have a truck loading and a truck unloading extension. If you have to update one of the plugins, you only need to replace the plugin DLL, the rest of the application (for example Login forms, Information screens, connection management) remains unchanged.

DLL loading in C/C++

Loading libs dynamically is well known in C/C++. You simply use LoadLibrary(dllName) and then you can get the addresses to known library functions and use these.

Static linking: Adding a reference in a compact framework (SmartDevice) application inside Visual Studio is like adding a header and lib file to a C/C++ application.

DLL loading in Compact Framework

You can also use the above in C#/VB.NET.

[codesyntax lang=”csharp”]

namespace ConsoleApplication1
{
  class Program
  {
    static IClass1 GetIClass1(string filename)
    {
      Assembly classLibrary1 = null;
      using (FileStream fs = File.Open(filename, FileMode.Open))
      {
        using (MemoryStream ms = new MemoryStream())
        {
          byte[] buffer = new byte[1024];
          int read = 0;
          while ((read = fs.Read(buffer, 0, 1024))>0)
            ms.Write(buffer, 0, read);
          classLibrary1 = Assembly.Load(ms.ToArray());
        }
      }
      foreach (Type type in classLibrary1.GetExportedTypes())
      {
        if (type.GetInterface("IClass1") != null)
          return Activator.CreateInstance(type) as IClass1;
      }

      throw new Exception("no class found that implements interface IClass1");
    }

    static void Main(string[] args)
    {
      IClass1 class1 = GetIClass1("ClassLibrary1.dll");
      class1.DoSomething();
    }
  }
}

[/codesyntax]

[source: http://social.msdn.microsoft.com/forums/en-US/clr/thread/093c3606-e68e-46f4-98a1-f2396d3f88ca/], may not work without change in CF.

Using writing all the C/C++ LoadLibrary/GetProcAddress or the above for all your library functions  is very time consuming and error-prone.

There is another way using class interfaces and libraries for hardware dependent code described at MSDN: Barcode Scanners and the Compact Framework. The disadvantage of this solution is, that you have to add references to all different possible implementation assemblies: in the example, you have to add a ref to the Intermec barcode scanner implementation and to the symbol barcode scanner. Imagine if you have to manage this for 4 and more manufacturer and not only for barcode scanners but also for other OEM vendor features (WLAN, keyboard).

So, looking for a better solution, I found the Managed Extensibility Framework (MEF) and fortunately a Pocket MEF (the compact framework compatible variant). MEF was developed as a community project with technology previews at codeplex. It now is part of NET 4.0 framework.
Pocket MEF is also hosted a codeplex, it follows the main technology previews until CTP8.1. Although the full MEF has already reached version 2, the Pocket MEF is sufficiant to be very usefull.

Pocket MEF

I will describe how you can use PocketMEF to write an application that will automatically load the right hardware dependent DLLs. We will use PocketMEF to implement a Hardware Abstraction Layer (HAL). PocketMEF will load the right assemblies during runtime on demand using a file name pattern.

Use Interfaces

This step is also used in the article at http://msdn.microsoft.com/en-us/library/aa446489.aspx. Using interfaces or factory classes ensures, that your “plugins” or OEM modules have all the same interface.

Hardware abstraction: Implement the different OEM dependent class libs
Let PocketMEF load the right class libs

Plugin use: Implement different plugins using the same interface
Let PocketMEF load all matching plugins

The sample solution MEFdemo

I have done a sample application that shows both, hardware abstraction and plugin usage. The Visual Studio 2008 Windows Mobile 6 Prof. solution defines a main application, two different contracts (interfaces) and some extensions that fullfill the interfaces.

Unfortunately, the solution is more complex, as it shows both aspects. OK, here is a design schema:

You see, we have one IAppPlugin and one IBarcodeScanControl interface. Then there are two Forms that implement IAppPlugin and one of that uses an IBarcodeScanControl.

The IAppPlugin Interface

namespace MEFdemo1.AppContracts
{
    //describe a public appPlugin interface (contract)
    public interface IAppPlugin : IComponent
    {
        Bitmap appBitmap{ get; }
        System.Windows.Forms.DialogResult DialogResult { get; }
        string sAppText { get; }
        string sReturnData { get; }
    }
}

An IAppPlugin implementation

The simple AppPlugin1 Form implements this interface and exports it.

[codesyntax lang="csharp"]
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

using System.ComponentModel;
using System.ComponentModel.Composition;

using MEFdemo1.AppContracts;

namespace AppPlugin1
{
    [PartCreationPolicy(CreationPolicy.NonShared)]
    [Export(typeof(IAppPlugin))]
    public partial class UserForm1 : Form, IAppPlugin
    {
...
[/codesyntax]

The AppPlugin2 uses IBarcodeControl

This form imports the IBarcodeControl and exports IAppPlugin.

[codesyntax lang="csharp"]
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

using System.ComponentModel.Composition;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Diagnostics;

using MEFdemo1.HAL.DeviceControlContracts;
using MEFdemo1.AppContracts;

namespace AppPlugin2
{
    [PartCreationPolicy(CreationPolicy.NonShared)]
    [Export(typeof(IAppPlugin))]
    public partial class BarcodeForm : Form, IAppPlugin
    {
    ...
    }
        //[Import(typeof(IBarcodeScanControl))]
        //if Import is used here, the catalog is unable to find the control!
        private IBarcodeScanControl conScan;

        DirectoryCatalog catalog2;
        CompositionContainer container2;

        public BarcodeForm()
        {
            InitializeComponent();
            try
            {
                string sPath="";
                if (isIntermec)
                    sPath = "MEFdemo1.HAL.Intermec.*Control*.dll";
                else
                    sPath = "MEFdemo1.HAL.ACME.*Control*.dll";

                //I was unable to use the different catalog and let it look in a subfolder
                //so the plugin names are used as a filter
                catalog2 = new DirectoryCatalog(".", sPath);

                foreach (string s in catalog2.LoadedFiles)
                    System.Diagnostics.Debug.WriteLine(s);

                container2 = new CompositionContainer(catalog2);

                container2.ComposeParts(this);
...
[/codesyntax]

The usable BarcodeControl is loaded by using a different file pattern for the DirectoryCatalog. So only one BarcodeControl is loaded.

The MainForm

The main form scans the application dir for IAppPlugins and lists them using there bitmap property.

[codesyntax lang="csharp"]
...
        //we will import all available plugins
        [ImportMany(typeof(IAppPlugin))]
        private int iPluginCount = 0;

        DirectoryCatalog catalog;
        CompositionContainer container;
...
        public MainForm()
        {
            InitializeComponent();
            try
            {
                catalog = new DirectoryCatalog(".", "MEFdemo1.Plugins.*.dll");
                container = new CompositionContainer(catalog);
                container.ComposeParts(this);
                drawPlugins();
            }
            catch (Exception ex)
            {
                MessageBox.Show("No Plugins loaded: " + ex.Message);
            }
        }
[/codesyntax]

drawPlugins will draw the images inside MainForm using pictureboxes and applies a click handler for these.

[codesyntax lang="csharp"]
        void MainForm_Click(object sender, EventArgs e)
        {
            string sApp = ((PictureBox)sender).Tag.ToString();
            System.Diagnostics.Debug.WriteLine(sApp);
            foreach (IAppPlugin iApp in plugins)
            {
                if (iApp.sAppText.Equals(sApp))
                {
                    ((System.Windows.Forms.Form)iApp).ShowDialog();
                    continue;
                }
            }
        }
[/codesyntax]

Here is a screenshot of the composed parts:

When you click one of the pictureboxes, the plugins form is shown: either the BarcodeForm or the simple UserForm:

 

The programs dir reflects all the contracts and plugins:

intermec.datacollection.cf3.5.dll                            intermec barcode hardware wrapper
intermec.devicemanagement.smartsystem.itcssapi.dll           intermec software wrapper
MEFdemo1.exe                                                 the application
mefdemo1.appcontracts.dll                                    the AppPlugin contract
mefdemo1.devicecontrolcontracts.dll                          the BarcodePlugin contract
MEFdemo1.HAL.ACME.BarcodeControl2.dll                        one BarcodeControl (the generic implementation)
MEFdemo1.HAL.Intermec.BarcodeControl1.dll                    second BarcodeControl (intermec dependent)
MEFdemo1.Plugins.AppPlugin1.dll                              the UserForm application plugin
MEFdemo1.Plugins.AppPlugin2.dll                              the BarcodeForm application plugin
pocket.componentmodel.initialization.dll                     PocketMEF runtime
pocket.system.componentmodel.composition.dll                 PocketMEF runtime

If you remove one of the AppPlugin DLL files and then start MEFdemo, only the remaining Plugin is shown.

Although I implemented all plugins in there own class library projects, you can combine multiple plugins into one project and so will get less DLLs. The disadvantage is that you cant simply update only one these plugins, you have then to replace the whole lib.

Source code is hosted at code.google.com/p/pocketmef/source/

 

Leave a Reply