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.
plug in 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 plug in 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.
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();
}
}
}
[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 manufacturers 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 sufficient to be very useful.
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 here. Using interfaces or factory classes ensures that your “plugins” or OEM modules all have the same interface.
Hardware abstraction: Implement the different OEM dependent class libs.
Let PocketMEF load the right class libs.
plug in 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 plug in 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
{
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.
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
{
...
}
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";
catalog2 = new DirectoryCatalog(".", sPath);
foreach (string s in catalog2.LoadedFiles)
System.Diagnostics.Debug.WriteLine(s);
container2 = new CompositionContainer(catalog2);
container2.ComposeParts(this);
...
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 their bitmap property.
...
[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);
}
}
drawPlugins
will draw the images inside MainForm
using pictureboxes
and applies a click handler for these.
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;
}
}
}
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 plug in
MEFdemo1.Plugins.AppPlugin2.dll the BarcodeForm application plug in
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 plug in is shown.
Although I implemented all plugins in their own class library projects, you can combine multiple plugins into one project and so will get less DLLs. The disadvantage is that you can't simply update only one of these plugins, you have then to replace the whole lib.
Source code is hosted at code.google.com/p/pocketmef/source/.