簡體   English   中英

卸載AppDomain后刪除dll文件

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM