[英]Delete dll file after unloading an AppDomain
我目前正在嘗試制作一個游戲引擎,嘗試復制逐塊統一函數,用於加載一些腳本,我沒有問題,但是當我必須重新加載它們時,mono 編譯失敗,告訴我 DLL 是已訪問,否則無法刪除 DLL 文件。
這是我的代碼:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Diagnostics;
using System.Security.Policy;
using System.Security;
using System.Security.Permissions;
public class Proxy : MarshalByRefObject
{
public Assembly GetAssembly(string assemblyPath)
{
try
{
byte[] response = new System.Net.WebClient().DownloadData(assemblyPath);
return Assembly.ReflectionOnlyLoad(response);
}
catch (Exception)
{
return null;
}
}
public Assembly GetAssembly2(string assemblyPath, AppDomain domain)
{
try
{
byte[] bytesDLL = new System.Net.WebClient().DownloadData(assemblyPath);
return domain.Load(bytesDLL);
}
catch (Exception)
{
return null;
// throw new InvalidOperationException(ex);
}
}
public Assembly GetAssemblyByName(AssemblyName name, AppDomain domain)
{
return domain.ReflectionOnlyGetAssemblies().
SingleOrDefault(assembly => assembly.GetName() == name);
}
}
class Program
{
public static AppDomain domain;
public static Assembly assembly;
public static Type type;
public static String dllPath;
public static String scriptPath;
public static String className;
public static String file;
public static dynamic instance;
private static bool Compile(String path, out String dir)
{
ProcessStartInfo start = new ProcessStartInfo();
dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
dir = Path.Combine(dir, Path.GetFileNameWithoutExtension(path) + ".dll");
if (File.Exists(dir))
{
Console.WriteLine("???????");
File.Delete(dir);
Console.WriteLine("???????2");
}
start.FileName = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "Mono\\lib\\mono\\4.5\\mcs.exe");
start.UseShellExecute = false;
start.RedirectStandardError = true;
start.RedirectStandardOutput = true;
start.Arguments = "\"" + path + "\" " + "/target:library" + " " + "/out:" + "\"" + dir + "\""; //+ " " + "/reference:OctogonEngine.dll" + " /reference:AssimpNet.dll";
using (Process process = Process.Start(start))
{
using (StreamReader reader = process.StandardError)
{
string result = reader.ReadToEnd();
Console.WriteLine(result);
}
using (StreamReader reader = process.StandardOutput)
{
string result = reader.ReadToEnd();
Console.WriteLine(result);
}
}
Console.WriteLine("compilation ok");
return (true);
}
public static void Unload()
{
FileStream[] streams = null;
if (assembly != null)
streams = assembly.GetFiles();
instance = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
type = null;
assembly = null;
AppDomain.Unload(domain);
assembly = null;
if (streams != null)
{
for (int i = 0; i < streams.Length; i++)
{
streams[i].Dispose();
}
}
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
Directory.Delete(cachePath, true);
return;
}
static Assembly GetAssemblyByName(string name, AppDomain domain)
{
return domain.GetAssemblies().
SingleOrDefault(assembly => assembly.GetName().Name == name);
}
public static String cachePath = "./cache/";
public static void Load()
{
Directory.CreateDirectory(cachePath);
if (Compile(scriptPath, out Program.dllPath))
{
if (File.Exists(Program.dllPath))
{
className = Path.GetFileNameWithoutExtension(Program.dllPath);
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
setup.ShadowCopyFiles = "true";
setup.CachePath = cachePath;
domain = AppDomain.CreateDomain(className, null, setup);
domain.DoCallBack(() => AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName("test.dll")));
var assemblyLoader = (Proxy)domain.CreateInstanceAndUnwrap(typeof(Proxy).Assembly.FullName, typeof(Proxy).FullName);
assembly = assemblyLoader.GetAssembly(Program.dllPath);
/*if (assembly == null)
{
Console.WriteLine("damn");
}*/
if (assembly != null)
{
type = assembly.GetType(className);
}
if (File.Exists(scriptPath))
Program.file = File.ReadAllText(scriptPath);
}
}
}
static bool check = false;
static void AppDomainInit(string[] args)
{
if (!File.Exists(args[0]))
{
return;
}
}
public static void init(String scriptPath)
{
if (File.Exists(scriptPath))
{
Program.file = File.ReadAllText(scriptPath);
Program.scriptPath = scriptPath;
Program.Load();
}
}
static void Main(string[] args)
{
Program.init(Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "test.cs"));
Program.Unload();
//here is the crash :/
File.Delete(Program.dllPath);
Console.WriteLine("???");
}
}
(為了測試,您可能需要在執行目錄中復制單聲道,可以在以下位置找到: http : //www.mono-project.com/download/ )
有沒有人知道我可以做些什么,強制刪除那個 dll 文件,或者讓那個文件可以被刪除?
如果沒有,是否有人對統一加載和重新加載腳本的方式有所了解,如何使其成為好方法?
如果有人需要,我的解決方案是使用 CSharpCodeProvider 進行編譯,其中包含以下參數:
CompilerParameters compilerParams = new CompilerParameters
{
GenerateInMemory = true,
GenerateExecutable = false,
IncludeDebugInformation = true
};
然后我什至不需要從中構建一個 dll,結果是一個可用的程序集。
如果您確實需要在運行時加載然后刪除程序集,您仍然可以使用
byte[] bytes = File.ReadAllBytes("pathtoyourdll");
AppDomain.CurrentDomain.Load(bytes);
然后您可以刪除該dll,但這需要一些內存。
所以我制作了這個對我來說很好用的例子
項目控制台application1.exe
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
try
{
String pathToAssembly = args[0];
AppDomain dom = AppDomain.CreateDomain("some");
AssemblyName assemblyName = new AssemblyName();
assemblyName.CodeBase = "loader.dll";
dom.Load(assemblyName);
object loader = dom.CreateInstanceAndUnwrap("loader", "loader.AsmLoader");
Type loaderType = loader.GetType();
loaderType.GetMethod("LoadAssembly").Invoke(loader, new object[] { pathToAssembly });
//make sure the given assembly is not loaded in the main app domain and thus would be locked
AppDomain.CurrentDomain.GetAssemblies().All(a => { Console.WriteLine(a.FullName); return true; });
AppDomain.Unload(dom);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
File.Delete(pathToAssembly);
}
catch(Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
}
類庫loader.dll:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace loader
{
public class AsmLoader: MarshalByRefObject
{
public AsmLoader()
{
}
public void LoadAssembly(string path)
{
AssemblyName n = new AssemblyName();
n.CodeBase = path;
AppDomain.CurrentDomain.Load(n);
}
}
}
類庫testasm.dll
... whatever code....
將所有 3 個文件放在同一個文件夾中,在該文件夾中打開一個 cmd 行並發出命令:
consoleapplication1.exe testasm.dll
它將 loader.dll 加載到主應用程序域中,遠程應用程序域從 AsmLoader 對象創建一個編組代理,通過編組的 AsmLoader.LoadAssembly 調用將 testasm.dll 加載到遠程域中。 然后 consoleapplication1.exe 將當前應用程序域中加載的所有程序集輸出到控制台,以查看 testasm.dll 未加載到其中。 卸載遠程應用程序域,刪除 testasm.dll 就好了。
在我的情況下,解決方案是在這行代碼之后調用:
AppDomain.Unload(domain);
還有這個:
domain = null;
從 AppDomain 卸載似乎還不夠,因為域對象仍然持有對已加載/卸載文件的引用。 將其設置為 null 后,我可以使用 File.Delete(unloadedAssemblyPath) 而無需其他 GC 解決方法。
@邁克爾:
我也嘗試了很多其他的方法,
我的頭在燃燒,但我在你的鏈接上嘗試過什么,我想我也試圖理解和使用我在谷歌上找到的每一個小例子:/
但在這里我再次嘗試:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Diagnostics;
using System.Security.Policy;
using System.Security;
using System.Security.Permissions;
using System.Configuration;
class Program
{
public static void Main(string[] args)
{
String pathToAssembly = args[0];
AppDomain dom = AppDomain.CreateDomain("some");
AssemblyName assemblyName = new AssemblyName();
assemblyName.CodeBase = pathToAssembly;
Assembly assembly = dom.Load(assemblyName);
AppDomain.Unload(dom);
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
File.Delete(pathToAssembly);
}
}
我遇到了同樣的問題,無法訪問文件進行刪除。
我什至試圖這樣做:
domain.DoCallBack(() => AppDomain.CurrentDomain.Load(AssemblyName.GetAssemblyName("test.dll")));
它有效,然后我可以刪除,但是如果在我嘗試從域(按名稱)檢索程序集之后,則刪除再次失敗。
我嘗試使用卷影副本文件、MultiDomainHost 和許多其他東西(我在谷歌上找到的所有東西)
現在我的解決方案是每次修改腳本時創建另一個 DLL,並在程序啟動時刪除它們,但它不是一個非常令人放心的方法,如果用戶有很多腳本要加載,或者保持引擎開啟一些天,不好。 :/
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.