BETA
Aby się zalogować, najpiew wybierz portal.
Aby się zarejestrować, najpiew wybierz portal.
Podaj słowa kluczowe
Słowa kluczowe muszą mieć co najmniej 3 sąsiadujące znaki alfanumeryczne
Pole zawiera niedozwolone znaki

Baza wiedzy











Samomodyfikujący się program cz.1.

02-11-2009 11:01 | pawel.gorczynski
Artykuł pokazuje jak można wykorzystać przestrzeń nazw System.Codedom do stworzenia programu, który modyfikuje dynamicznie własną ścieżkę wykonania.

Ostatnio zastanawiałem się, czy można w C# napisać program, który sam modyfikuje się w trakcie działania. Okazało się (jak to zwykle w takich sytuacjach bywa), że można osiągnąć efekt co najmniej zbliżony ;).

Zakładam, że potencjalny czytelnik posiada dość biegłą znajomość koncepcji programowania obiektowego, oraz oczywiście języka C# ;).

Przestrzeń nazw System.Codedom

Jak sama nazwa wskazuje, jest to document object model dla kodu wykonywalnego, czyli obiektowa struktura reprezentująca tenże kod w postaci drzewka. Drzewko to jest oczywiście tylko abstrakcją programu, a zatem możemy na jego podstawie wygenerować kod w dowolnym zarządzanym języku, o ile tylko posiadamy odpowiedni provider (C# i VB są w standardzie ;)).

Przestrzeń ta stanowi bardziej wysokopoziomową alternatywę dla generacji kodu CIL (przestrzeń System.Reflection.Emit).

Stworzymy teraz w pamięci przykładową prostą klasę:

var compileUnit = newCodeCompileUnit(); //compile unit odpowiada logicznie assembly

var codeNamespace = newCodeNamespace("Some.Namespace"); //tworzymy przestrzeń nazwa

 

codeNamespace.Imports.Add(newCodeNamespaceImport("System")); //dołączamy sobie “using System;”

 

compileUnit.Namespaces.Add(codeNamespace); // włączamy przestrzeń nazw do naszego assembly

var typeDeclaration = newCodeTypeDeclaration("FooClass"); //tworzymy deklaracje typu

codeNamespace.Types.Add(typeDeclaration); //i włączamy ją do przestrzeni nazw

Dodajmy jeszcze do naszej klasy jedną metodę:

 

var method = newCodeMemberMethod

                             {

                                 Attributes = MemberAttributes.Public | MemberAttributes.Static,

                                 Name = “ZugZug”,

                                 ReturnType = newCodeTypeReference(typeof(string))

                             };

 

Mamy publiczną statyczną metodę o nazwie ZugZug zwracającą string. Dodajmy jej parametr typu int o nazwie blabla.

 

method.Parameters.Add(newCodeParameterDeclarationExpression(newCodeTypeReference(typeof (int)), "blabla"));

 

Samą metodę dodamy jeszcze do klasy:

typeDeclaration.Members.Add(method);

 

Warto byłoby także uzupełnić wnętrze metody kodem (potrzebujemy przynajmniej return’a żeby kod w ogóle się skompilował). Zwróćmy zatem przekazany parametr zamieniony na string:       

 

var returnStatement = newCodeMethodReturnStatement(

            new CodeMethodInvokeExpression(newCodeVariableReferenceExpression("blabla"), "ToString"));

 

method.Statements.Add(returnStatement);

 

Voila!

Następująca metoda pozwoli nam zamienić nasze wypociny na „żywy” kod C#:

private static string GenerateCode(CodeNamespace sourceCode)

{

    var codeGeneratorOptions = newCodeGeneratorOptions

    {

        BlankLinesBetweenMembers = true,

        BracingStyle = "C",

        IndentString = "    "

    };

    var codeGenerator = newCSharpCodeProvider().CreateGenerator();

    var code = newStringBuilder();

    var stringWriter = newStringWriter(code);

    codeGenerator.GenerateCodeFromNamespace(sourceCode, stringWriter, codeGeneratorOptions);

    return code.ToString();

}

 

Wypisany na konsolę efekt wygląda całkiem zadowalająco:

namespace Some.Namespace

{

    using System;

 

 

    public class FooClass

    {

 

        public static string ZugZug(int blabla)

        {

            return blabla.ToString();

        }

    }

}

 

Co więcej, kod ten możemy uruchomić w trakcie działania programu .

 

var csc = newCSharpCodeProvider(newDictionary<string, string>() {{"CompilerVersion", "v3.5"}}); //tworzymy provider

 

var parameters = newCompilerParameters(new[] {"System.dll", "System.Core.dll"}, "Test.dll", true)

                                     {

                                         GenerateExecutable = false,

                                         GenerateInMemory = true,   

                                     };

//oraz parametry kompilacji (ustawienie GenerateInMemory = false spowoduje zapisanie wyniku na dysk – póki co nie chcemy tego)

 

var results = csc.CompileAssemblyFromDom(parameters, new[] {compileUnit});

                results.Errors.Cast<CompilerError>().ToList().ForEach(error => Console.WriteLine(error.ErrorText));

 

//ewentualne błędy wypisujemy na konsolę

 

var type =  results.CompiledAssembly.GetType(codeNamespace.Name + "." + typeDeclaration.Name); //ze zbudowanego assembly pobieramy po nazwie nasz typ

 

var result = (string)type.InvokeMember("ZugZug", BindingFlags.InvokeMethod | BindingFlags.Public | BindingFlags.Static, null, null, new object[] {3});

// na koniec wywołujemy naszą metodę

 

Wypisując na konsolę zawartość zmiennej result dowiemy się, żę 3 to 3 ;).

 

W drugiej cześci pokażę, przykład modyfikacji kodu w trakcie działania.

Załączniki:

Podobne artykuły

Komentarze 2

Greg102
Greg102
0 pkt.
Nowicjusz
14-01-2011
oceń pozytywnie 0

Bardzo dobry artykul. Czekam na cz. 2.

pawel.gorczynski
pawel.gorczynski
20 pkt.
Nowicjusz
18-01-2011
oceń pozytywnie 0

Po mojej interwencji, po 1,5 roku, opublikowali cześć drugą: http://www.codeguru.pl/Articles/17454.aspx ;)

pkt.

Zaloguj się lub Zarejestruj się aby wykonać tę czynność.