如何动态编译、加载和使用C#DLL
A) 动态编译C#EXE和DLL相对容易。如何动态编译、加载和使用C#DLL,c#,compilation,runtime,.net-assembly,appdomain,C#,Compilation,Runtime,.net Assembly,Appdomain,A) 动态编译C#EXE和DLL相对容易。 B) 执行EXE意味着运行新的应用程序。加载DLL意味着可以在应用程序或项目之间共享的情况下使用方法和函数。 现在,为了方便起见,可以从或中找到编译EXE(或经过轻微修改的DLL)的最快、最简单的方法: private bool CompileCSharpCode(string script) { lvErrors.Items.Clear(); try { CSharpCodeProvider provider = n
B) 执行EXE意味着运行新的应用程序。加载DLL意味着可以在应用程序或项目之间共享的情况下使用方法和函数。
现在,为了方便起见,可以从或中找到编译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被“引用”,我们就可以打开它并使用它了。有两种方法可以做到这一点。一个要求您知道该方法是否有参数,另一个将为您检查。我稍后再做,你可以检查另一个。
// 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):
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]标记添加到我的类,但没有成功。仍在研究修复方法
当你看不到它们是如何被使用的时候,事情可能会变得有点混乱,所以这里让它变得简单
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加载到正在运行的进程中(默认的appdomain)后,在进程终止之前,无法覆盖磁盘上的文件。DLL不能像在非托管代码中一样在托管代码中卸载 您需要在主机进程中创建一个新的appdomain,并将新创建的DLL程序集加载到该appdomain中。当您准备编译DLL的新版本时,可以处置appdomain。这将从内存中卸载DLL并释放DLL文件上的锁,以便您可以将新DLL编译到同一文件。然后,您可以构造一个新的appdomain以将新DLL加载到其中 使用appdomains的主要危害是必须封送跨越appdomain边界的所有调用,就像IPC或网络RPC一样。尽量减少需要跨appdomain边界调用的对象的接口 您还可以将程序集编译到内存中,接收字节数组或流作为输出,然后将该程序集加载到单独的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中的实际对象实例。您可以使用反射或类型转换obj对公共接口类型调用obj上的方法,并直接调用方法。准备进行调整以支持方法调用参数的RPC编组。(请参见.NET远程处理)
在使用多个appdomain时,您必须小心如何访问类型和程序集,因为许多.NET函数默认在调用者的当前appdomain中操作,这通常不是您在使用多个appdomain时想要的<例如,code>compilerResult.CompiledAssembly在调用方的appdomain中内部执行生成程序集的加载。您想要的是将程序集加载到其他appdomain中。你必须明确地这样做
更新:
在最近添加的显示如何加载appdomain的代码段中,这一行是您的问题:
_assembly = Assembly.LoadFrom(path);
将DLL加载到当前appdomain(调用方的appdomain),而不是目标appdomain(在您的示例中由_domain引用)。您需要使用
\u domain.Load()
将程序集加载到该appdomain中。如果您不需要调试,或者不介意调试“动态”代码,但缺少一些信息。
您可以在内存中生成代码。。这将允许您多次编译代码。。但不会生成.pdb
cp.GenerateInMemory = true;
或者,如果您不需要在磁盘上定位程序集,您可以要求编译器转储temp目录中的所有代码,并为dll生成一个temp名称(该名称始终是唯一的)
在这两种情况下,要访问dll及其类型,可以从编译器结果中获取它
Assembly assembly = cr.CompiledAssembly;
不显式加载是必要的
但是,如果不存在这种情况,并且必须在已知文件夹中创建一个包含.pdp的物理.dll。。我能给你的唯一建议是在dll中添加一个版本号。。
如果你没有简单的方法来控制dll编译的次数,你可以使用时间戳
cp.OutputAssembly = "eventHandler"+DateTime.Now.ToString("yyyyMMddHHmmssfff")+".dll";
当然,您必须意识到,每次编译新的.dll时,都会将其加载到内存中,并且不会卸载,除非您使用单独的应用程序域。。但这超出了我们的范围
cp.OutputAssembly = "eventHandler"+DateTime.Now.ToString("yyyyMMddHHmmssfff")+".dll";