运行时重新编译 C#,不使用 AppDomains [英] Recompile C# while running, without AppDomains

查看:30
本文介绍了运行时重新编译 C#,不使用 AppDomains的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

假设我有两个 C# 应用程序——game.exe(XNA,需要支持 Xbox 360)和 editor.exe(XNA 托管在 WinForms 中)——它们两者共享一个 engine.dll 程序集,该程序集完成了大部分工作.

现在假设我想添加某种基于 C# 的脚本(它不完全是脚本",但我会称之为).每个级别都有自己的类继承自一个基类(我们将其称为 LevelController).

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

  1. 它们需要是真实的、经过编译的 C# 代码

  2. 如果有的话,它们应该需要最少的手动粘合"工作

  3. 它们必须与其他所有东西运行在同一个 AppDomain 中

对于游戏 - 这非常简单:所有脚本类都可以编译成一个程序集(例如,levels.dll),并且可以根据需要使用反射对各个类进行实例化.

编辑器更难.编辑器可以在编辑器窗口中玩游戏",然后将所有内容重置回开始的位置(这就是编辑器首先需要了解这些脚本的原因).

我想要实现的基本上是编辑器中的重新加载脚本"按钮,它将重新编译并加载与正在编辑的关卡关联的脚本类,并且当用户按下播放"按钮时,创建一个最近编译的脚本.

其结果将是编辑器内的快速编辑-测试工作流程(而不是替代方案——保存关卡、关闭编辑器、重新编译解决方案、启动编辑器、加载关卡、测试).

<小时>

现在我认为我已经找到了一种可能的方法来实现这一目标 - 这本身会导致一些问题(如下所示):

  1. 将给定级别(或者,如果需要,整个 levels.dll 项目)所需的 .cs 文件集合编译为临时文件,唯一命名的程序集.该程序集需要引用 engine.dll.如何在运行时以这种方式调用编译器?如何让它输出这样的程序集(我可以在内存中执行它)?

  2. 加载新程序集.我将同名的类加载到同一个进程中是否重要?(我的印象是名称由程序集名称限定?)

    现在,正如我所提到的,我无法使用 AppDomains.但是,另一方面,我不介意泄露旧版本的脚本类,因此卸载能力并不重要.除非是?我假设加载几百个程序集是可行的.

  3. 在玩关卡时,从刚刚加载的特定程序集中实例化从 LevelController 继承的类.如何做到这一点?

最后:

这是一种明智的做法吗?有没有更好的办法?

<小时>

更新:最近我使用更简单的方法来解决根本问题.

解决方案

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

<块引用><块引用>

编译.cs文件集合

应该非常简单,比如 http://support.microsoft.com/kb/304655

<块引用><块引用>

将同名的类加载到同一个进程中会有影响吗?

完全没有.只是名字.

<块引用><块引用>

实例化从 LevelController 继承的类.

加载您创建的程序集,例如 Assembly.Load 等.使用反射查询要实例化的类型.获取构造函数并调用它.

Let’s say that I have two C# applications - game.exe (XNA, needs to support Xbox 360) and editor.exe (XNA hosted in WinForms) - they both share an engine.dll assembly that does the vast majority of the work.

Now let’s say that I want to add some kind of C#-based scripting (it’s not quite "scripting" but I’ll call it that). Each level gets its own class inherited from a base class (we’ll call it LevelController).

These are the important constraints for these scripts:

  1. They need to be real, compiled C# code

  2. They should require minimal manual "glue" work, if any

  3. They must run in the same AppDomain as everything else

For the game - this is pretty straight forward: All the script classes can be compiled into an assembly (say, levels.dll) and the individual classes can be instanced using reflection as needed.

The editor is much harder. The editor has the ability to "play the game" within the editor window, and then reset everything back to where it started (which is why the editor needs to know about these scripts in the first place).

What I am trying to achieve is basically a "reload script" button in the editor that will recompile and load the script class associated with the level being edited and, when the user presses the "play" button, create an instance of the most recently compiled script.

The upshot of which will be a rapid edit-test workflow within the editor (instead of the alternative - which is to save the level, close the editor, recompile the solution, launch the editor, load the level, test).


Now I think I have worked out a potential way to achieve this - which itself leads to a number of questions (given below):

  1. Compile the collection of .cs files required for a given level (or, if need be, the whole levels.dll project) into a temporary, unique-named assembly. That assembly will need to reference engine.dll. How to invoke the compiler this way at runtime? How to get it to output such an assembly (and can I do it in memory)?

  2. Load the new assembly. Will it matter that I am loading classes with the same name into the same process? (I am under the impression that the names are qualified by assembly name?)

    Now, as I mentioned, I can’t use AppDomains. But, on the other hand, I don’t mind leaking old versions of script classes, so the ability to unload isn’t important. Unless it is? I’m assuming that loading maybe a few hundred assemblies is feasible.

  3. When playing the level, instance the class that is inherited from LevelController from the specific assembly that was just loaded. How to do this?

And finally:

Is this a sensible approach? Could it be done a better way?


UPDATE: These days I use a far simpler approach to solve the underlying problem.

解决方案

Check out the namespaces around Microsoft.CSharp.CSharpCodeProvider and System.CodeDom.Compiler.

Compile the collection of .cs files

Should be pretty straightforward like http://support.microsoft.com/kb/304655

Will it matter that I am loading classes with the same name into the same process?

Not at all. It's just names.

instance the class that is inherited from LevelController.

Load the assembly that you created something like Assembly.Load etc. Query the type you want to instanciate using reflection. Get the constructor and call it.

这篇关于运行时重新编译 C#,不使用 AppDomains的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆