Przekompiluj C# podczas pracy, bez AppDomains

Powiedzmy, że mam dwie aplikacje C# - game.exe (XNA, musi obsługiwać Xboxa 360) i editor.exe (XNA hostowane w WinForms) - obie mają Wspólne Zgromadzenie engine.dll, które wykonuje większość pracy.

Teraz powiedzmy, że chcę dodać jakiś rodzaj skryptów opartych na C#(nie jest to do końca "skryptowanie", ale tak to nazwę). Każdy poziom otrzymuje swoją własną klasę odziedziczoną po klasie bazowej (nazwiemy ją LevelController).

Są to ważne ograniczenia dla tych Skrypty:

  1. Muszą być prawdziwe, skompilowany kod C #

  2. Powinny one wymagać minimalnej ręcznej pracy "kleju", jeśli w ogóle

  3. Muszą działać w tym samym AppDomain jak Wszystko inne

Do gry - jest to dość proste: wszystkie klasy skryptów mogą być skompilowane w asemblację (powiedzmy, levels.dll), a poszczególne klasy mogą być instancjonowane za pomocą refleksji w razie potrzeby.

Edytor jest znacznie trudniejszy. Redaktor ma możliwość " gry " w oknie edytora, a następnie zresetować wszystko z powrotem do miejsca, w którym się zaczęło (dlatego edytor musi wiedzieć o tych skryptach w pierwszej kolejności).

To, co próbuję osiągnąć, to w zasadzie przycisk" reload script "w edytorze, który przekompiluje i załaduje klasę skryptu powiązaną z edytowanym poziomem, a gdy użytkownik naciśnie przycisk "play", utworzy instancję ostatnio skompilowanego skryptu.

Wynik, który będzie be a rapid edit-test workflow wewnątrz edytora (zamiast alternatywy - czyli zapisać poziom, zamknąć Edytor, przekompilować rozwiązanie, uruchomić Edytor, załadować poziom, przetestować).


Teraz myślę wypracowałem potencjalny sposób, aby to osiągnąć - co samo w sobie prowadzi do wielu pytań (podanych poniżej):

  1. Skompiluj zbiór plików .cs wymaganych dla danego poziomu (lub, jeśli zajdzie taka potrzeba, całego projektu levels.dll) do tymczasowego, unikalny zespół o nazwie. To zgromadzenie będzie musiało odwoływać się do engine.dll. Jak wywołać kompilator w ten sposób w czasie wykonywania? Jak zmusić go do wyjścia takiego montażu (i czy mogę to zrobić w pamięci)?

  2. Załaduj nowy zespół. Czy ma znaczenie, że Ładuję klasy o tej samej nazwie do tego samego procesu? (Mam wrażenie, że nazwy są kwalifikowane przez nazwę zespołu?)

    Teraz, Jak wspomniałem, nie mogę używać AppDomains. Ale z drugiej strony, nie mam nic przeciwko wyciekowi starych wersje klas skryptów, więc możliwość rozładowania nie jest ważna. Chyba, że jest? Zakładam, że załadowanie może kilkuset zestawów jest możliwe.

  3. Podczas odtwarzania poziomu, instancja klasy, która jest dziedziczona z LevelController z konkretnego zestawu, który został właśnie załadowany. Jak to zrobić?

I wreszcie:

Czy to rozsądne podejście? Czy można to zrobić w lepszy sposób?


UPDATE: w dzisiejszych czasach używam znacznie prostszego podejście do rozwiązania podstawowego problemu.

Author: Community, 2009-08-30

4 answers

Sprawdź przestrzenie nazw wokół Microsoft.CSharp.CSharpCodeProvider i System.CodeDom.Kompilator.

Skompiluj zbiór .pliki cs

Powinno być dość proste jak http://support.microsoft.com/kb/304655

Czy ma znaczenie, że Ładuję klasy o tej samej nazwie do tego samego procesu?

Wcale nie. To tylko imiona.

Instancja klasy to jest dziedziczone z LevelController.

Załaduj zespół, który utworzyłeś coś takiego jak Assembly.Załadować itp. Odpytywaj typ, który chcesz instancjować, używając reflection. Wezwij konstruktora i zadzwoń.

 3
Author: Alex,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-30 12:19:31

Istnieje teraz dość eleganckie rozwiązanie, możliwe dzięki (A) nowej funkcji w. NET 4.0 i (B) Roslyn.

Zestawy Kolekcjonerskie

W. NET 4.0, możesz określić AssemblyBuilderAccess.RunAndCollect podczas definiowania zestawu dynamicznego, co sprawia, że zestaw dynamiczny może być kolekcjonowany:

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

Z vanilla. NET 4.0, i myślę, że musisz wypełnić dynamiczny montaż, pisząc metody w surowym IL.

Roslyn

Enter Roslyn: Roslyn pozwala kompilować surowy kod C# w dynamiczny zespół. Oto przykład, zainspirowany tymi dwoma blogami posty , zaktualizowane do pracy z najnowszymi binariami 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();
        }
    }
}

Podsumowując: z collectible assemblies i Roslyn, możesz skompilować kod C# do zestawu, który można załadować do AppDomain, a następnie zebrać śmieci(z zastrzeżeniem szeregu zasad ).

 4
Author: Tim Jones,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-05-27 10:14:21

Cóż, chcesz być w stanie edytować rzeczy w locie, prawda? to jest twój cel, prawda?

Kiedy kompilujesz i ładujesz zestawy, jest teraz sposób na ich rozładowanie, chyba że rozładujesz swoją AppDomain.

Możesz załadować wstępnie skompilowane zestawy za pomocą zestawu.Załaduj metodę, a następnie wywołaj punkt wejścia przez odbicie.

Rozważyłbym podejście dynamicznego montażu. Gdzie za pośrednictwem bieżącej AppDomain powiedzieć, że chcesz utworzyć dynamiczny zespół. Tak działa DLR (dynamic language runtime). Dzięki złożeniom dynamicznym możesz tworzyć typy, które implementują jakiś widoczny interfejs i wywoływać je przez to. Z tyłu pracy z dynamic assemblies jest to, że musisz DOSTARCZYĆ poprawne IL samodzielnie, nie możesz po prostu wygenerować tego za pomocą wbudowanego kompilatora. NET, jednak założę się, że projekt Mono ma implementację kompilatora C#, którą możesz chcieć sprawdzić. Mają już interpreter C#, który odczytuje w pliku źródłowym C# i kompiluje to i wykonuje go, a to na pewno jest obsługiwane przez System.Odbicie.Emit API.

Nie jestem pewien co do zbierania śmieci tutaj, ponieważ jeśli chodzi o typy dynamiczne, myślę, że runtime ich nie zwalnia, ponieważ mogą być odwołane w dowolnym momencie. Tylko wtedy, gdy sam zespół dynamiczny zostanie zniszczony i nie będzie żadnych odniesień do tego zespołu, rozsądne byłoby uwolnienie tej pamięci. Jeśli jesteś i ponownie generując dużo kodu upewnij się, że pamięć jest, w niektórych punkt, zebrany przez GC.

 1
Author: John Leidegren,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-30 09:22:46

Gdyby językiem była Java, odpowiedzią byłoby użycie JRebel. Ponieważ tak nie jest, odpowiedzią jest podniesienie poziomu hałasu, aby pokazać, że jest na to zapotrzebowanie. Może wymagać jakiegoś alternatywnego CLR lub 'C# engine project template' I VS IDE pracującego w koordynacji itp.

Wątpię, że jest wiele scenariuszy, w których jest to "must have", ale jest wiele, w których zaoszczędziłoby to dużo czasu, ponieważ można ujść na sucho z mniejszą infrastrukturą i szybszą realizacją rzeczy, które nie będą używany przez długi czas. (Tak są tacy, którzy twierdzą, że przesadzają z inżynierią rzeczy, ponieważ będą używane 20 + lat, ale problem z tym polega na tym, że trzeba zrobić jakąś masową zmianę, prawdopodobnie będzie to tak kosztowne, jak przebudowa całości od podstaw. Więc sprowadza się do tego, czy wydawać pieniądze teraz, czy później. Ponieważ nie jest pewne, że projekt stanie się później krytyczny biznesowo i może wymagać dużych zmian później tak czy inaczej, argumentem dla mnie jest użycie Zasady "KISS"i mieć złożoność edycji na żywo w IDE, CLR / runtime i tak dalej zamiast budowania go w każdej aplikacji, gdzie może być przydatny później. Z pewnością potrzebne będą pewne defensywne programowanie i ćwiczenia, aby zmodyfikować niektóre usługi na żywo za pomocą tego rodzaju funkcji. Jak mówi się o Erlang devs)

 0
Author: Anonymous Coward,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-05-02 05:24:31