Ersetzen von Anweisungen in MethodBody einer Methode

8

(Zunächst einmal ist dies ein sehr langer Post, aber keine Sorge: Ich habe bereits alles implementiert, ich frage nur nach Ihrer Meinung oder nach möglichen Alternativen.)

Ich habe Probleme bei der Implementierung des Folgenden; Ich würde etwas Hilfe schätzen:

  1. Ich bekomme einen Type als Parameter.
  2. Ich definiere eine Unterklasse mit Reflektion. Beachten Sie, dass ich nicht beabsichtige, den ursprünglichen Typ zu ändern, sondern einen neuen zu erstellen.
  3. Ich erstelle eine Eigenschaft pro Feld der ursprünglichen Klasse, wie folgt:

    %Vor%
  4. Für jede Methode der Superklasse erstelle ich eine analoge Methode in der Unterklasse. Der Body der Methode muss derselbe sein, außer dass ich die Anweisungen ldfld x durch callvirt this.get_X ersetze, das heißt, anstatt das Feld direkt zu lesen, rufe ich den get-Accessor auf.

Ich habe Probleme mit Schritt 4. Ich weiß, dass Sie Code nicht so manipulieren sollen, aber ich muss wirklich.

Folgendes habe ich versucht:

Versuch Nr. 1: Verwenden Sie Mono.Cecil. Dies würde es mir erlauben, den Körper der Methode in menschenlesbare Instructions zu analysieren und Anweisungen leicht zu ersetzen. Der ursprüngliche Typ befindet sich jedoch nicht in einer DLL-Datei, daher kann ich keine Möglichkeit finden, ihn mit Mono.Cecil zu laden. Schreiben Sie den Typ in eine DLL, dann laden Sie es, dann ändern Sie es und schreiben Sie den neuen Typ auf die Festplatte (was ich denke, ist die Art, wie Sie einen Typ mit Mono.Cecil erstellen), und dann lädt es scheint wie ein riesig Overhead.

Versuch 2: Verwenden Sie Mono.Reflection. Dies würde mir auch ermöglichen, den Körper in Instructions zu analysieren, aber dann habe ich keine Unterstützung für das Ersetzen von Anweisungen. Ich habe eine sehr hässliche und ineffiziente Lösung mit Mono.Reflection implementiert, aber es unterstützt noch keine Methoden, die try-catch-Anweisungen enthalten (obwohl ich denke, dass ich das implementieren kann) und ich bin besorgt, dass es andere Szenarien geben könnte was es nicht funktioniert, da ich den ILGenerator auf eine etwas ungewöhnliche Weise benutze. Außerdem ist es sehr hässlich;). Folgendes habe ich getan:

%Vor%

Und hier kommt die schreckliche, schreckliche Methode Reemit :

%Vor%

Verzweigungsanweisungen sind ein Sonderfall, weil instr.Operand ist SByte , aber Emit erwartet einen Operanden vom Typ Label . Daher die Notwendigkeit für die Dictionary labels .

Wie Sie sehen können, ist das ziemlich schrecklich. Außerdem funktioniert es nicht in allen Fällen, beispielsweise bei Methoden, die try-catch-Anweisungen enthalten, da ich sie nicht mit den Methoden BeginExceptionBlock , BeginCatchBlock usw. von ILGenerator ausgegeben habe. Das wird kompliziert. Ich denke, ich kann es tun: MethodBody hat eine Liste von ExceptionHandlingClause , die die notwendigen Informationen enthalten sollte, um dies zu tun. Aber ich mag diese Lösung sowieso nicht, also werde ich das als letzte Lösung speichern.

Versuch # 3: Gehen Sie bare-back und kopieren Sie einfach das by MethodBody.GetILAsByteArray() zurückgegebene Byte-Array, da ich nur einen einzelnen Befehl für einen anderen einzelnen Befehl der gleichen Größe ersetzen möchte, der den genau dasselbe Ergebnis: es lädt den gleichen Objekttyp auf den Stapel usw. Es werden also keine Labels verschoben und alles sollte genauso funktionieren. Ich habe dies getan, bestimmte Bytes des Arrays ersetzt und dann MethodBuilder.CreateMethodBody(byte[], int) aufgerufen, aber ich bekomme immer noch den gleichen Fehler mit Ausnahmen, und ich muss immer noch die lokalen Variablen deklarieren oder ich werde einen Fehler bekommen ... auch wenn Ich kopiere einfach den Körper der Methode und ändere nichts. Das ist also effizienter, aber ich muss mich immer noch um die Ausnahmen kümmern, usw.

Seufz.

Hier ist die Implementierung von Versuch # 3, falls jemand interessiert ist:

%Vor%

(Ich weiß, dass es nicht schön ist. Tut mir leid. Ich habe es schnell zusammengefügt, um zu sehen, ob es funktionieren würde.)

Ich habe nicht viel Hoffnung, aber kann irgendjemand etwas Besseres vorschlagen?

Tut mir leid wegen der extrem langen Post und danke.

UPDATE # 1: Aghh ... Ich habe gerade diese in der msdn-Dokumentation :

  

[Die CreateMethodBody-Methode] ist   derzeit nicht vollständig unterstützt. Das   Der Benutzer kann den Standort nicht angeben   Token-Fix-Ups und Exception-Handler.

Ich sollte wirklich die Dokumentation lesen, bevor ich etwas versuche. Eines Tages werde ich lernen ...

Das bedeutet, dass Option # 3 try-catch-Anweisungen nicht unterstützen kann, was es für mich nutzlos macht. Muss ich wirklich die schreckliche # 2 benutzen? :/ Hilfe! : P

UPDATE # 2: Ich habe erfolgreich # 2 mit Unterstützung für Ausnahmen implementiert. Es ist ziemlich hässlich, aber es funktioniert. Ich poste es hier, wenn ich den Code ein wenig verfeinere. Es ist keine Priorität, also kann es in ein paar Wochen sein. Lass dich einfach wissen, falls sich jemand dafür interessiert.

Danke für Ihre Vorschläge.

    
Alix 10.05.2010, 14:49
quelle

6 Antworten

1

Ich versuche, eine sehr ähnliche Sache zu machen. Ich habe bereits Ihren # 1 Ansatz ausprobiert, und ich stimme zu, dass dies einen enormen Overhead verursacht (ich habe es jedoch nicht genau gemessen).

Es gibt eine DynamicMethod Klasse, die laut MSDN ist - "Definiert und stellt eine dynamische Methode dar, die kompiliert, ausgeführt und verworfen werden kann. Aussortierte Methoden sind für die Garbage Collection verfügbar."

Leistung klingt es gut.

Mit der ILReader Bibliothek I könnte MethodInfo in DynamicMethod . Wenn Sie in die ConvertFrom-Methode der DyanmicMethodHelper-Klasse der finden Sie den Code, den wir benötigen:

%Vor%

Lassen Sie uns theoretisch den Code einer bestehenden Methode modifizieren und als dynamische Methode ausführen.

Mein einziges Problem ist jetzt, dass Mono.Cecil uns nicht erlaubt, den Bytecode einer Methode zu speichern (zumindest konnte ich den Weg dazu nicht finden). Wenn Sie den Mono.Cecil-Quellcode herunterladen, verfügt er über eine CodeWriter-Klasse, um die Aufgabe auszuführen, ist jedoch nicht öffentlich.

Ein anderes Problem, das ich bei diesem Ansatz habe, ist das MethodInfo - & gt; DynamicMethod-Umwandlung funktioniert nur mit statischen Methoden mit ILReader . Aber das kann man umgehen.

Die Ausführung des Aufrufs hängt von der Methode ab, die ich verwendet habe. Ich habe folgende Ergebnisse erhalten, nachdem ich 10'000'000 mal kurz die Methode aufgerufen habe:

  • Reflection.Invoke - 14 Sekunden
  • DynamicMethod.Invoke - 26 Sekunden
  • DynamicMethod mit Delegierten - 9 Sekunden

Als nächstes werde ich versuchen:

  1. Laden Sie die ursprüngliche Methode mit Cecil
  2. Ändern Sie den Code in Cecil
  3. entferne den unmodifizierten Code von der Assembly
  4. Speichern Sie die Assembly als MemoryStream anstelle von Datei
  5. Laden Sie die neue Assembly (aus dem Speicher) mit Reflection
  6. rufe die Methode mit Reflection auf, wenn es ein einmaliger Aufruf ist
  7. generieren die Delegierten von DynamicMethod und speichern sie, wenn ich diese Methode regelmäßig aufrufen möchte
  8. versuche herauszufinden, ob ich die nicht benötigten Assemblies aus dem Speicher entladen kann (stelle sowohl die MemoryStream als auch die Laufzeit-Assembly-Repräsentation frei)

Es hört sich nach viel Arbeit an und es funktioniert vielleicht nicht, wir werden sehen:)

Ich hoffe, es hilft, lassen Sie mich wissen, was Sie denken.

    
Andras 14.09.2012 11:22
quelle
0

Haben Sie PostSharp ausprobiert? Ich denke, dass es über die Ein Feldzugriffsaspekt .

    
Lucero 10.05.2010 15:00
quelle
0

Vielleicht habe ich etwas falsch verstanden, aber wenn Sie eine bestehende Instanz einer Klasse erweitern möchten, können Sie in Burg Dynamischer Proxy .

    
Oliver 10.05.2010 15:11
quelle
0

Sie müssten die Eigenschaften in der Basisklasse zuerst als virtuell oder abstrakt definieren. Außerdem müssen die Felder geändert werden, um "geschützt" zu sein, im Gegensatz zu "privat".

Oder verstehe ich hier etwas falsch?

    
MaLio 13.05.2010 14:38
quelle
0

Grundsätzlich kopieren Sie den Programmtext der ursprünglichen Klasse und nehmen dann regelmäßig Änderungen daran vor. Ihre aktuelle Methode besteht darin, den Objekt Code für die Klasse zu kopieren und diesen zu patchen. Ich kann verstehen, warum das hässlich aussieht; Du arbeitest auf einem extrem niedrigen Level.

Dies scheint so zu sein, als wäre es leicht, mit Quell-zu-Quell-Programmtransformationen zu arbeiten. Dies funktioniert auf der AST für den Quellcode und nicht der Quellcode selbst für die Genauigkeit. Siehe DMS Software Reengineering Toolkit für ein solches Tool. DMS hat einen vollständigen C # 4.0-Parser.

    
Ira Baxter 16.05.2010 13:25
quelle
0

Was ist mit der Verwendung von SetMethodBody anstelle von CreateMethodBody (das wäre eine Variation von # 3)? Es ist eine neue Methode in .NET 4.5 eingeführt und scheint Ausnahmen und Korrekturen zu unterstützen.

    
Shay Rojansky 10.09.2014 12:47
quelle