2013-04-04 60 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会加载到内存中,并不会被卸下,除非您使用单独的应用程序域..但超出范围这个问题..