如何在运行,加载和使用中编译C#DLL

A)动态编译C#EXE和DLL是相对容易的。
B)执行EXE意味着运行新的应用程序。 加载DLL意味着可以在可以在应用程序或项目之间共享的情况下使用方法和函数。

现在,可以从MSDN或为方便起见,找到编译EXE(或使用温和修改,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后失败。


目前对Marshaling && AppDomain的尝试:

 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); } } 

_instance = _domain.CreateInstanceAndUnwrap(_assembly.FullName,typeName)上的错误; 说我的大会不可序列化。 尝试将[Serializable]标签添加到我的class级,没有运气。 仍在研究修复。

当你看不到它们是如何被使用时,似乎事情会有点混乱,所以这里让它变得容易吗?

 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 { } } } 

将DLL加载到正在运行的进程(的默认应用程序域)之后,在该进程终止之前,不能覆盖磁盘上的文件。 DLL无法在托管代码中卸载,就像它们可以在非托管代码中一样。

您需要在主机进程中创建一个新的appdomain,并将新创建的DLL程序集加载到该appdomain中。 当您准备编译新版本的DLL时,您可以处置该appdomain。 这将从内存中卸载DLL并释放DLL文件上的锁,以便您可以将新DLL编译到同一文件。 然后,您可以构造一个新的appdomain来加载新的DLL。

使用appdomains的主要危险是必须整理appdomain边界的所有调用,就像IPC或网络RPC一样。 尝试将需要在appdomain边界调用的对象的接口保持在最低限度。

您还可以将程序集编译到内存,接收字节数组或流作为输出,然后将该程序集加载到单独的appdomain中。 这样可以避免在磁盘上创建最终需要删除的碎片。

不要使用编译到内存作为文件锁定问题的解决方法。 核心问题是,当程序集加载到进程的默认应用程序域时,无法从内存中删除程序集。 如果要在进程的生命周期中稍后从内存中卸载该程序集,则必须创建一个新的appdomain并将DLL加载到该appdomain中。

以下是如何在另一个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内的实际对象实例。 您可以使用reflection或类型转换obj调用obj上的方法到公共接口类型并直接调用方法。 准备进行调整以支持方法调用参数的RPC编组。 (参见.NET远程处理)

使用多个appdomains时,您必须要小心如何访问类型和程序集,因为许多.NET函数默认在调用者的当前appdomain中运行,这通常不是您拥有多个appdomains时所需的。 例如, compilerResult.CompiledAssembly 在调用者的appdomain内部执行生成的程序集的Load 。 您想要的是将程序集加载到您的其他应用程序域。 你必须明确地这样做。

更新:在您最近添加的代码段中显示了如何加载您的appdomain,这一行是您的问题:

  _assembly = Assembly.LoadFrom(path); 

这会将DLL加载到当前的 appdomain(调用者的appdomain),而不是加载到目标appdomain(在您的示例中由_domain引用)。 您需要使用_domain.Load()将程序集加载到 appdomain中。

如果您不需要调试,或者不介意调试“动态”代码,并且缺少某些信息。 你可以在内存中生成代码..这将允许你多次编译代码..但不会生成.pdb

 cp.GenerateInMemory = true; 

另外,如果你不需要在磁盘上找到程序集,你可以要求编译器将所有代码转储到temp目录中并为dll生成临时名称(这将永远是唯一的)

 cp.TempFiles = new TempFileCollection(Path.GetTempPath(), false); //cp.OutputAssembly = "eventHandler.dll"; 

在这两种情况下,要访问dll及其类型,您可以从编译器结果中获取它

 Assembly assembly = cr.CompiledAssembly; 

没有明确加载是必要的

但是,如果非这种情况适用,你必须和一个.pdp在一个已知文件夹中的物理.dll ..唯一的建议,我可以给你在版本号上放一个版本号..如果你没有有一个简单的方法来控制dll编译的次数,你总是可以求助于时间戳。

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

当然你必须意识到,每次你编译一个新的.dll将被加载到内存中,除非你使用单独的应用程序域,否则不会被卸载..但这超出了这个问题的范围..