在程序正在使用的過程中,常常需要升級DLL。這時,如果dll已經被主程序引用,則無法修改,這樣的需求應該很常見。換個角度,可以理解成程序的升級或者修改Bug的功能。
以下通過動態的加載Dll來解決這個問題。
整個思路的前提是,動態調用的東西和前台需要的功能通過代理IBaseInterface連接起來,也就是說動態dll里面的類和Proxy都需要實現這個接口。
namespace BaseInterface { public interface IBaseInterface { string GetString(); } }
核心代碼:Proxy.dll
namespace Proxy { internal class AppDomainCore { public AppDomain DefaultAppDomain { get; private set; } public string DefaultAppDomainName { get; private set; } public AppDomainCore(string appDomainName) { DefaultAppDomainName = appDomainName; AppDomainSetup setup = new AppDomainSetup(); setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory; setup.ConfigurationFile = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile; Evidence evidence = new Evidence(AppDomain.CurrentDomain.Evidence); DefaultAppDomain = AppDomain.CreateDomain(appDomainName, evidence, setup); } public bool ClearAppDomain() { try { AppDomain.Unload(DefaultAppDomain); DefaultAppDomain = null; return true; } catch { return false; } } ~AppDomainCore() { ClearAppDomain(); } } }
namespace Proxy { internal class AssemblyCore { public string ActivedAssemblyName { get; private set; } public string CurrentType { get; private set; } public FileInfo DefaultAssemblyFile { get; private set; } public AssemblyCore(string assemblyName, string type) { ActivedAssemblyName = assemblyName; CurrentType = type; try { DefaultAssemblyFile = new FileInfo(assemblyName); } catch (Exception ex) { MessageBox.Show(ex.Message); } } } }
namespace Proxy { public class Proxy : BaseInterface.IBaseInterface { AssemblyCore _assemblyCore; AppDomainCore _appDomainCore; public string DefaultAssemblyName { get { return _assemblyCore.ActivedAssemblyName; } } public string DefaultAppDomainName { get { return _appDomainCore.DefaultAppDomainName; } } public Proxy(string assemblyName, string typeName, string appDomainName) { _assemblyCore = new AssemblyCore(assemblyName, typeName); _appDomainCore = new AppDomainCore(appDomainName); } public void UnLoad() { _appDomainCore.ClearAppDomain(); } BaseInterface.IBaseInterface _proxy; public string GetString() { if (_proxy == null) _proxy = _appDomainCore.DefaultAppDomain.CreateInstanceFromAndUnwrap( _assemblyCore.ActivedAssemblyName, _assemblyCore.CurrentType) as BaseInterface.IBaseInterface; return _proxy.GetString(); } } }
以上代碼在項目Proxy里面寫入,是動態加載Dll的核心代碼。
在Proxy里面,其中AppDomainCore根據傳遞的參數生成新的AppDomain, AssemblyCore則根據傳遞的參數生成新的FileInfo. 在Proxy類里面,同時定義一個AssemblyCore和AppDomainCore,根據方法:_appDomainCore.DefaultAppDomain.CreateInstanceFromAndUnwrap(_assemblyCore.ActivedAssemblyName, _assemblyCore.CurrentType), 動態的生成實例,最后用as 轉換成接口IBaseInterface,通過調用接口的GetString方法來調用dll里面的方法。
一下為模擬的兩個Dll類,
namespace Assembly1 { [Serializable] public class ClassLibrary : MarshalByRefObject, BaseInterface.IBaseInterface { public string GetString() { return "Assembly1.ClassLibrary"; } } }
namespace Assembly2 { [Serializable] public class ClassLibrary : MarshalByRefObject, BaseInterface.IBaseInterface { public string GetString() { return "Assembly2.ClassLibrary"; } } }
分別生成到Assembly v1.0.dll和Assembly v2.0.dll中。
最終的界面代碼:
namespace DynamicDll { public partial class Form1 : Form { Proxy.Proxy _proxy; public Form1() { InitializeComponent(); LoadDll("Assembly v1.0.dll", "Assembly1.ClassLibrary", "Domain1"); radV1.Click += (a, b) => LoadDll("Assembly v1.0.dll", "Assembly1.ClassLibrary", "Domain1"); radV2.Click += (a, b) => LoadDll("Assembly v2.0.dll", "Assembly2.ClassLibrary", "Domain2"); } void LoadDll(string assName, string type, string appName) { if (_proxy != null) _proxy.UnLoad(); _proxy = new Proxy.Proxy(assName, type, appName); lblCurrentAppDomain.Text = _proxy.DefaultAppDomainName; lblCurrentAssembly.Text = _proxy.DefaultAssemblyName; lblMainAppDomain.Text = AppDomain.CurrentDomain.FriendlyName; } private void btnRes_Click(object sender, EventArgs e) { txtRes.Text = _proxy.GetString(); } } }
查看項目Dynamic的依賴項,可以很清楚的看到:它只依賴BaseInterface和Proxy兩個項目,與Assembly v1.0和v2.0都無關。
也就是說,程序運行的時候,可以修改Assembly v1.0和v2.0里面的代碼,重新生成,同時替換運行目錄下的dll。