Kompilieren einer WPF-Anwendung

Windows Presentation Foundation (WPF)-Anwendungen können als .NET Framework-EXE-, -DLL-Datei oder als Kombination aus beiden Assemblytypen erstellt werden. In diesem Thema wird beschrieben, wie WPF-Anwendungen erstellt werden, und die wichtigsten Schritte im Buildprozess werden erläutert.

Erstellen einer WPF-Anwendung

Zum Kompilieren einer WPF-Anwendung stehen folgende Methoden zur Verfügung:

WPF-Buildpipeline

Beim Erstellen eines WPF-Projekts wird die Kombination aus sprachspezifischen und WPF-spezifischen Zielen aufgerufen. Das Ausführen dieser Ziele wird als Buildpipeline bezeichnet. Die wichtigsten Schritte werden in der folgenden Abbildung dargestellt.

WPF build process

Präbuildinitialisierungen

Vor Beginn der Erstellung bestimmt MSBuild den Speicherort wichtiger Tools und Bibliotheken, darunter die folgenden:

  • Das .NET Framework

  • Die Windows SDK-Verzeichnisse

  • Der Speicherort von WPF-Verweisassemblys

  • Die Eigenschaft für die Assemblysuchpfade.

Der erste Speicherort, an dem MSBuild nach Assemblys sucht, ist das Verzeichnis für Verweisassemblys (%Programme%\Reference Assemblies\Microsoft\Framework\v3.0\). In diesem Schritt initialisiert der Buildprozess auch die verschiedenen Eigenschaften und Elementgruppen und führt die erforderlichen Bereinigungen durch.

Auflösen von Verweisen

Der Buildprozess sucht und bindet die Assemblys, die zum Erstellen des Anwendungsprojekts erforderlich sind. Diese Logik ist in der ResolveAssemblyReference-Aufgabe enthalten. Alle in der Projektdatei als Reference deklarierten Assemblys werden der Aufgabe mit Informationen zu Suchpfaden und Metadaten für Assemblys, die bereits im System installiert sind, zur Verfügung gestellt. Die Aufgabe sucht Assemblys und verwendet die Metadaten der installierten Assembly, um die WPF-Kernassemblys herauszufiltern, die in den Ausgabemanifesten nicht angezeigt werden müssen. Auf diese Weise vermeiden Sie redundante Informationen in den ClickOnce-Manifesten. Da die „PresentationFramework.dll“ ein Beispiel für eine Anwendung ist, die auf und für WPF erstellt wurde, insbesondere da sich alle WPF-Assemblys auf jedem Computer, auf dem .NET Framework installiert ist, am selben Speicherort befinden, ist das Einfügen sämtlicher Informationen für alle .NET Framework-Verweisassemblys in den Manifesten nicht erforderlich.

Markupkompilierungsdurchlauf 1

In diesem Schritt werden XAML-Dateien geparst und kompiliert, sodass während der Laufzeit kein XML geparst und Eigenschaftswerte validiert werden. Die kompilierte XAML-Datei wird zuvor mit Token versehen, sodass das Laden zur Laufzeit sehr viel schneller verläuft als das Laden einer XAML-Datei.

In diesem Schritt werden für jede XAML-Datei, die ein Page-Buildelement ist, die folgenden Aktionen ausgeführt:

  1. Die XAML-Datei wird vom Markupcompiler geparst.

  2. Eine kompilierte Darstellung wird für diese XAML-Datei erstellt und in den Ordner „obj\Release“ kopiert.

  3. Eine CodeDOM-Darstellung einer neuen Teilklasse wird erstellt und in den Ordner „obj\Release“ kopiert.

Außerdem wird eine sprachspezifische Codedatei für jede XAML-Datei generiert. Für eine „Page1.xaml“-Seite in einem Visual Basic-Projekt wird z. B. eine Datei „Page1.g.vb“ generiert und für eine „Page1.xaml“-Seite in einem C#-Projekt eine Datei „Page1.g.cs“. Das „.g“ im Dateinamen gibt an, dass die Datei generierten Code darstellt, der über eine Deklaration der partiellen Klasse für das Element der oberen Ebene der Markupdatei entspricht (z. B. Page oder Window). Die Klasse wird mit dem partial-Modifizierer in C# (Extends in Visual Basic) deklariert, um anzugeben, dass an einer anderen Stelle noch eine weitere Deklaration für die Klasse vorhanden ist, normalerweise in der CodeBehind-Datei „Page1.xaml.cs“.

Die partielle Klasse wird von der entsprechenden Basisklasse erweitert (z. B. Page für eine Seite) und implementiert die System.Windows.Markup.IComponentConnector-Schnittstelle. Die IComponentConnector-Schnittstelle verfügt über Methoden zum Initialisieren einer Komponente sowie zum Verbinden von Namen und Ereignissen für Elemente im Inhalt. Folglich sieht die Methodenimplementierung der generierten Codedatei folgendermaßen aus:

public void InitializeComponent() {
    if (_contentLoaded) {
        return;
    }
    _contentLoaded = true;
    System.Uri resourceLocater =
        new System.Uri(
            "window1.xaml",
            System.UriKind.RelativeOrAbsolute);
    System.Windows.Application.LoadComponent(this, resourceLocater);
}
Public Sub InitializeComponent() _

    If _contentLoaded Then
        Return
    End If

    _contentLoaded = True
    Dim resourceLocater As System.Uri = _
        New System.Uri("mainwindow.xaml", System.UriKind.Relative)

    System.Windows.Application.LoadComponent(Me, resourceLocater)

End Sub

Standardmäßig wird Markupkompilierung in derselben AppDomain ausgeführt wie die MSBuild-Engine. Dies ermöglicht einen erheblichen Leistungszuwachs. Dieses Verhalten kann mit der AlwaysCompileMarkupFilesInSeparateDomain-Eigenschaft umgeschaltet werden. Dies hat den Vorteil, dass alle Verweisassemblys durch Entladen der separaten AppDomain entladen werden.

Markupkompilierungsdurchlauf 2

Während des Durchlaufs 1 der Markupkompilierung werden nicht alle XAML-Seiten kompiliert. XAML-Dateien, die lokal definierte Typverweise aufweisen (Verweise auf Typen, die im Code an anderer Stelle im selben Projekt definiert sind), werden derzeit von der Kompilierung ausgenommen. Das liegt daran, dass diese lokal definierten Typen nur im Quellcode existieren und noch nicht kompiliert wurden. Um dies zu bestimmen, verwendet der Parser heuristische Verfahren, die das Suchen nach Elementen umfassen, z. B. x:Name in der Markupdatei. Wird eine solche Instanz gefunden, wird die Kompilierung der Markupdatei bis zur Kompilierung der Codedateien zurückgestellt. Anschließend werden diese Dateien im zweiten Durchlauf der Markupkompilierung verarbeitet.

Dateiklassifizierung

Der Buildprozess fügt Ausgabedateien in verschiedene Ressourcengruppen ein, und zwar abhängig von der Anwendungsassembly, in die sie platziert werden. In einer normalen nicht lokalisierten Anwendung werden alle als Resource markierten Datendateien in der Hauptassembly platziert (ausführbare Datei oder Bibliothek). Wenn UICulture im Projekt festgelegt ist, werden alle kompilierten XAML-Dateien und speziell als sprachspezifisch markierte Ressourcen in die Satellitenressourcenassembly eingefügt. Außerdem werden alle sprachneutralen Ressourcen in die Hauptassembly eingefügt. In diesem Schritt des Buildprozesses wird diese Bestimmung vorgenommen.

Die Buildaktionen ApplicationDefinition, Page und Resource in der Projektdatei können mit den Localizable-Metadaten erweitert werden (zulässige Werte sind true und false), die festlegen, ob die Datei sprachspezifisch oder sprachneutral ist.

Kernkompilierung

In dem Schritt der Kernkompilierung wird die Kompilierung von Codedateien ausgeführt. Dieser Vorgang wird in den sprachspezifischen Zieldateien „Microsoft.CSharp.targets“ und „Microsoft.VisualBasic.targets“ logisch koordiniert. Die Hauptassembly wird generiert, wenn die Heuristik einen einzelnen Durchlauf des Markupcompilers als ausreichend einschätzt. Wenn jedoch eine oder mehrere XAML-Dateien im Projekt Verweise auf lokal definierte Typen aufweisen, wird eine temporäre DLL-Datei generiert, damit die finalen Anwendungsassemblys nach Abschluss des zweiten Durchlaufs der Markupkompilierung erstellt werden können.

Manifestgenerierung

Nachdem alle Anwendungsassemblys und Inhaltsdateien fertiggestellt wurden, werden die ClickOnce-Manifeste am Ende des Buildprozesses für die Anwendung generiert.

Mit der Bereiststellungsmanifestdatei wird das Bereitstellungsmodell beschrieben: die aktuelle Version, Aktualisierungsverhalten und die Identität des Herausgebers mit der digitalen Signatur. Dieses Manifest sollte von Administratoren erstellt werden, die für die Bereitstellung zuständig sind. Die Dateierweiterung ist .xbap (für XAML Browser Applications (XBAPs)) und .application für installierte Anwendungen. Erstere wird durch die HostInBrowser-Projekteigenschaft vorgeschrieben. Als Ergebnis identifiziert das Manifest die Anwendung als vom Browser gehostet.

Im Anwendungsmanifest (die Datei „.exe.manifest“) werden die Anwendungsassemblys und abhängigen Bibliotheken beschrieben und die von der Anwendung benötigten Berechtigungen aufgeführt. Diese Datei sollte vom Anwendungsentwickler erstellt werden. Um eine ClickOnce-Anwendung zu starten, öffnet ein Benutzer die Bereitstellungsmanifestdatei der Anwendung.

Diese Manifestdateien werden stets für XBAPs erstellt. Für installierte Anwendungen werden sie nicht erstellt, sofern die GenerateManifests-Eigenschaft in der Projektdatei nicht mit dem Wert true angegeben wird.

XBAPs erhalten zwei zusätzliche Berechtigungen über die Berechtigungen hinaus, die herkömmlichen Anwendungen der Internetzone zugewiesen werden: WebBrowserPermission und MediaPermission. Das WPF-Buildsystem deklariert diese Berechtigungen im Anwendungsmanifest.

Unterstützung für inkrementelle Builds

Das WPF-Buildsystem unterstützt inkrementelle Builds. An Markup oder Code vorgenommene Änderungen werden auf intelligente Weise ermittelt. Außerdem werden nur jene Artefakte kompiliert, die von den Änderungen beeinflusst wurden. Der Mechanismus für inkrementelle Builds verwendet die folgenden Dateien:

  • Die Datei $(AssemblyName)_MarkupCompiler.Cache zum Verwalten des aktuellen Compilerzustands.

  • Die Datei $(AssemblyName)_MarkupCompiler.lref, um XAML-Dateien mit Verweisen auf lokal definierte Typen zwischenzuspeichern.

Im Folgenden finden Sie einen Satz von Regeln für inkrementelle Builds:

  • Die Datei ist die kleinste Einheit, bei der das Buildsystem eine Änderung erkennt. Demnach kann das Buildsystem bei einer Codedatei nicht erkennen, ob ein Typ geändert oder ob Code hinzugefügt wurde. Dasselbe gilt für Projektdateien.

  • Der Mechanismus für inkrementelle Builds muss wissen, ob eine XAML-Seite entweder eine Klasse definiert oder andere Klassen verwendet.

  • Wenn sich Reference-Einträge ändern, kompilieren Sie alle Seiten neu.

  • Wenn sich eine Codedatei ändert, kompilieren Sie alle Seiten mit lokal definierten Typverweisen neu.

  • Wenn sich eine XAML-Datei ändert:

    • Wenn XAML als Page in einem Projekt deklariert wird: Wenn das XAML keine lokal definierten Typverweise besitzt, kompilieren Sie dieses XAML und alle XAML-Seiten mit lokalen Verweisen neu. Wenn das XAML lokale Verweise aufweist, kompilieren Sie alle XAML-Seiten mit lokalen Verweisen neu.

    • Wenn XAML als ApplicationDefinition im Projekt deklariert ist: Kompilieren Sie alle XAML-Seiten neu (Grund: jedes XAML verfügt über einen Verweis auf einen eventuell geänderten Application-Typ).

  • Wenn die Projektdatei statt einer XAML-Datei eine Codedatei als Anwendungsdefinition deklariert:

    • Überprüfen Sie, ob sich der ApplicationClassName-Wert in der Projektdatei geändert hat (gibt es einen neuen Anwendungstyp?). Kompilieren Sie in diesem Fall die gesamte Anwendung neu.

    • Kompilieren Sie ansonsten alle XAML-Seiten mit lokalen Verweisen neu.

  • Wenn sich eine Projektdatei ändert: Wenden Sie alle vorangehenden Regeln an, und stellen Sie fest, was neu kompiliert werden muss. Änderungen an den folgenden Eigenschaften lösen eine vollständige Neukompilierung aus: AssemblyName, IntermediateOutputPath, RootNamespace und HostInBrowser.

Folgende Rekompilierungsszenarien sind möglich:

  • Die gesamte Anwendung wird neu kompiliert.

  • Nur XAML-Dateien, die lokal definierte Typverweise enthalten, werden neu kompiliert.

  • Nichts wird neu kompiliert (es hat keine Änderung im Projekt stattgefunden).

Siehe auch