Windows-Apps als Einzeldatei verteilen – Single Binaries und die Grenzen der OS-Abhängigkeit

· · Windows, Verteilung, Single Binary, .NET, C++, WebView2, WinUI

Dieser Artikel entstand aus einem lockeren Gespräch darüber, wie weit man unter Windows überhaupt von einer Single Binary sprechen darf. Ausgangspunkt war der folgende Beitrag. Sie können diesen Abschnitt überspringen – der Rest des Artikels lässt sich auch eigenständig lesen.

Unter Windows ist „wenn möglich als eine einzige Datei verteilen“ ein ganz gewöhnlicher Wunsch. Für interne Tools, Anbindungswerkzeuge für Geräte, Überwachungsterminals, Offline-Umgebungen und Umgebungen, die Installationsprogramme möglichst vermeiden wollen, ist die Umstellung auf eine Single Binary durchaus attraktiv.

Allerdings passt das Gespräch meist nicht mehr zusammen, wenn man es nicht vorher entwirrt. Denn hinter „wir wollen eine Single Binary“ unter Windows verstecken sich in der Praxis vier verschiedene Anliegen.

  • Wir wollen ein einziges Verteilungsartefakt
  • Wir wollen die .NET- oder Visual-C++-Laufzeit nicht vorab installieren müssen
  • Wir wollen ohne Installationsprogramm und ohne Administratorrechte einfach nur ablegen und starten können
  • Wir wollen nicht von Unterschieden zwischen Ziel-Windows-Versionen abhängen

Diese vier Anliegen sind nicht dasselbe. In der Praxis führt diese Denkweise am wenigsten in die Irre:

Ein Verteilungspaket auf 1 EXE zu reduzieren, ist gut möglich. Die Abhängigkeit vom Ziel-Windows auf null zu bringen, ist aber unmöglich.

Dieser Artikel arbeitet diese Grenze für die praktische Arbeit an Windows-Apps heraus.

1. Zuerst das Fazit

Zusammengefasst vorab:

  • Bei einer gewöhnlichen Desktop-EXE lässt sich die Single-Binary-Umstellung ziemlich weit treiben
  • Allerdings sind die Fähigkeit, auf 1 EXE zu reduzieren und die Unabhängigkeit vom Ziel-Windows zwei verschiedene Dinge
  • Bei Shell-Erweiterungen, Windows-Diensten, Treibern, WebView2 und Teilen von WinUI 3 geht es weniger um die Dateianzahl als vielmehr darum, was beim OS registriert wird und was vorausgesetzt wird
  • In der Praxis ist es am wichtigsten, getrennt zu entscheiden, ob Sie eine Single Binary wollen, ob Sie kein Installationsprogramm wollen oder ob Sie die OS-Abhängigkeit verringern wollen

Anders ausgedrückt sieht die Grenzziehung unter Windows so aus:

  • Verteilung auf ein einziges Artefakt reduzieren: gut möglich
  • Zusätzliche Laufzeiten mitbringen: gut möglich
  • Auf xcopy-Verteilung setzen: hängt vom App-Typ ab
  • Abhängigkeit vom Ziel-Windows beseitigen: unmöglich

1.1 Begriffe, die in diesem Artikel verwendet werden

Zuerst eine Übersicht über die Begriffe, die im Folgenden immer wieder auftauchen.

Begriff Aussprache/Ausschreibung Bedeutung in diesem Artikel
UCRT Universal C Runtime Der Teil der Standard-C-Bibliothek, der entstand, als die C-Laufzeit in Visual Studio 2015 aufgeteilt wurde. Ab Windows 10 ist er als Bestandteil des Betriebssystems enthalten
VC++-Redistributable-Paket Visual C++ Redistributable Ein Installationsprogramm, das die Visual-C++-Laufzeit-DLLs außer der UCRT auf dem Zielrechner installiert
framework-dependent Abhängig vom .NET der Zielumgebung Eine Verteilungsform, die voraussetzt, dass auf dem Zielrechner bereits eine .NET-Laufzeit installiert ist
self-contained .NET-Laufzeit inklusive Eine Form, bei der die App die komplette .NET-Laufzeit selbst mitbringt
single-file Einzeldatei-Veröffentlichung Eine .NET-Veröffentlichungsoption, die das Verteilungspaket zu einer einzigen EXE-Datei zusammenfasst
Native AOT Native Ahead-Of-Time Eine Verteilungsform, bei der IL beim Veröffentlichen vorab in nativen Code kompiliert wird. Zur Laufzeit kommt kein JIT zum Einsatz
app-local-Verteilung App-nahe Verteilung Eine Verteilungsart, bei der DLLs im selben Ordner wie die EXE liegen und nur von dieser App genutzt werden
xcopy-Verteilung Verteilung durch reines Kopieren Eine Verteilungsart, die ohne Installationsprogramm und ohne Administratorrechte auskommt – es reicht, den ganzen Ordner abzulegen
SCM Service Control Manager, Dienststeuerungs-Manager Der Mechanismus des Betriebssystems zur Registrierung, zum Starten und Stoppen von Windows-Diensten
UAC User Account Control, Benutzerkontensteuerung Der Windows-Sicherheitsmechanismus, der die Erhöhung auf Administratorrechte steuert
Shell-Erweiterung Shell-Erweiterung Eine COM-Komponente, die in Prozesse wie den Explorer geladen wird und dort läuft
Evergreen Dauerhaft aktualisiertes Verfahren Ein Verteilungsmodus, bei dem die WebView2 Runtime als eine gemeinsam genutzte, automatisch aktualisierte Instanz bereitgestellt wird
Fixed Version Versionsfestes Verfahren Ein Verteilungsmodus, bei dem eine bestimmte Version der WebView2 Runtime der eigenen App beigelegt wird
arch architecture, CPU-Architektur x86 / x64 / Arm64

2. „Single Binary“ in vier Stufen denken

Stellen wir die Stufen zuerst als Diagramm dar. Von unten nach oben wird es schwieriger, und die oberste Stufe ist unter Windows nicht erreichbar.

Stufe A: Verteilung besteht aus 1 Dateigut möglichStufe B: Keine vorherige Installation der Sprachlaufzeit nötiggut möglichStufe C: Keine Installation oder Registrierung nötigje nach App-TypStufe D: Keine Abhängigkeit vom Ziel-Windowsunter Windows nicht erreichbar

Was mit „wir wollen eine Single Binary“ genau gemeint ist, hängt davon ab, auf welche dieser vier Stufen sich der Wunsch bezieht – und das entscheidet, welche Arbeit tatsächlich nötig ist.

2.1 Stufe A: Verteilung besteht aus 1 Datei

Das ist die oberflächlichste Stufe.

  • Man kann eine einzige Datei per E-Mail verschicken
  • Man muss nur eine Datei auf einen USB-Stick legen
  • Am Zielort liegt nur app.exe

Das betrifft die sichtbare Verteilungseinheit. Auch wenn die App beim Start temporär Dateien entpackt oder von OS-seitigen DLLs abhängt, kann diese Bedingung allein erfüllt sein.

2.2 Stufe B: Keine vorherige Installation der Sprachlaufzeit nötig

Als Nächstes: Der Zustand, in dem die App läuft, ohne dass vorab eine .NET-Laufzeit oder das VC++-Redistributable-Paket auf dem Zielrechner installiert werden muss.

  • Statisches Linken bei C/C++
  • .NET self-contained
  • .NET single-file
  • .NET Native AOT

Auf dieser Stufe wird das Gefühl „das lässt sich eigenständig mitnehmen“ deutlich stärker.

2.3 Stufe C: Keine Installation oder Registrierung nötig

Ab hier wird es plötzlich schwierig.

Eine einfache EXE funktioniert manchmal schon, wenn man sie nur ablegt. Diese Dinge hier sind jedoch etwas anderes.

  • Shell-Erweiterungen
  • Windows-Dienste
  • Benutzerdefinierte URL-Schemas oder Dateizuordnungen
  • Treiber
  • Komponenten, die in andere Prozesse wie Explorer oder Office geladen werden

In diesem Bereich reicht das reine Ablegen der Datei nicht aus. Es braucht Registrierung auf OS-Seite oder eine Verdrahtung mit dem Host.

2.4 Stufe D: Keine Abhängigkeit vom Ziel-Windows

Das ist unter Windows unmöglich.

Windows-Apps laufen letztlich immer auf den APIs, dem Loader, dem Sicherheitsmodell und dem Gerätestack von Windows. Die Single-Binary-Umstellung deckt nur den Verantwortungsbereich der App selbst ab. Das Betriebssystem selbst wird dabei nicht mitgenommen.

3. Bereiche, in denen sich 1 EXE gut erreichen lässt

Auch unter Windows gibt es Apps, die sich vergleichsweise gut auf 1 EXE reduzieren lassen.

  • Eigenständig gestartete Desktop-Tools
  • Business-Apps, bei denen die EXE selbst UI und Verarbeitung übernimmt
  • Werkzeuge für Kommunikation, Dateiverarbeitung, Log-Sammlung, Überwachung oder Gerätesteuerung
  • Anwendungen, die keine Host-Integration mit Explorer oder Office benötigen
  • UIs, die keine Web-Laufzeit voraussetzen

Bei diesem Typ lässt sich vieles problemlos in die App selbst integrieren.

  • Eigener Code
  • Ressourcen
  • Manifest
  • Standardeinstellungen
  • Vorlagendaten
  • Manche Drittanbieter-Bibliotheken
  • Die Sprachlaufzeit selbst

Auch ohne DLLs vollständig in die EXE einzubetten, ist die app-local-Verteilung, bei der DLLs neben der EXE liegen, unter Windows eine ganz gewöhnliche, starke Option. In der Praxis heißt das:

  • eine einzelne app.exe
  • oder app.exe plus ein paar benachbarte DLLs
  • dabei kein Installationsprogramm, keine Administratorrechte, per xcopy verteilbar

Diese Form ist nicht selten leichter zu pflegen, als alles gewaltsam in eine einzige EXE zu pressen.

4. Windows-Abhängigkeiten, die auch bei 1 EXE bestehen bleiben

Wer davon ausgeht, „eine EXE bedeutet keine Abhängigkeit vom Ziel-Windows mehr“, gerät genau hier in Schwierigkeiten. Tatsächlich bleiben auch bei einer einzigen EXE Abhängigkeiten bestehen.

4.1 Abhängigkeit von der OS-Version

Jede Windows-API hat ein minimal unterstütztes OS. Auch die Unterschiede zwischen x64 und Arm64 spielen eine Rolle. Selbst bei einer einzigen EXE muss also von Anfang an feststehen:

  • Läuft sie noch unter Windows 10?
  • Wird Windows 11 vorausgesetzt?
  • Muss sie auch unter Windows Server laufen?
  • Welche der Architekturen x86 / x64 / Arm64 werden anvisiert?

4.2 Abhängigkeit von Systembibliotheken

Auch wenn man meint, eine einzige EXE zu haben, nutzt sie zur Laufzeit selbstverständlich vom OS bereitgestellte Komponenten.

  • kernel32.dll
  • user32.dll
  • advapi32.dll
  • Die COM-Infrastruktur
  • Die Infrastruktur der Dienststeuerung

Das liegt im Verantwortungsbereich von Windows.

4.3 Abhängigkeit vom Sicherheitsmodell

  • UAC
  • Datei-ACLs
  • Der Service Control Manager
  • Die Registry
  • Die Richtlinie zur Treibersignierung

Solche Dinge kann die App nicht allein in sich tragen.

4.4 Abhängigkeit von Host und Laufzeit

Handelt es sich nicht um eine eigenständig gestartete EXE, sondern um ein Design, das auf einem Host aufsetzt, steigen die Abhängigkeiten schlagartig.

  • Wird WebView2 verwendet: Die WebView2 Runtime wird benötigt
  • Wird WinUI 3 / Windows App SDK verwendet: Der Verteilungsmodus muss geklärt werden
  • Wird eine Shell-Erweiterung gebaut: Die Registrierung auf Explorer-Seite ist erforderlich

Mit anderen Worten: Die Wahl der UI oder der Integration wird häufig direkt zur Schwierigkeit der Verteilung.

5. Realistische Kompromisse nach Technologie

5.1 Natives C/C++

Natives C/C++ steht auf der Seite mit der größten Freiheit bei der Single-Binary-Umstellung. Statisches Linken ist eine Option, und eine eigenständig gestartete EXE lässt sich ziemlich weit reduzieren.

Wichtiger als alles in eine einzige Datei zu pressen ist in der Praxis aber:

  • Wie mit UCRT und der VC++-Laufzeit umgegangen wird
  • Ob Drittanbieter-DLLs app-local abgelegt werden
  • Wie eng die Ziel-CPU / das Ziel-OS eingegrenzt wird

Einstieg: Der Unterschied zwischen /MT und /MD

Bei MSVC entscheidet im Wesentlichen diese Compiler-Option darüber, ob die Laufzeit mitgebracht wird.

Option Was gelinkt wird Was auf dem Zielrechner benötigt wird
/MD ucrt.lib und vcruntime.lib, die Import-Bibliotheken für die DLLs. Zur Laufzeit werden ucrtbase.dll und vcruntime<Version>.dll verwendet UCRT und VC++-Laufzeit
/MT libucrt.lib, libvcruntime.lib und libcmt.lib werden statisch gelinkt Keine zusätzliche Laufzeit

Am einfachsten probiert man das in der Developer Command Prompt aus:

cl /std:c++17 /EHsc /O2 /MT app.cpp /Fe:app.exe

Dabei sind drei Punkte zu beachten.

  • Die UCRT wurde mit der Aufteilung der C-Laufzeit in Visual Studio 2015 zu einem Bestandteil von Windows und ist ab Windows 10 als Teil des Betriebssystems enthalten. Soll ein älteres Windows als Ziel dienen, ist eine erneute Verteilung über vcredist erforderlich.
  • Eine DLL mit /MT zu bauen, wird nicht empfohlen. Da der statisch gelinkte CRT-Zustand nur innerhalb dieser DLL gekapselt ist, weichen Speicherzuweisung, Locale-Verhalten und die Wirkung von _set_se_translator zwischen EXE und DLL voneinander ab. „EXE mit /MT, mitgelieferte DLL ebenfalls mit /MT“ nach Gefühl zu vereinheitlichen, führt zu Problemen bei Zuweisung und Freigabe über die Grenze hinweg.
  • /clr und /MT lassen sich nicht kombinieren. Kommt C++/CLI ins Spiel, ist /MD die richtige Wahl.

5.2 .NET

.NET bietet single-file, self-contained und Native AOT, sodass sich die sichtbare Verteilungseinheit ziemlich klein halten lässt.

Die Unterscheidung ist aber notwendig.

  • framework-dependent: Hängt vom .NET der Zielumgebung ab
  • self-contained: Bringt die .NET-Laufzeit mit
  • single-file: Reduziert die Verteilung auf ein Artefakt
  • Native AOT: Reduziert die Startzeit-Abhängigkeiten weiter, bringt aber auch Funktionseinschränkungen mit sich

„Single-file, also weniger OS-Abhängigkeit“ stimmt nicht. Was dadurch vor allem reduziert wird, ist, wie zusammenhängend das Verteilungspaket der App ist.

Einstieg: Die 3 Muster von dotnet publish

Um die Unterschiede erst einmal auszuprobieren, reichen diese drei Befehle.

rem 1. framework-dependent + single-file: Auf dem Zielrechner wird eine .NET-Laufzeit benötigt
dotnet publish -c Release -r win-x64 --self-contained false -p:PublishSingleFile=true

rem 2. self-contained + single-file: Die .NET-Laufzeit wird mitgeliefert
dotnet publish -c Release -r win-x64 --self-contained true -p:PublishSingleFile=true

rem 3. Native AOT: PublishAot muss vorher in der csproj gesetzt sein, dann veröffentlichen
dotnet publish -c Release -r win-x64

PublishSingleFile und PublishAot besser in der csproj als auf der Kommandozeile anzugeben, ist die offizielle Empfehlung. Dadurch wird die Kompatibilitätsanalyse während des Builds aktiv, was Überraschungen nach der Veröffentlichung reduziert.

<PropertyGroup>
  <PublishSingleFile>true</PublishSingleFile>
  <PublishAot>true</PublishAot>
</PropertyGroup>

Wird RuntimeIdentifier angegeben, ist SelfContained standardmäßig true. Soll es framework-dependent sein, muss --self-contained false explizit gesetzt werden. Um Native AOT unter Windows zu veröffentlichen, wird die Visual-Studio-Workload Desktopentwicklung mit C++ benötigt.

Kompromiss: „Auf eins reduzieren“ ist nicht umsonst

Es reicht nicht zu schreiben, was „geht“ – hier stehen auch die Kosten.

Startzeit-Entpacken bei single-file

  • Standardmäßig werden nur die managed DLLs gebündelt. Sie werden beim Start in den Speicher geladen, nicht in einen Ordner entpackt. Die nativen Binärdateien der Laufzeit selbst bleiben als eigene Dateien bestehen.
  • Sollen auch die nativen Binärdateien in einer einzigen Datei enthalten sein, kommt IncludeNativeLibrariesForSelfExtract zum Einsatz; soll vor dem Start alles entpackt werden, IncludeAllContentForSelfExtract. Beide bewirken „erst entpacken, dann starten“, und unter Windows wird dabei nach %TEMP%\.net entpackt. Mit DOTNET_BUNDLE_EXTRACT_BASE_DIR lässt sich der Ort ändern – wählen Sie aber keinen Ort, der von Benutzern oder Diensten mit anderen Rechten beschreibbar ist.
  • Wird EnableCompressionInSingleFile aktiviert, wird die EXE deutlich kleiner, aber der Start dauert länger, weil beim Start im Speicher entpackt werden muss. Auch die offizielle Dokumentation rät, vor dem Einsatz sowohl Größe als auch Startkosten zu messen. Die Auswirkung schwankt stark von App zu App.

API-Inkompatibilitäten bei single-file

Wird die Verteilung auf eine Datei reduziert, bricht Code, der von Dateipfaden ausgeht, still und leise.

API Verhalten bei single-file
Assembly.Location Gibt einen leeren String zurück
Assembly.CodeBase PlatformNotSupportedException
Assembly.GetFile IOException
Module.Name Gibt den String <Unknown> zurück

Wollen Sie auf Dateien neben der EXE zugreifen, verwenden Sie AppContext.BaseDirectory; brauchen Sie den Pfad der ausführbaren Datei, Environment.ProcessPath. Auch Drittanbieter-Bibliotheken nutzen diese APIs mitunter intern, weshalb nach der Umstellung auf single-file mindestens einmal ein Starttest auf echter Hardware nötig ist.

Funktionseinschränkungen bei Native AOT

Als Gegenleistung für „schnellerer Start, keine Laufzeit nötig“ entfällt Folgendes.

  • Dynamisches Laden wie bei Assembly.LoadFile
  • Codeerzeugung zur Laufzeit wie bei System.Reflection.Emit
  • C++/CLI
  • Unter Windows eingebettetes COM
  • Trimming ist Pflicht, daher gelten auch dessen Einschränkungen
  • Da es faktisch single-file ist, gelten auch die oben genannten API-Inkompatibilitäten
  • System.Linq.Expressions läuft immer interpretiert und ist damit langsamer als zur Laufzeit erzeugter Code

Auch die Zielplattform ist fest vorgegeben. Unter .NET 8 werden für Windows x64 und Arm64 unterstützt, ab .NET 9 kommt x86 hinzu. Der Gedanke „AnyCPU als eine einzige Datei“ funktioniert von vornherein nicht.

5.3 WebView2

Mit WebView2 ändert sich die Schwierigkeit der Single-Binary-Umstellung grundlegend. Hier geht es nicht um die Anzahl der EXE-Dateien, sondern um den Umgang mit der WebView2 Runtime.

Noch vor der Frage „lässt sich das auf 1 EXE reduzieren?“ stehen andere Fragen im Raum.

  • Wird die Runtime als in der Zielumgebung bereits vorhanden vorausgesetzt?
  • Wird Evergreen verwendet?
  • Wird eine Fixed Version mitgeliefert?
  • Wie weit trägt man selbst die Verantwortung für die Offline-Verteilung?

Der Unterschied zwischen Evergreen und Fixed Version lässt sich grob so zusammenfassen:

  Evergreen Fixed Version
Wer aktualisiert Microsoft. Automatische Aktualisierung Sie selbst. Ausgetauscht passend zu App-Updates
Instanz auf dem Gerät Eine gemeinsame Instanz für alle Apps Für jede App separat mitgeliefert
Verteilungsgröße Wächst kaum Überschreitet 250 MB
Installationsart Der Bootstrapper ist rund 2 MB groß und lädt nur das Nötige herunter. Für Offline-Verteilung wird der Standalone Installer mitgeliefert Das entpackte Binärpaket wird komplett zusammen mit der App verteilt
Bestehende Einschränkung Muss vor dem Start geprüft werden, ob sie auf dem Gerät vorhanden ist Kann nicht über einen Netzwerkpfad oder UNC-Pfad ausgeführt werden

Die Evergreen Runtime ist auf Windows 11 bereits als Bestandteil des Betriebssystems vorinstalliert. Allerdings gibt es unter Windows 10 nach wie vor Geräte, auf denen sie fehlt, weshalb Microsoft selbst schreibt, dass die Runtime auch bei der Wahl von Evergreen mitverteilt werden sollte. Mit anderen Worten: Das Verfahren, auf App-Seite selbst zu prüfen, ob sie vorhanden ist, und sie bei Bedarf nachzuinstallieren, ist so oder so nötig. Fixed Version erlaubt es, den Zeitpunkt der Aktualisierung selbst in der Hand zu behalten, wächst dafür aber um mehrere hundert MB. Losgelöst von der Frage nach der 1-EXE-Lösung muss das vorher geklärt werden.

5.4 WinUI 3 / Windows App SDK

Auch WinUI 3 ändert allein durch seine Wahl die Verteilungsanforderungen. Die Wahl der UI-Technologie ist unmittelbar die Wahl der Verteilungsmethode.

Konkret gibt es zwei Achsen, die zu entscheiden sind.

  • packaging: packaged (MSIX), packaged mit Verweis auf einen externen Ort, oder unpackaged
  • runtime: framework-dependent oder self-contained

Und aus Sicht der Single Binary wirkt sich diese Kombinationseinschränkung aus:

  • PublishSingleFile funktioniert nur bei WinUI-3-Apps, die unpackaged und self-contained sind, und setzt Windows App SDK 1.5 oder neuer voraus.
  • Bei packaged Apps und bei packaged Apps mit Verweis auf einen externen Ort funktioniert PublishSingleFile nicht.
  • Bei unpackaged geht die package identity verloren, wodurch Windows-Funktionen, die diese voraussetzen – Benachrichtigungen, Hintergrundaufgaben, Dateizuordnungen, Erweiterungen des Kontextmenüs –, nicht mehr genutzt werden können.

Bei WinUI 3 stoßen „auf 1 EXE reduzieren“ und „Windows-Erweiterungsfunktionen nutzen“ also direkt aufeinander. Wer die Single Binary in den Vordergrund stellen will, kommt oft schneller ans Ziel, wenn er zuerst die Grundannahme der UI-Technologie überdenkt.

6. Bereiche, die grundsätzlich Registrierung und Abhängigkeiten erfordern

6.1 Shell-Erweiterungen

Eine in den Explorer geladene Shell-Erweiterung ist etwas ganz anderes als eine „einfach abgelegte EXE“. Hier geht es weniger um die Dateianzahl als vielmehr darum, wie beim Explorer registriert wird.

6.2 Windows-Dienste

Auch wenn sich die eigentliche exe des Dienstes auf eine Datei reduzieren lässt, ist die Verteilung ein eigenes Thema.

  • Registrierung bei der SCM
  • Rechte
  • Startkonto
  • Wiederherstellungseinstellungen

müssen bedacht werden. Bei Diensten geht es also weniger darum, „auf 1 EXE zu reduzieren“, als vielmehr darum, „wie installiert wird“.

6.3 Treiber

Bei Treibern ist das noch eindeutiger. Sie stehen erst mit INF, Signierung und Installationsverfahren zusammen, weshalb sie von vornherein kaum auf dem Spielfeld der Single Binary landen.

7. Entscheidungstabelle für die Praxis

Für eine grobe Einschätzung ist diese Tabelle hilfreich.

Was gebaut werden soll Realistischer Grad für 1 EXE Was zuerst zu klären ist
Eigenständig gestartetes Win32-/C++-Tool Hoch Statisches Linken, Ziel-OS / -Architektur
Eigenständig gestartetes WinForms-/WPF-Tool Hoch Eignung von self-contained, single-file, Native AOT
WinUI 3 / Windows App SDK-App Mittel Verteilungsmodus, zusätzliche Abhängigkeiten
WebView2-basierte Desktop-UI Niedrig bis mittel Verteilungsart der Runtime
Explorer-Kontextmenü-Erweiterung oder Vorschau-Handler Niedrig COM-/Registry-Registrierung
Windows-Dienst Mittel SCM-Registrierung, Rechte, Aktualisierungsverfahren
App mit gebündeltem Treiber Niedrig INF, Signierung, Installation

Das Wichtigste an dieser Tabelle ist die Erkenntnis, dass „Anzahl der Binärdateien“ und „Umfang der Verteilungsverantwortung“ zwei verschiedene Dinge sind.

8. Was beim Verteilungsdesign zuerst zu entscheiden ist

Wer die Single-Binary-Umstellung zum Erfolg führen will, sollte vor der Implementierung einige Dinge festlegen.

8.1 Klären, was genau auf eins reduziert werden soll

  • Wollen Sie ein einziges Verteilungsartefakt?
  • Wollen Sie die vorherige Runtime-Installation loswerden?
  • Wollen Sie das Installationsprogramm loswerden?
  • Wollen Sie Offline-Updates vereinfachen?

Die Antwort bestimmt, welche Technologie gewählt wird.

8.2 Das minimal unterstützte Windows und die Architektur zuerst festlegen

Sowohl single-file als auch Native AOT sind grundsätzlich OS- und architekturspezifisch. Bleibt das unklar und man geht einfach nach dem Motto „irgendwie als eine Datei“ vor, gerät man am Ende wegen fehlender APIs oder inkompatibler Laufzeiten ins Stocken.

8.3 Festhalten, was mitgeliefert wird und was Windows überlassen bleibt

In der Praxis lassen sich viele Probleme allein dadurch vermeiden, dass man diese Tabelle einmal aufschreibt.

  • Was die App mitliefert
    • Die eigentliche exe
    • Eigene DLLs
    • Konfigurationsvorlagen
    • Die self-contained-Laufzeit
  • Was Windows überlassen bleibt
    • Systembibliotheken
    • OS-APIs
    • SCM / Registry / Explorer
    • Die Treiberinfrastruktur
  • Was separat als Voraussetzung angenommen wird
    • WebView2 Runtime
    • VC++ Redistributable
    • Office / Excel
    • Dedizierte Treiber

8.4 Wer die Single Binary priorisiert, sollte die Host-Integration verringern

Das wirkt ziemlich stark.

  • Auf die Shell-Erweiterung verzichten und eine normale EXE daraus machen
  • Nicht als Dienst laufen, sondern Aufgabenplanung oder expliziten Start nutzen
  • Statt WebView2 eine native UI verwenden
  • COM innerhalb des eigenen Prozesses kapseln

Kurz gesagt: Je mehr man Designs vermeidet, bei denen das OS etwas „lädt“ oder „registriert“, desto näher kommt man der Single Binary.

9. Zusammenfassung

Die Single-Binary-Umstellung unter Windows ist bis zu einem gewissen Grad durchaus möglich. Am Ende läuft aber alles auf diesen einen Satz hinaus:

Eine App auf 1 EXE zu reduzieren, ist möglich. Aber das Windows, von dem diese App abhängt, lässt sich nicht auf 1 EXE reduzieren.

Fünf Punkte, die besonders im Gedächtnis bleiben sollten:

  • Bei einer gewöhnlichen, eigenständig gestarteten EXE lässt sich die Verteilung als Einzeldatei ziemlich weit vorantreiben
  • Statisches Linken bei C/C++, .NET single-file und Native AOT sind starke Optionen
  • Allerdings verschwinden die Abhängigkeiten von OS-Version, Architektur, Systembibliotheken und Sicherheitsmodell nicht
  • Bei Shell-Erweiterungen, Diensten, Treibern, WebView2 und Teilen von WinUI 3 werden OS-Registrierung und zusätzliche Laufzeiten zum eigentlichen Thema
  • Ob die Single Binary gelingt, entscheidet sich daran, ob man von Anfang an klar trennt, was genau auf eins reduziert werden soll

Wer die Single Binary stark priorisiert, hat weit bessere Erfolgschancen, wenn er bereits bei der Technologieauswahl darauf achtet, die Kopplung mit dem OS zu verringern.

10. Quellen

Aktuelle Artikel mit denselben Schlagwörtern führen zu verwandten Themen weiter.

Diese Seiten ordnen den Artikel in einen größeren Leistungs- und Entscheidungskontext ein.

Dieser Artikel ist direkt mit den folgenden Leistungen verbunden.

Windows-App-Entwicklung

Bei der Verteilung von Windows-Apps lässt sich Nacharbeit vermeiden, wenn Single-File-Packaging, Runtime-Bundling, die Entscheidung für WebView2 oder WinUI sowie die Frage, ob eine Dienstanwendung sinnvoll ist, zusammen entworfen werden.

Häufige Fragen

Fragen, die in Beratungen zu diesem Artikelthema häufig gestellt werden.

Kann man eine Windows-App als eine einzige EXE-Datei verteilen?
Bei einem eigenständig gestarteten Desktop-Tool geht das ziemlich weit. Mit statischem Linken bei C/C++, mit self-contained oder single-file bei .NET oder mit Native AOT lässt sich das Verteilungspaket auf eine einzige EXE reduzieren. Allerdings sind „auf 1 EXE reduzierbar“ und „unabhängig vom Ziel-Windows“ zwei verschiedene Dinge – die Abhängigkeit von OS-Version, Architektur, Systembibliotheken und Sicherheitsmodell verschwindet dadurch nicht.
Entfällt bei .NET single-file die OS-Abhängigkeit?
Nein. Was single-file vor allem reduziert, ist, wie zusammenhängend das Verteilungspaket der App ist – nicht die OS-Abhängigkeit. Framework-dependent hängt vom .NET der Zielumgebung ab, self-contained bringt die .NET-Laufzeit mit, und Native AOT reduziert die Startzeit-Abhängigkeiten weiter, hat aber auch Funktionseinschränkungen. Sowohl single-file als auch Native AOT sind grundsätzlich OS- und architekturspezifisch, weshalb das minimal unterstützte Windows und die Ziel-Architektur von Anfang an feststehen müssen.
Bei welchen Apps ist eine einzige EXE schwer zu erreichen?
Bei Shell-Erweiterungen, Windows-Diensten, Treibern, WebView2-basierten UIs und Teilen von WinUI 3. Hier geht es weniger um die Anzahl der Dateien als um die Registrierung beim Betriebssystem und den Umgang mit zusätzlichen Laufzeiten. Eine Shell-Erweiterung muss etwa beim Explorer registriert werden, ein Dienst erfordert die Registrierung bei der SCM sowie den Entwurf von Rechten und Startkonto, und ein Treiber steht erst mit INF und Signierung – reines Ablegen der Dateien funktioniert hier nicht. Bei WebView2 muss zuerst geklärt werden, wie die WebView2 Runtime bereitgestellt wird.
Gibt es bessere Verteilungswege, als eine App gewaltsam in 1 EXE zu pressen?
Die app-local-Verteilung, bei der DLLs neben der EXE liegen, ist eine starke Option. Wenn `app.exe` mit ein paar benachbarten DLLs ohne Installationsprogramm und ohne Administratorrechte per xcopy verteilbar ist, ist das oft leichter zu pflegen, als alles gewaltsam in eine EXE zu pressen. Wichtig ist, zuerst zu trennen, ob Sie ein einziges Verteilungsartefakt, den Wegfall der vorherigen Runtime-Installation oder den Wegfall des Installationsprogramms wollen.

Autorenprofil

Profilseite des Artikelautors.

Go Komura

Geschäftsführer von KomuraSoft LLC

Spezialisiert auf Windows-Softwareentwicklung, technische Beratung und Fehleranalyse, insbesondere bei bestehenden Systemen und schwer reproduzierbaren Störungen.

Zurück zum Blog