[英]How to compile C# DLL on the fly, Load, and Use
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 Studios中的參考,以編譯到項目中。 這很容易,您可能已經做過很多次了,但是我們想在當前項目中使用它,我們不能很好地要求用戶每次想要測試新的DLL時都重新編譯整個項目。 。 因此,我將簡單討論如何“動態”加載庫。 這里的另一個術語是“以編程方式”。 為此,在成功編譯后,我們按以下方式加載程序集:
Assembly assembly = Assembly.LoadFrom("yourfilenamehere.dll");
如果您有AppDomain,則可以嘗試以下操作:
Assembly assembly = domain.Load(AssemblyName.GetAssemblyName("yourfilenamehere.dll"));
現在,該庫已被“引用”,我們可以打開它並使用它。 有兩種方法可以做到這一點。 一個要求您知道該方法是否具有參數,另一個要求您進行檢查。 稍后再做,您可以檢查其他的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:
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並將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遠程處理)
當使用多個應用程序域時,必須小心如何訪問類型和程序集,因為許多.NET函數默認在調用者的當前應用程序域中運行,而在擁有多個應用程序域時通常不是您想要的。 compilerResult.CompiledAssembly
,例如,在內部執行在呼叫者的應用程序域生成組件的一個負載。 您需要將程序集加載到其他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上放一個版本號..如果您不這樣做的話有一個簡單的方法來控制dll的編譯時間,您可以隨時使用時間戳。
cp.OutputAssembly = "eventHandler"+DateTime.Now.ToString("yyyyMMddHHmmssfff")+".dll";
當然,您必須意識到,每次編譯時,新的.dll將被加載到內存中,除非您使用單獨的應用程序域,否則不會被卸載..但這超出了此問題的范圍。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.