卸载AppDomain后删除dll文件

时间:2015-05-14 14:46:37

标签: c# dll mono .net-assembly appdomain

我目前正试图制作游戏引擎,试图复制逐件统一功能, 加载一些脚本,我没有问题,但当我不得不重新加载它们时,单声道编译失败,告诉我已经访问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("???");
    }
}

(要测试,您可能需要在执行目录中复制mono,可在以下位置找到:http://www.mono-project.com/download/

有没有人知道我能做些什么,强行删除那个dll文件,或者让那个文件可以删除?

如果没有,是否有人对统一加载和重新加载脚本的方式有所了解,如何使其成为好方法?

4 个答案:

答案 0 :(得分:2)

如果有人需要,我的解决方法是使用CSharpCodeProvider进行编译, 这个参数:

^[0-9\.]*$

然后我甚至不需要从中构建一个dll,结果是一个可用的程序集。

如果你真的需要在运行时加载然后删除一个程序集,你仍然可以使用

来完成
CompilerParameters compilerParams = new CompilerParameters
{
GenerateInMemory = true,
GenerateExecutable = false,
IncludeDebugInformation = true
};

然后你可以删除dll,但需要一些内存。

答案 1 :(得分:1)

所以我精心设计了这个适合我的例子

项目consoleapplication1.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加载到主应用程序域和远程appdomain中 从AsmLoader对象创建一个编组的代理,通过编组的AsmLoader.LoadAssembly调用将testasm.dll加载到远程域。 然后,consoleapplication1.exe将当前appdomain中加载的所有程序集输出到控制台,以查看testasm.dll是否未加载到其中。 卸载远程appdomain,删除testasm.dll就好了。

答案 2 :(得分:0)

就我而言,解决方案是在这行代码之后调用:

AppDomain.Unload(domain);

还有这个:

domain = null;

从 AppDomain 卸载似乎还不够,因为 domain 对象仍然持有对已加载/卸载文件的引用。将其设置为 null 后,我可以使用 File.Delete(unloadedAssemblyPath) 而无需其他 GC 解决方法。

答案 3 :(得分:-1)

@Michal:

我也尝试了很多其他方法,

我的脑袋正在燃烧,但我尝试了你的链接,我想我也试图了解并使用我在谷歌上找到的每个小例子:/

但在这里我再次尝试过:

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,并在程序启动时删除它们,但它不是一个非常令人放心的方法,如果用户有很多要加载的脚本,或者保留引擎有些日子,它不好。 :/