Подтвердить что ты не робот

Перекомпилируйте С# во время работы, без AppDomains

Предположим, что у меня есть два приложения С# - game.exe (XNA, требуется поддержка Xbox 360) и editor.exe (XNA, размещенная в WinForms) - они совместно используют сборку engine.dll, которая подавляет подавляющее большинство работа.

Теперь скажем, что я хочу добавить какой-то скрипт на С# (его не совсем "скриптинг", но я его назову). Каждый уровень получает свой собственный класс, унаследованный от базового класса (назовите его LevelController).

Это важные ограничения для этих скриптов:

  • Они должны быть реальными, скомпилированными С# -кодами

  • Они должны требовать минимальной ручной работы с клеем, если есть

  • Они должны запускаться в том же AppDomain, что и все остальные

Для игры - это довольно прямолинейно: все классы script могут быть скомпилированы в сборку (скажем, levels.dll), и отдельные классы могут быть созданы с использованием рефлексии по мере необходимости.

Редактор намного сложнее. Редактор имеет возможность "играть в игру" в окне редактора, а затем reset все обратно туда, где он был запущен (поэтому редактор должен знать об этих сценариях в первую очередь).

То, что я пытаюсь достичь, это в основном "перезагрузить script" в редакторе, который будет перекомпилировать и загрузить класс script, связанный с редактируемым уровнем, и, когда пользователь нажимает кнопку "play", создайте экземпляр последнего скомпилированного script.

Результатом этого будет быстрый рабочий процесс редактирования в редакторе (вместо альтернативы - сохранение уровня, закрытие редактора, перекомпилирование решения, запуск редактора, загрузка уровня, тест).


Теперь я думаю, что я разработал потенциальный способ достижения этого - что само по себе приводит к ряду вопросов (см. ниже):

  • Скомпилируйте коллекцию файлов .cs, необходимых для заданного уровня (или, если нужно, весь проект levels.dll) во временную сборку с уникальным именем. Эта сборка должна будет ссылаться на engine.dll. Как вызвать компилятор таким образом во время выполнения? Как получить его для вывода такой сборки (и могу ли я сделать это в памяти)?

  • Загрузите новую сборку. Будет ли иметь значение, что я загружаю классы с тем же именем в один и тот же процесс? (У меня создается впечатление, что имена квалифицируются по имени сборки?)

    Теперь, как я уже говорил, я не могу использовать AppDomains. Но, с другой стороны, я не против утечки старых версий классов script, поэтому возможность разгрузки не важна. Разве это не так? Я предполагаю, что загрузка может быть несколько сотен сборок.

  • При воспроизведении уровня введите экземпляр класса, унаследованного от LevelController, из только что загруженной конкретной сборки. Как это сделать?

И наконец:

Это разумный подход? Может ли это сделать лучше?


UPDATE: в наши дни я использую гораздо более простой подход для решения основной проблемы.

4b9b3361

Ответ 1

Проверьте пространства имен вокруг Microsoft.CSharp.CSharpCodeProvider и System.CodeDom.Compiler.

Скомпилировать коллекцию файлов .cs

Должно быть довольно просто, как http://support.microsoft.com/kb/304655

Будет ли иметь значение, что я загружаю классы с тем же именем в один и тот же процесс?

Совсем нет. Он просто называет.

экземпляр класса, который унаследован от LevelController.

Загрузите сборку, которую вы создали, например, Assembly.Load и т.д. Запросите тип, который вы хотите использовать, используя отражение. Получите конструктор и назовите его.

Ответ 2

В настоящее время существует довольно элегантное решение, которое стало возможным благодаря (а) новой функции в .NET 4.0 и (b) Roslyn.

Коллекционные сборки

В .NET 4.0 вы можете указать AssemblyBuilderAccess.RunAndCollect при определении динамической сборки, что делает сборку мусора динамической сборки:

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

С vanilla.NET 4.0, я думаю, вам нужно заполнить динамическую сборку, написав методы в raw IL.

Рослин

Введите Roslyn: Roslyn позволяет вам скомпилировать исходный код С# в динамическую сборку. Вот пример, вдохновленный этими двумя блоками сообщения, обновленный для работы с последними бинарниками 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 вы можете скомпилировать код С# в сборку, которая может быть загружена в AppDomain, а затем собрана мусор (с учетом количества правила).

Ответ 3

Ну, ты хочешь иметь возможность редактировать вещи "на лету", верно? что ваша цель здесь не так ли?

Когда вы собираете сборки и загружаете их там, теперь их можно выгрузить, если вы не разгрузите AppDomain.

Вы можете загрузить предварительно скомпилированные сборки с помощью метода Assembly.Load, а затем вызвать точку входа через отражение.

Я бы рассмотрел подход динамической сборки. Когда вы через свой текущий AppDomain говорите, что хотите создать динамическую сборку. Так работает DLR (динамическое языковое исполнение). С помощью динамических сборок вы можете создавать типы, которые реализуют некоторый видимый интерфейс и вызывать их через это. Задняя сторона работы с динамическими сборками заключается в том, что вы сами должны предоставить правильный IL, вы не можете просто генерировать это со встроенным компилятором .NET, однако, я уверен, проект Mono имеет реализацию компилятора С#, которую вы можете захотеть проверить, У них уже есть интерпретатор С#, который читает в исходном файле С# и компилирует его и выполняет, и определенно обрабатывается через API System.Reflection.Emit.

Я не уверен в сборке мусора здесь, потому что, когда дело доходит до динамических типов, я думаю, что среда исполнения не освобождает их, потому что в любой момент они могут ссылаться. Только если сама динамическая сборка будет уничтожена, и ссылки на эту сборку не будут доступны, было бы разумно освободить эту память. Если вы и повторно генерируете много кода, убедитесь, что память в какой-то момент собрана GC.

Ответ 4

Если языком был Java, то ответ должен был бы использовать JRebel. Поскольку это не так, ответ заключается в том, чтобы набрать достаточный шум, чтобы показать, что это требует. Это может потребовать какой-то альтернативный CLR или "шаблон проекта двигателя С#" и VS IDE, работающие в координации и т.д.

Я сомневаюсь, что существует множество сценариев, где это "должно быть", но есть многие, в которых это экономит много времени, поскольку вы могли бы уйти с меньшей инфраструктурой и более быстрым поворотом для вещей, которые не будут использоваться для длинный. (Да, есть некоторые, которые утверждают, что они слишком сложны, потому что они будут использоваться более 20 лет, но проблема в том, что когда вам нужно провести массовые изменения, это, вероятно, будет так же дорого, как и восстановление всего этого с нуля. Таким образом, дело доходит до того, тратить ли деньги сейчас или позже. Поскольку он не знает наверняка, что проект станет критическим для бизнеса позже и может потребовать больших изменений позже, аргумент для меня состоит в том, чтобы использовать "KISS" -принцип и иметь сложности живого редактирования в среде IDE, CLR/runtime и т.д. вместо того, чтобы создавать его в каждом приложении, где это может быть полезно позже. Конечно, некоторые защитные программирования и практики будут необходимы для модификации некоторых живых сервисов с использованием такого рода функций. devs, как говорят, делают)