Eine WinForms-App dekompilieren: InitializeComponent des Designers und die Ressourcen rekonstruieren
Eine kompilierte WinForms-App hat weder .Designer.cs noch .resx — der Designer-Code wurde als InitializeComponent() in einen einzigen Typ zusammengeführt, und die UI-Texte und -Bilder liegen in einem eingebetteten .resources-Stream. So rekonstruiert Glass.NET ein originalgetreues InitializeComponent() aus der Binärdatei, macht die ComponentResourceManager-Werte sichtbar und lässt dich die eingebetteten Ressourcen exportieren — alles statisch, alles kostenlos.
Öffne ein WinForms-Projekt in Visual Studio, und jedes Formular besteht aus zwei Dateien. Es gibt MainForm.cs, wo dein Code lebt — der Konstruktor, die Button-Klick-Handler, die Logik, die du tatsächlich geschrieben hast — und es gibt MainForm.Designer.cs, die dem Designer gehört. Diese zweite Datei enthält eine einzige generierte Methode, InitializeComponent(), plus die privaten Felder für jedes Steuerelement des Formulars. Du öffnest sie selten; der Designer schreibt sie jedes Mal neu, wenn du etwas auf die Oberfläche ziehst.
Dann lieferst du die App aus, verlierst den Quellcode, und brauchst ihn Jahre später zurück. Du öffnest die .exe in einem Dekompiler, und die beiden Dateien sind weg. Es gibt keine MainForm.Designer.cs, keine .resx, nur einen MainForm-Typ mit allem darin. Das ist kein versagender Dekompiler — so wurde WinForms schon immer kompiliert. Dieser Artikel geht durch, was der Compiler mit deinem Designer-Code gemacht hat, und wie Glass.NET — der kostenlose, rein statische .NET-Dekompiler — ihn in etwas rekonstruiert, das du lesen und aus dem du neu bauen kannst.
Die Zwei-Datei-Aufteilung ist eine reine Quellcode-Illusion
Die Aufteilung zwischen MainForm.cs und MainForm.Designer.cs ist eine partielle Klasse in C#. Beide Dateien deklarieren partial class MainForm, und der Compiler führt sie zu einem einzigen Typ zusammen, noch bevor er überhaupt IL ausgibt. Die Aufteilung existiert ausschließlich, damit der Designer eine Datei hat, die er regenerieren kann, ohne deinen Code zu berühren. Die CLR kennt sie überhaupt nicht.
Angenommen, du hast ein winziges Formular mit einem Label und einem Button gebaut. Deine Hälfte sieht so aus:
// MainForm.cs — der Teil, den du schreibst
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private void greetButton_Click(object sender, EventArgs e)
{
greetingLabel.Text = $"Hello at {DateTime.Now:T}";
}
}
Die Hälfte des Designers — die Datei, die du fast nie öffnest — sieht so aus:
// MainForm.Designer.cs — der Teil, der dem Designer gehört
partial class MainForm
{
private System.Windows.Forms.Label greetingLabel;
private System.Windows.Forms.Button greetButton;
private void InitializeComponent()
{
this.greetingLabel = new System.Windows.Forms.Label();
this.greetButton = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// greetingLabel
//
this.greetingLabel.AutoSize = true;
this.greetingLabel.Location = new System.Drawing.Point(24, 24);
this.greetingLabel.Name = "greetingLabel";
this.greetingLabel.Size = new System.Drawing.Size(120, 15);
this.greetingLabel.TabIndex = 0;
this.greetingLabel.Text = "Not greeted yet";
//
// greetButton
//
this.greetButton.Anchor = System.Windows.Forms.AnchorStyles.Bottom;
this.greetButton.Location = new System.Drawing.Point(24, 64);
this.greetButton.Name = "greetButton";
this.greetButton.Size = new System.Drawing.Size(120, 32);
this.greetButton.TabIndex = 1;
this.greetButton.Text = "Greet me";
this.greetButton.Click += new System.EventHandler(this.greetButton_Click);
//
// MainForm
//
this.ClientSize = new System.Drawing.Size(300, 160);
this.Controls.Add(this.greetButton);
this.Controls.Add(this.greetingLabel);
this.Name = "MainForm";
this.Text = "Greeter";
this.ResumeLayout(false);
this.PerformLayout();
}
}
Zwei Dateien an der Tastatur, ein Typ in der Assembly. Der Compiler nimmt die Felddeklarationen aus der Designer-Datei, die Methoden aus beiden Dateien und gibt einen einzigen MainForm-Typ aus. InitializeComponent() wird zu einer gewöhnlichen privaten Instanzmethode, in den Metadaten nicht von irgendeiner Methode zu unterscheiden, die du von Hand geschrieben hast.
Was die Binärdatei tatsächlich enthält
Wenn Glass die Assembly öffnet, taucht der MainForm-Typ mit seinen übersichtlich angeordneten Mitgliedern auf: die Felder greetingLabel und greetButton, der Konstruktor, dein greetButton_Click-Handler und InitializeComponent(). Die privaten Steuerelement-Felder, die in der Designer-Datei lebten, stehen genau dort als Typmitglieder — private Label greetingLabel; und private Button greetButton; — weil der Compiler sie genau dort hingelegt hat.
InitializeComponent() rekonstruieren
InitializeComponent() ist nur eine Methode, also dekompiliert Glass sie, wie es jede Methode dekompiliert: indem es die IL liest und zu C# zurückhebt. Der Designer gibt sehr regelmäßige, fast mechanische IL aus — ein Steuerelement erzeugen, Eigenschaft um Eigenschaft setzen, es der Controls-Auflistung des Elternelements hinzufügen — und genau diese Art geradlinigen Codes rekonstruiert ein Dekompiler sauber. Was Glass erzeugt, liest sich im Grunde wie die Designer-Datei, die du verloren hast:
private void InitializeComponent()
{
this.greetingLabel = new Label();
this.greetButton = new Button();
this.SuspendLayout();
this.greetingLabel.AutoSize = true;
this.greetingLabel.Location = new Point(24, 24);
this.greetingLabel.Name = "greetingLabel";
this.greetingLabel.Size = new Size(120, 15);
this.greetingLabel.TabIndex = 0;
this.greetingLabel.Text = "Not greeted yet";
this.greetButton.Anchor = AnchorStyles.Bottom;
this.greetButton.Location = new Point(24, 64);
this.greetButton.Name = "greetButton";
this.greetButton.Size = new Size(120, 32);
this.greetButton.TabIndex = 1;
this.greetButton.Text = "Greet me";
this.greetButton.Click += this.greetButton_Click;
this.ClientSize = new Size(300, 160);
this.Controls.Add(this.greetButton);
this.Controls.Add(this.greetingLabel);
this.Name = "MainForm";
this.Text = "Greeter";
this.ResumeLayout(false);
this.PerformLayout();
}
Alles, worauf es ankam, ist zurück. Die SuspendLayout()/ResumeLayout()-Klammerung, die das Formular daran hindert, beim Hinzufügen der Steuerelemente zu flackern; die Zuweisungen von Location, Size, Text und Anchor; die Controls.Add(...)-Aufrufe in ihrer ursprünglichen Reihenfolge (die die Z-Reihenfolge ist, unabhängig von TabIndex); und die Ereignisverdrahtung — this.greetButton.Click += this.greetButton_Click; — die auf die Handler-Methode zeigt, die im selben Typ sitzt. Der Rumpf des Handlers selbst, greetButton_Click, wird als normale Methode dekompiliert; an ihr war nie etwas Besonderes. Lege das in eine Datei mit deinen rekonstruierten Handlern, und du hast ein kompilierendes Formular.
Der einzige kosmetische Unterschied sind die //-Kommentarbanner, die der Designer über jeden Steuerelement-Block schreibt. Das sind Quellcode-Kommentare; sie werden nicht in IL kompiliert, also sind sie weg. Der Code ist gleichwertig, nur ohne Kommentare.
Die Designer-Ressourcen und der ComponentResourceManager
Das Formular oben setzte seinen Text mit Zeichenketten-Literalen, die direkt in die IL eingebacken waren (this.greetButton.Text = "Greet me";). Lokalisierbare Formulare funktionieren anders. Wenn du die Eigenschaft Localizable eines Formulars auf true setzt, hört der Designer auf, Literale zu schreiben, und leitet stattdessen jede Eigenschaft durch einen ComponentResourceManager:
private void InitializeComponent()
{
ComponentResourceManager resources =
new ComponentResourceManager(typeof(MainForm));
this.greetButton = new Button();
this.SuspendLayout();
resources.ApplyResources(this.greetButton, "greetButton");
this.greetButton.Name = "greetButton";
this.greetButton.Click += this.greetButton_Click;
resources.ApplyResources(this, "$this");
this.Controls.Add(this.greetButton);
this.ResumeLayout(false);
}
Jetzt stehen greetButton.Text, seine Location, seine Size und sogar ein Bild oder Icon gar nicht mehr im Code. ApplyResources(this.greetButton, "greetButton") schlägt jeden mit greetButton. vorangestellten Eintrag — greetButton.Text, greetButton.Size, greetButton.Location — in einer Ressourcentabelle nach und wendet ihn an. Diese Tabelle ist der eingebettete .resources-Stream, benannt <Namespace>.<Form>.resources: Für ein Formular Greeter.MainForm im Namensraum Greeter ist der Stream Greeter.MainForm.resources. Er wurde aus der MainForm.resx kompiliert, die du zur Entwurfszeit bearbeitet hast, und die kulturspezifischen Varianten (MainForm.fr.resx, MainForm.de.resx) wurden in Satelliten-Assemblys unter den Ordnern fr/ und de/ kompiliert.
Glass dekompiliert die ApplyResources-Aufrufe originalgetreu — du siehst, welche Steuerelemente ressourcengesteuert sind und unter welchem Schlüssel. Interessant ist aber, dass es auch den eingebetteten Ressourcen-Stream selbst sichtbar macht. Klappe den Ressourcen-Knoten der Assembly auf und wähle Greeter.MainForm.resources, und Glass listet die Einträge auf, die ApplyResources zur Laufzeit zieht: greetButton.Text = "Greet me", greetButton.Size = eine serialisierte Size, $this.Text = "Greeter" sowie jedes Bild oder Icon als Binärwert, den du ansehen und exportieren kannst. So liest du die tatsächlichen Zeichenketten eines lokalisierten Formulars — einschließlich der französischen und deutschen in den Satelliten-Assemblys — ohne es auszuführen, und so holst du die Bilder und Icons zurück, die ApplyResources verdrahtet hat.
Von der CLI aus ausführen
Alles Obige ist skriptbar. Der Unterbefehl glass decompile rekonstruiert den Typ ohne Oberfläche, was du bei einem Ordner voller zurückgeholter Binärdateien willst:
$ glass decompile ./Greeter.exe --type Greeter.MainForm -o ./recovered/MainForm.cs
Das schreibt den rekonstruierten MainForm — Felder, InitializeComponent(), Handler und alles — in eine einzige Datei. Um die Designer-Ressourcen daneben herauszuziehen, exportiert Glass den eingebetteten .resources-Stream und seinen Inhalt, sodass die Zeichenketten, Bilder und Icons auf der Festplatte landen, wo du sie inspizieren oder wieder in eine .resx überführen kannst. Jeder dieser Aufrufe liest die Binärdatei und rekonstruiert aus Metadaten und IL; keiner führt die Assembly aus, also bleibt eine WinForms-.exe, die du nie starten würdest, dennoch sicher zu dekompilieren und zu exportieren.
Was du zurückbekommst — und was nicht
Sei klar über die Grenzen, denn sie folgen unmittelbar daraus, was der Compiler behält:
- Du bekommst ein originalgetreues
InitializeComponent()— die Steuerelement-Erzeugung,SuspendLayout/ResumeLayout, jede Eigenschaftszuweisung, dieControls.Add-Reihenfolge und die Ereignisverdrahtung — plus die Steuerelement-Felder, die Handler-Rümpfe und die Werte der Ressourcen aus dem eingebetteten Stream. - Du bekommst nicht die ursprüngliche Zwei-Datei-Aufteilung. Es gab nie eine
MainForm.Designer.csin der Binärdatei; es gab einen einzigenMainForm-Typ. Glass gibt dir eine Datei. Wenn der Designer das Formular wieder öffnen können soll, teile sie von Hand in ein partielles.Designer.csauf — ein mechanischer Handgriff. - Du bekommst nicht das originale
.resx-XML. Dieses Autorenformat wird in den binären.resources-Stream wegkompiliert und nie ausgeliefert. Aber da die Werte alle aus dem Stream zurückholbar sind, kannst du daraus wieder eine.resxerzeugen, wenn du das Autorenformat erneut brauchst.
Kurz gesagt: Die Absicht des Designers überlebt die Kompilierung vollständig — das Layout, die Verdrahtung, die Zeichenketten und Bilder sind alle da — auch wenn die beiden Dateien und das .resx-XML, die sie ausdrückten, es nicht tun. Glass liest, was die Binärdatei behalten hat, und gibt es dir als lesbares C# und zurückholbare Ressourcendaten zurück.
Genau das ist der Ablauf, um deinen eigenen verlorenen WinForms-Quellcode zurückzuholen oder eine Abhängigkeit zu verstehen, über deren Oberfläche du nachdenken musst. Hol dir Glass.NET von der Produktseite und richte es auf ein Formular. Es ist kostenlos — keine Lizenz, keine Plätze, keine Anmeldung — und es führt die App, die es liest, niemals aus.
Nebula.NET testen
Härten Sie Ihren .NET-Code in wenigen Minuten — starten Sie mit der kostenlosen Edition.