c#从另一个AppDomain访问时,脚本中有错误的数据

本文关键字:脚本 有错误 数据 另一个 AppDomain 访问 | 更新日期: 2023-09-27 18:29:03

我正在尝试将C#脚本添加到我的应用程序中,并决定使用AppDomain,这样我就可以成功地加载和卸载脚本,而不会引起内存问题。我当前的代码有一个ScriptManager,它使用观察程序在文件夹中加载脚本以检查更改:

    private static void LoadScript(string scriptPath)
    {
        string scriptName = Path.GetFileNameWithoutExtension(scriptPath);
        AppDomain domain = AppDomain.CreateDomain(scriptName);
        domains.Add(domain);
        ScriptCompiler scriptCompiler = (ScriptCompiler)domain.CreateInstanceFromAndUnwrap(Assembly.GetExecutingAssembly().Location, "Milkshake.Tools.ScriptCompiler");
        scriptCompiler.Compile(scriptPath);
    }

它创建了我的脚本编译器的一个新的appdomain实例,然后尝试编译脚本代码,最后实例化脚本:

    public void Compile(string filepath)
    {
        string code = File.ReadAllText(filepath);
        CSharpCodeProvider codeProvider = new CSharpCodeProvider(new Dictionary<String, String> { { "CompilerVersion", "v3.5" } });
        CompilerParameters parameters = new CompilerParameters();
        parameters.GenerateInMemory = true;
        parameters.GenerateExecutable = false;
        //Add references used in scripts
        parameters.ReferencedAssemblies.Add(Assembly.GetExecutingAssembly().Location); //Current application ('using milkshake;')
        parameters.ReferencedAssemblies.Add("System.dll");
        parameters.ReferencedAssemblies.Add("System.Core.dll");
        parameters.ReferencedAssemblies.Add("System.Data.dll");
        parameters.ReferencedAssemblies.Add("System.Data.DataSetExtensions.dll");
        parameters.ReferencedAssemblies.Add("System.Xml.dll");
        parameters.ReferencedAssemblies.Add("System.Xml.Linq.dll");
        CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters, code);
        if (results.Errors.HasErrors)
        {
            string error = "Error in script: " + Path.GetFileNameWithoutExtension(filepath);
            foreach (CompilerError e in results.Errors)
            {
                error += "'n" + e;
            }
            Log.Print(LogType.Error, error);
        }
        else
        {
            //Successful Compile
            Log.Print(LogType.Debug, "Script Loaded: " + Path.GetFileNameWithoutExtension(filepath));
            assembly = results.CompiledAssembly;
            type = assembly.GetTypes()[0];
            //Instansiate script class.
            Activator.CreateInstance(type);
        }
    }

现在代码实际上工作得很好,它正确地加载和卸载了脚本,但是当我尝试从脚本中调用主应用程序中的方法时,它有错误的数据。例如,这个脚本会打印出"测试",然后打印出服务器上有多少玩家,最后向所有玩家发送消息"测试"。

class TestScript
{
    public TestScript()
    {
        Log.Print(LogType.Debug, "Test!");
        //WorldServer.Sessions is missing data.
        Log.Print(LogType.Debug, WorldServer.Sessions.Count);
        WorldServer.Sessions.ForEach(s => s.sendMessage("Test!!"));
    }
}

现在有趣的是,当我从脚本中调用WorldServer.Sessions.Count(WorldServer是静态的)时,它总是显示为0(当我从主应用程序中调用它时,它显示为1)。每次我尝试调用一个方法或从主应用程序获取数据时,它都会出现问题。我猜这是与跨应用程序域的东西有关,所以我真的不知道什么是最好的方法。

c#从另一个AppDomain访问时,脚本中有错误的数据

脚本打印零的原因是在.NET应用程序域中提供了非常高的隔离级别:当同一个类加载到两个不同的应用程序域时,两个副本的行为完全独立。除其他外,每个副本都有自己的一组静态变量,每个副本运行其静态初始化代码,等等

因此,两个应用程序域之间的通信不能通过静态变量或内存共享结构来隐式完成*。它需要像在不同的过程中与代码通信一样明确地完成。

如果必须与脚本交换的数据量很小,则可以使用[SetData'][1] and [ GetData`]2方法来避免设置更复杂的回调系统。

*尽管应用程序域似乎共享虚拟地址空间,但我在任何标准中都找不到证据。