2013-04-04 32 views
3

A)編譯C#EXE和動態鏈接庫相對容易。 B)執行EXE意味着運行一個新的應用程序。加載DLL意味着可以在應用程序或項目之間共享的情況下使用方法和函數。

現在,編譯EXE最快和最簡單的方法(或輕度修改,DLL)可以從MSDN或爲了您的方便找到:
如何快速編譯C#DLL,加載和使用

private bool CompileCSharpCode(string script) 
{ 
lvErrors.Items.Clear(); 
    try 
    { 
     CSharpCodeProvider provider = new CSharpCodeProvider(); 
     // Build the parameters for source compilation. 
     CompilerParameters cp = new CompilerParameters 
     { 
      GenerateInMemory = false, 
      GenerateExecutable = false, // True = EXE, False = DLL 
      IncludeDebugInformation = true, 
      OutputAssembly = "eventHandler.dll", // Compilation name 
     }; 

     // Add in our included libs. 
     cp.ReferencedAssemblies.Add("System.dll"); 
     cp.ReferencedAssemblies.Add("System.Windows.Forms.dll"); 
     cp.ReferencedAssemblies.Add("Microsoft.VisualBasic.dll"); 

     // Invoke compilation. This works from a string, but you can also load from a file using FromFile() 
     CompilerResults cr = provider.CompileAssemblyFromSource(cp, script); 
     if (cr.Errors.Count > 0) 
     { 
      // Display compilation errors. 
      foreach (CompilerError ce in cr.Errors) 
      { 
       //I have a listview to display errors. 
       lvErrors.Items.Add(ce.ToString()); 
      } 
      return false; 
     } 
     else 
     { 
      lvErrors.Items.Add("Compiled Successfully."); 
     } 
     provider.Dispose(); 
    } 
    catch (Exception e) 
    { 
     // never really reached, but better safe than sorry? 
     lvErrors.Items.Add("SEVERE! "+e.Message + e.StackTrace.ToString()); 
     return false; 
    } 
    return true; 
} 

現在,你可以在飛行中編譯,如何加載DLL之間有一些差異。通常來說,您可以將其作爲Visual Studio中的參考添加到項目中。這很容易,你可能已經完成了很多次,但是我們想在當前的項目中使用它,並且我們不能很好地要求用戶在每次他們想測試他們的新DLL時重新編譯整個項目。因此,我將簡單討論一下如何加載一個圖書館。這裏的另一個術語是「編程式」。要做到這一點,一個編譯成功後,我們加載了一個大會如下:

Assembly assembly = Assembly.LoadFrom("yourfilenamehere.dll"); 

如果你有一個AppDomain,你可以試試這個:

Assembly assembly = domain.Load(AssemblyName.GetAssemblyName("yourfilenamehere.dll")); 


現在的lib是「引用」的,我們可以打開它並使用它。有兩種方法可以做到這一點。一個要求你知道該方法是否有參數,另一個會檢查你。我會做的更晚,你可以檢查另一個MSDN

// replace with your namespace.class 
Type type = assembly.GetType("company.project"); 
if (type != null) 
{ 
    // replace with your function's name 
    MethodInfo method = type.GetMethod("method"); 

    if (method != null) 
    { 
     object result = null; 
     ParameterInfo[] parameters = method.GetParameters(); 
     object classInstance = Activator.CreateInstance(type, null); 
     if (parameters.Length == 0) // takes no parameters 
     { 
       // method A: 
      result = method.Invoke(classInstance, null); 
       // method B: 
      //result = type.InvokeMember("method", BindingFlags.InvokeMethod, null, classInstance, null); 
     } 
     else // takes 1+ parameters 
     { 
      object[] parametersArray = new object[] { }; // add parameters here 

       // method A: 
      result = method.Invoke(classInstance, parametersArray); 
       // method B: 
      //result = type.InvokeMember("method", BindingFlags.InvokeMethod, null, classInstance, parametersArray); 
     } 
    } 
} 

問題: 首先編譯工作正常。第一次執行正常。但是,重新編譯嘗試會出錯,並說您的* .PDP(調試器數據庫)正在使用中。我聽說過關於編組和AppDomains的一些提示,但我還沒有完全解決這個問題。重新編譯只會在DLL加載後失敗。


在編組當前嘗試& &的AppDomain:上_instance = _domain.CreateInstanceAndUnwrap(_assembly.FullName,的typeName)

class ProxyDomain : MarshalByRefObject 
    { 
     private object _instance; 
     public object Instance 
     { 
      get { return _instance; } 
     } 
     private AppDomain _domain; 
     public AppDomain Domain 
     { 
      get 
      { 
       return _domain; 
      } 
     } 
     public void CreateDomain(string friendlyName, System.Security.Policy.Evidence securityinfo) 
     { 
      _domain = AppDomain.CreateDomain(friendlyName, securityinfo); 
     } 
     public void UnloadDomain() 
     { 
      try 
      { 
       AppDomain.Unload(_domain); 
      } 
      catch (ArgumentNullException dne) 
      { 
       // ignore null exceptions 
       return; 
      } 
     } 
     private Assembly _assembly; 
     public Assembly Assembly 
     { 
      get 
      { 
       return _assembly; 
      } 
     } 
     private byte[] loadFile(string filename) 
     { 
      FileStream fs = new FileStream(filename, FileMode.Open); 
      byte[] buffer = new byte[(int)fs.Length]; 
      fs.Read(buffer, 0, buffer.Length); 
      fs.Close(); 

      return buffer; 
     } 
     public void LoadAssembly(string path, string typeName) 
     { 
      try 
      { 
       if (_domain == null) 
        throw new ArgumentNullException("_domain does not exist."); 
       byte[] Assembly_data = loadFile(path); 
       byte[] Symbol_data = loadFile(path.Replace(".dll", ".pdb")); 

       _assembly = _domain.Load(Assembly_data, Symbol_data); 
       //_assembly = _domain.Load(AssemblyName.GetAssemblyName(path)); 
       _type = _assembly.GetType(typeName); 
      } 
      catch (Exception ex) 
      { 
       throw new InvalidOperationException(ex.ToString()); 
      } 
     } 
     private Type _type; 
     public Type Type 
     { 
      get 
      { 
       return _type; 
      } 
     } 
     public void CreateInstanceAndUnwrap(string typeName) 
     { 
      _instance = _domain.CreateInstanceAndUnwrap(_assembly.FullName, typeName); 
     } 
    } 

錯誤;說我的大會不是可序列化的。嘗試添加[Serializable]標籤到我的班級,沒有運氣。仍在研究修復。

當你看不到它們是如何被使用的時候,似乎事情會變得有點混亂,所以讓它變得容易?

private void pictureBox1_Click(object sender, EventArgs e) 
    { 
     pd.UnloadDomain(); 

     if (CompileCSharpCode(header + tScript.Text + footer)) 
     { 
      try 
      { 
       pd.CreateDomain("DLLDomain", null); 
       pd.LoadAssembly("eventHandler.dll", "Events.eventHandler"); 
       pd.CreateInstanceAndUnwrap("Events.eventHandler"); // Assembly not Serializable error! 

       /*if (pd.type != null) 
       { 
        MethodInfo onConnect = pd.type.GetMethod("onConnect"); 

        if (onConnect != null) 
        { 
         object result = null; 
         ParameterInfo[] parameters = onConnect.GetParameters(); 
         object classInstance = Activator.CreateInstance(pd.type, null); 
         if (parameters.Length == 0) 
         { 
          result = pd.type.InvokeMember("onConnect", BindingFlags.InvokeMethod, null, classInstance, null); 
          //result = onConnect.Invoke(classInstance, null); 
         } 
         else 
         { 
          object[] parametersArray = new object[] { }; 

          //result = onConnect.Invoke(classInstance, parametersArray); 
          //result = type.InvokeMember("onConnect", BindingFlags.InvokeMethod, null, classInstance, parametersArray); 
         } 
        } 
       }*/ 
       //assembly = Assembly.LoadFrom(null); 

      } 
      catch (Exception er) 
      { 
       MessageBox.Show("There was an error executing the script.\n>" + er.Message + "\n - " + er.StackTrace.ToString()); 
      } 
      finally 
      { 
      } 
     } 
    } 
+0

你的進程已經加載的DLL,你不能overwr迭代它。 – leppie 2013-04-04 15:29:23

+2

您想創建一個新的應用程序域並將該DLL加載到該域中。然後,如果您想重新編譯,請將該應用程序域取消。因此,您的主程序運行在一個應用程序域中,並且它創建並管理用於加載正在處理的程序集的其他應用程序域。 – 2013-04-04 15:33:28

+0

你需要PDB文件嗎?人們用VS進行調試嗎?如果不包含,則設置IncludeDebugInformation = false。 – MrMoDoJoJr 2013-04-04 15:33:40

回答

8

一旦你已經加載的DLL到(默認應用程序域)正在運行的進程,磁盤上的文件不能被覆蓋,直到該進程被終止。託管代碼中的DLL無法卸載,就像它們可能位於非託管代碼中一樣。

您需要在主進程中創建一個新的appdomain,並將新創建的DLL程序集加載到該appdomain中。當您準備編譯DLL的新版本時,您可以處理該appdomain。這將從內存中卸載DLL並釋放DLL文件上的鎖定,以便您可以將新的DLL編譯到同一個文件中。然後,您可以構建一個新的AppDomain來加載新的DLL。

使用appdomains的主要危險是跨AppDomain邊界的所有調用都必須進行編組,非常像IPC或網絡RPC。儘量保持你需要通過appdomain邊界調用的對象的接口達到最小。

您也可以將程序集編譯到內存,接收字節數組或流作爲輸出,然後將該程序集加載到單獨的appdomain中。這可避免在磁盤上創建最終需要刪除的碎片。

不要使用compile to memory作爲文件鎖定問題的解決方法。核心問題是程序集加載到進程的默認應用程序域時不能從內存中刪除。你必須創建一個新的appdomain,並將該DLL加載到該appdomain中,如果你想在流程的生命週期中稍後從內存中卸載該程序集。

下面是如何在另一個應用程序域的背景下構建的對象的大致的輪廓:

var appdomain = AppDomain.CreateDomain("scratch"); 
    byte[] assemblyBytes = // bytes of the compiled assembly 
    var assembly = appdomain.Load(assemblyBytes); 
    object obj = appdomain.CreateInstanceAndUnwrap(assembly.FullName, "mynamespace.myclass"); 

此序列後,obj將包含一個鏈接到實際的對象實例的AppDomain中代理的引用。您可以使用反射或類型轉換obj調用obj上的方法到通用接口類型,並直接調用方法。準備進行調整以支持方法調用參數的RPC編組。 (請參閱.NET遠程處理)

當處理多個應用程序域時,必須小心如何訪問類型和程序集,因爲許多.NET函數默認在調用程序的當前appdomain中運行,而這通常不是你想要當你有多個應用程序域。例如,compilerResult.CompiledAssembly在內部執行生成的程序集在調用者的應用程序域中的加載。你想要的是將程序集加載到其他appdomain中。你必須明確地做到這一點。

更新: 在你展示如何加載您的AppDomain您最近添加的代碼段,這條線是你的問題:

_assembly = Assembly.LoadFrom(path); 

加載的DLL到當前的AppDomain(調用者的應用程序域),而不是到目標appdomain(在您的示例中由_domain引用)。您需要使用_domain.Load()將程序集加載到 appdomain中。

+1

+1:很好的解釋:) – leppie 2013-04-04 15:51:52

+0

固定的片段,但我在組裝得到'型「Events.eventHandler」'事件處理程序,版本= 0.0.0.0,文化=中立,公鑰=空」試圖保存時,不標示爲serializable.''_instance = _domain.CreateInstanceAndUnwrap(_assembly.FullName,typeName的);' – Komak57 2013-04-04 16:34:59

+0

@ Komak57現在你開始進入RPC/.net遠程處理需要在appdomain邊界上公開原始對象。您可以對目標類型進行調整以使其可序列化,也可以使用最少的屬性和方法參數向目標程序集添加虛擬類(字符串數據類型易於序列化;複雜類型更難),並調用應用程序域邊界上的虛擬類,並使虛擬類從AppDomain內部操縱目標對象。這可能會讓您將目標類型序列化,從而節省一些頭痛的問題。 – dthorpe 2013-04-04 19:34:39

1

如果您不需要調試,或者不介意調試「動態」代碼,但缺少一些信息。 你可以在內存中生成代碼..這將允許你編譯代碼幾次..但不會生成一個。PDB

cp.GenerateInMemory = true; 
在替代

如果您有沒有必要能夠在磁盤上找到組件,你可以要求編譯器傾倒在臨時目錄中的所有代碼和生成的dll一個臨時名稱(至極總是會唯一的)

cp.TempFiles = new TempFileCollection(Path.GetTempPath(), false); 
//cp.OutputAssembly = "eventHandler.dll"; 
在這兩種情況下,訪問DLL

,它的類型,您可以從編譯器得到它導致

Assembly assembly = cr.CompiledAssembly; 

沒有明確加載需要

但如果非這種情況適用,你必須和一個物理的.dll與.pdp在一個已知的文件夾..唯一的建議,我可以給你它把一個版本號的DLL .. 和在如果你沒有一個簡單的方法來控制倍的DLL編譯你總是可以訴諸時間戳量..

cp.OutputAssembly = "eventHandler"+DateTime.Now.ToString("yyyyMMddHHmmssfff")+".dll"; 

當然,你必須明白,你每次編譯一個新的.dll會加載到內存中,並不會被卸下,除非您使用單獨的應用程序域..但超出範圍這個問題..