.NET動態調用DLL的方法
很多軟件都是可插拔的,最知名的便是微軟的Windows操作系統。你可以在Windows操作系統上安裝QQ,也可卸掉QQ,這便是可插拔。這里不談Windows的實現,因為太過復雜。本文就談談管理軟件的可插拔的實現。相對Windows操作系統,QQ就是它的一個插件。所以可以簡單的將開發可插拔的軟件分為兩個部分。一個是主應用程序的開發,一個是插件的開發。
比Windows小的,常見的可插拔的軟件是MSN。MSN主應用程序由MS開發,還存在一些MSN插件開發商,國內好像也有不少,這些插件開發商通過在插件中植入廣告獲取利潤。MS不可能提高源代碼給這些開發商,那么MSN的主應用程序和MSN的插件是如何銜接起來的呢。我想應該是MS提供了一些接口和方法供開發商使用,估計有個api之類的東西,所以開發可插拔的應用系統分為三個部分。
1、主應用程序的開發
2、公用接口的開發
3、插件的開發
了解了這些以后,下面通過一個實例來說明。這個實例的原則是可擴展性強,能向下兼用。
業務需求是:老系統每當逢年過節的時候,會通過郵件給用戶發送一些祝福的郵件。現在流行手機和MSN(QQ沒有借口)之后,客戶希望系統能通過手機短信和MSN的消息給用戶送去祝福。現在我們需要開發手機短信和MSN留言兩個插件,然后將它們安裝到系統中去。
實現:
為了簡單起見,這里使用控制臺應用程序,如果你有興趣,可以修改成asp.net或者Windows Form的。
定義兩個接口:
public interface IPluginHost { void AddMenuItem(string name, MenuItemClickedHandler clickHandler); void RegisterComponent<T>(T component) where T : class; void MailNotice(string messaage); } public delegate void MenuItemClickedHandler(string name);
這個接口是主應用程序繼承的,現在只有MailNotice功能, AddMenuItem是供插件調用的方法,創建一個菜單。RegisterComponent是插件向主應用程序提供一些方法。
public interface IPlugin { void Initialize(IPluginHost pluginHost); void DoSomething(); }
上面是插件的接口。
在主應用程序中有一個加載插件的地方。這里的插件是dll,所以我通過反射去加載這些dll。
public void LoadPlugin() { foreach (string fileName in Directory.GetFiles(Directory.GetCurrentDirectory() + "\\" + "Plugins", "*.dll")) { Assembly assembly = Assembly.LoadFile(fileName); foreach (Type pluginType in assembly.GetTypes()) { if (!pluginType.IsPublic || pluginType.IsAbstract || pluginType.IsInterface) continue; Type concreteType = pluginType.GetInterface(typeof(IPlugin).FullName, true); if (concreteType != null) { IPlugin plugin = (IPlugin)Activator.CreateInstance(pluginType); plugin.Initialize(this); pluginList.Add(plugin); break; } } } }
主應用程序執行的代碼如下:
void Start() { //郵件發送祝福 MailNotice("中秋快樂"); //加載插件 LoadPlugin(); //運行插件 if (pluginList.Count > 0) { foreach (IPlugin plugin in pluginList) { plugin.DoSomething(); } } Console.ReadLine(); }
運行結果如下:
開發兩個插件,都繼承IPlugin。
手機短信通知插件:
public class PluginA : IPlugin { public void Initialize(IPluginHost pluginHost) { IPluginHost myApplication = (IPluginHost)pluginHost; myApplication.AddMenuItem("Click me", OnClick); } private void OnClick(string name) { Console.WriteLine("Omg! You clicked me!"); } public void DoSomething() { Console.WriteLine("手機短信通知:中秋快樂"); } }
MSN通知插件:
public class PluginB : IPlugin { public void Initialize(IPluginHost pluginHost) { IPluginHost myApplication = (IPluginHost)pluginHost; myApplication.AddMenuItem("Click me", OnClick); } private void OnClick(string name) { Console.WriteLine("Omg! You clicked me!"); } public void DoSomething() { Console.WriteLine("MSN信息通知:中秋快樂"); } }
插件的目錄如下圖:
運行效果:
擴展性和兼容性:
如果我想在主應用程序中添加一個ShowMessageBox方法。而且這個方法供插件調用。考慮到版本的兼容性,公開的接口是不能修改的。比如:將主應用程序的接口修改成:
public interface IPluginHost { void AddMenuItem(string name, MenuItemClickedHandler clickHandler); void RegisterComponent<T>(T component) where T : class; T GetComponent<T>() where T : class; void MailNotice(string messaage); void ShowMessageBox(string message); }
那么如何實現呢,很簡單,使用依賴注入的方式。添加下面接口:
public interface IMessageBoxHost { void ShowMessageBox(string message); }
通過主應用程序的構造函數,將MessageBoxHost對下崗注入到主應用程序,在通過插件的構造函數,將其注入插件之中。
主應用程序的構造函數:
public Program(IMessageBoxHost messageBoxHostInstance) { this.messageBoxHostInstance = messageBoxHostInstance; }
插件構造函數:
public PluginA(IMessageBoxHost messageBoxHost) { this.messageBoxHost = messageBoxHost; }
修改實例化插件的代碼:
IPlugin plugin = (IPlugin)Activator.CreateInstance(pluginType, new object[] { messageBoxHostInstance });
這樣我們對版本兼容有了保障。
總結:本文閑談了可插拔應用程序的開發原理,文章的后面提供了插件和應用程序之間版本兼容的一種方案。有討論才有進步,歡迎各位留言。
參考:A Flexible Plugin System A more extensible way to build plugin system