代码之家  ›  专栏  ›  技术社区  ›  Andrew Russell

运行时重新编译C,不带AppDomain

  •  20
  • Andrew Russell  · 技术社区  · 15 年前

    假设我有两个C应用程序- game.exe (XNA,需要支持Xbox 360)和 editor.exe (XNA以WinForms为宿主)-它们都共享 engine.dll 完成大部分工作的装配。

    现在,假设我想添加某种基于C的脚本(它不完全是“脚本”,但我称之为“脚本”)。每个级别都从一个基类继承自己的类(我们称之为 LevelController )

    这些是这些脚本的重要约束:

    1. 它们必须是真实的、编译好的C代码

    2. 它们需要最少的手动“粘合”工作(如果有的话)

    3. 它们必须与其他所有内容在同一AppDomain中运行

    对于游戏来说,这是非常直接的:所有脚本类都可以编译成一个程序集(比如, levels.dll )并且可以根据需要使用反射来实例化各个类。

    编辑要难得多。编辑器能够在编辑器窗口中“玩游戏”,然后将所有内容重置回它开始的位置(这就是编辑器首先需要了解这些脚本的原因)。

    我尝试实现的基本上是编辑器中的“重新加载脚本”按钮,它将重新编译并加载与正在编辑的级别相关联的脚本类,当用户按下“播放”按钮时,创建最近编译的脚本的实例。

    其结果是编辑器中的快速编辑测试工作流(而不是另一种方法——保存级别、关闭编辑器、重新编译解决方案、启动编辑器、加载级别、测试)。


    现在我 认为 我已经找到了一种实现这一目标的潜在方法——这本身就导致了许多问题(如下所示):

    1. 编译的集合 .cs 给定级别所需的文件(或者,如果需要,整个级别 动态链接库 项目)转换为临时的、唯一的命名程序集。那个集会需要参考 引擎.dll .如何在运行时以这种方式调用编译器?如何让它输出这样一个程序集(我可以在内存中这样做吗)?

    2. 加载新程序集。将同名的类加载到同一进程中有关系吗?(我认为这些名称是由程序集名称限定的?)

      现在,正如我提到的,我不能使用AppDomain。但是,另一方面,我不介意泄漏旧版本的脚本类,因此卸载的能力并不重要。除非是?我假设装载几百个组件是可行的。

    3. 播放级别时,实例从刚刚加载的特定程序集继承的LevelController类。如何做到这一点?

    最后:

    这是明智的做法吗?能做得更好吗?


    更新:这些天我用 far simpler approach 解决根本问题。

    4 回复  |  直到 9 年前
        1
  •  3
  •   Alex    15 年前

    检查Microsoft.CSharp.CSharpCodeProvider和System.CodeDom.Compiler周围的命名空间。

    编译.cs文件集合

    应该是非常直截了当的 http://support.microsoft.com/kb/304655

    将同名的类加载到同一进程中有关系吗?

    一点也不。只是名字而已。

    实例从LevelController继承的类。

    加载您创建的程序集,如assembly.load等。使用反射查询要声明的类型。获取构造函数并调用它。

        2
  •  4
  •   Tim Jones    11 年前

    现在有了一个相当优雅的解决方案,(a)在.NET 4.0中有了一个新功能,(b)Roslyn。

    可回收程序集

    在.NET 4.0中,可以指定 AssemblyBuilderAccess.RunAndCollect 定义动态程序集时,这会使动态程序集成为可回收的垃圾:

    AssemblyBuilder ab = AppDomain.CurrentDomain.DefineDynamicAssembly(
        new AssemblyName("Foo"), AssemblyBuilderAccess.RunAndCollect);
    

    香草味.net 4.0,i 认为 您需要通过在原始IL中编写方法来填充动态程序集。

    罗斯林

    输入roslyn:roslyn允许您将原始C代码编译为动态程序集。这是一个例子,灵感来自这两个 blog posts ,更新以使用最新的Roslyn二进制文件:

    using System;
    using System.Reflection;
    using System.Reflection.Emit;
    using Roslyn.Compilers;
    using Roslyn.Compilers.CSharp;
    
    namespace ConsoleApplication1
    {
        public static class Program
        {
            private static Type CreateType()
            {
                SyntaxTree tree = SyntaxTree.ParseText(
                    @"using System;
    
                    namespace Foo
                    {
                        public class Bar
                        {
                            public static void Test()
                            {
                                Console.WriteLine(""Hello World!"");
                            }
                        }
                    }");
    
                var compilation = Compilation.Create("Hello")
                    .WithOptions(new CompilationOptions(OutputKind.DynamicallyLinkedLibrary))
                    .AddReferences(MetadataReference.CreateAssemblyReference("mscorlib"))
                    .AddSyntaxTrees(tree);
    
                ModuleBuilder helloModuleBuilder = AppDomain.CurrentDomain
                    .DefineDynamicAssembly(new AssemblyName("FooAssembly"), AssemblyBuilderAccess.RunAndCollect)
                    .DefineDynamicModule("FooModule");
                var result = compilation.Emit(helloModuleBuilder);
    
                return helloModuleBuilder.GetType("Foo.Bar");
            }
    
            static void Main(string[] args)
            {
                Type fooType = CreateType();
                MethodInfo testMethod = fooType.GetMethod("Test");
                testMethod.Invoke(null, null);
    
                WeakReference weak = new WeakReference(fooType);
    
                fooType = null;
                testMethod = null;
    
                Console.WriteLine("type = " + weak.Target);
                GC.Collect();
                Console.WriteLine("type = " + weak.Target);
    
                Console.ReadKey();
            }
        }
    }
    

    总之:使用可收集的程序集和Roslyn,您可以将C代码编译成可以加载到 AppDomain ,然后收集垃圾(取决于 rules )

        3
  •  1
  •   John Leidegren    15 年前

    好吧,你想在飞行中编辑东西,对吗?这是你的目标,不是吗?

    当您编译程序集并加载它们时,现在有了卸载它们的方法,除非您卸载AppDomain。

    可以使用assembly.load方法加载预编译的程序集,然后通过反射调用入口点。

    我将考虑动态装配方法。通过当前的AppDomain,您表示要创建动态程序集。这就是DLR(动态语言运行时)的工作原理。使用动态程序集,可以创建实现某些可见接口的类型,并通过该接口调用它们。使用动态程序集的背面是您必须自己提供正确的IL,您不能简单地用内置的.NET编译器生成它,但是,我敢打赌Mono项目有一个您可能想要签出的C编译器实现。他们已经有了一个C解释器,该解释器读取C源文件并编译并执行它,这肯定是通过system.reflection.emit API处理的。

    不过,我不确定这里的垃圾收集,因为当涉及到动态类型时,我认为运行时不会释放它们,因为它们可以在任何时候被引用。只有当动态程序集本身被销毁并且不存在对该程序集的引用时,才可以释放该内存。如果您正在并且正在生成大量代码,请确保在某个时刻内存是由GC收集的。

        4
  •  0
  •   Anonymous Coward    9 年前

    如果语言是Java,答案就是使用JRealEL。既然不是这样,答案就是提高足够的噪音来显示对这个的需求。它可能需要某种类型的替代clr或“c引擎项目模板”和vs-ide协调工作等。

    我怀疑有很多情况下,这是“必须的”,但有很多情况下,它将节省大量的时间,因为你可以摆脱较少的基础设施和更快的周转时间的东西,不会长期使用。(是的,有些人会争论过度设计的事情,因为它们将被使用20年以上,但问题是当你需要做一些巨大的改变时,它可能会像从头开始重建整个东西一样昂贵。所以归根结底是现在还是以后花钱。由于不确定项目稍后是否会成为业务关键型项目,并且可能需要进行较大的更改,所以我的观点是使用“kiss”原则,并具有在IDE、CLR/运行时等环境中进行实时编辑的复杂性,而不是将其构建到以后可能有用的每个应用程序中。当然,需要一些防御性的编程和实践来使用这种特性修改一些实时服务。就像传说中的二郎开发者所做的那样)