Distribution en un seul fichier des applications Windows - Binaire unique et limites des dépendances au système

· Mis à jour le: · · Windows, Distribution, Binaire unique, .NET, C++, WebView2, WinUI

Cet article est parti de la publication suivante.

Sous Windows, « si possible, on aimerait distribuer ça en un seul fichier » est une demande tout à fait courante. Pour les outils internes, les outils d’intégration avec des équipements, les postes de surveillance, les environnements hors ligne, et les sites qui veulent autant que possible éviter les installateurs, le passage en binaire unique est très séduisant.

Cependant, si l’on ne clarifie pas ce point dès le départ, la discussion finit généralement par ne plus se recouper en cours de route. En effet, ce qu’on appelle « vouloir un binaire unique » sous Windows mélange en réalité facilement quatre sujets distincts.

  • On veut réduire la distribution à un seul artefact
  • On veut éviter d’avoir à préinstaller les runtimes .NET ou Visual C++
  • On veut que ça fonctionne simplement en le déposant, sans installateur ni droits administrateur
  • On ne veut pas dépendre des différences entre versions de Windows cibles

Ces quatre points ne sont pas identiques. En pratique, c’est cette façon de voir les choses qui colle le mieux à la réalité :

On peut, dans une large mesure, ramener la distribution à un seul EXE. Mais on ne peut pas réduire à zéro la dépendance au Windows cible.

Cet article fait le point sur cette frontière, du point de vue pratique du développement d’applications Windows.

1. D’abord, la conclusion

Pour résumer d’emblée la conclusion :

  • Pour un EXE de bureau ordinaire, on peut pousser le passage en binaire unique jusqu’à un niveau assez élevé
  • Cependant, pouvoir tenir en un seul EXE et ne pas dépendre du Windows cible sont deux choses différentes
  • Pour les extensions Shell, les services Windows, les pilotes, WebView2 et une partie de WinUI 3, le vrai sujet tend à être moins le nombre de fichiers que ce que l’on enregistre auprès de l’OS et ce que l’on présuppose de lui
  • Ce qui compte le plus en pratique, c’est de décider séparément si l’on veut un binaire unique, l’absence d’installateur, ou moins de dépendances au système

Autrement dit, la ligne de partage sous Windows se dessine ainsi.

  • Réduire la distribution à un seul artefact : très réalisable
  • Embarquer des runtimes supplémentaires : très réalisable
  • Se rapprocher d’une distribution xcopy : dépend du type d’application
  • Supprimer les dépendances côté Windows cible : impossible

2. Penser « binaire unique » en quatre niveaux

2.1 Niveau A : un seul artefact de distribution

Le niveau le plus superficiel est celui-ci.

  • On peut l’envoyer par e-mail en un seul fichier
  • Il suffit d’en placer un seul sur une clé USB
  • On ne place que app.exe à l’emplacement de destination

C’est une question d’unité de distribution apparente. En réalité, même si l’application extrait temporairement des fichiers au démarrage, ou dépend de DLL côté OS, cette seule condition peut être satisfaite.

2.2 Niveau B : pas besoin de préinstaller le runtime du langage

Ensuite vient l’état où l’application fonctionne sans avoir besoin d’installer au préalable le runtime .NET ou le package redistribuable VC++ sur la machine cible.

  • Liaison statique en C/C++
  • Mode self-contained de .NET
  • Mode single-file de .NET
  • Native AOT de .NET

À ce niveau, la sensation de pouvoir « l’emporter tel quel » devient nettement plus forte.

2.3 Niveau C : pas besoin d’installation ni d’enregistrement

À partir d’ici, les choses se compliquent brusquement.

Un simple EXE peut fonctionner en le déposant tel quel. Mais les cas suivants sont différents.

  • Extensions Shell
  • Services Windows
  • Schémas d’URL personnalisés et associations de fichiers
  • Pilotes
  • Composants chargés dans d’autres processus, comme l’Explorateur ou Office

Ce domaine ne se règle pas en se contentant de déposer un fichier. Il faut un enregistrement côté OS, ou une mise en relation avec l’hôte.

2.4 Niveau D : aucune dépendance au Windows cible

C’est impossible sous Windows.

Une application Windows s’exécute en fin de compte au-dessus des API Windows, du chargeur, du modèle de sécurité et de la pile de périphériques. Ce que le passage en binaire unique peut couvrir se limite au périmètre de responsabilité propre à l’application. On n’emporte pas l’OS lui-même avec soi.

3. Les domaines où un seul EXE est assez facilement atteignable

Même sous Windows, certaines applications se prêtent relativement bien à un seul EXE.

  • Outils de bureau lancés de manière autonome
  • Applications métier où l’EXE lui-même porte l’interface et le traitement
  • Outils de type communication, traitement de fichiers, collecte de journaux, surveillance ou pilotage d’équipements
  • Ce qui ne nécessite pas d’intégration hôte avec l’Explorateur ou Office
  • Interfaces qui ne présupposent pas de runtime web

Pour ce type d’application, beaucoup d’éléments se prêtent facilement à être inclus dans l’application elle-même.

  • Code propre
  • Ressources
  • Manifeste
  • Paramètres par défaut
  • Données de modèle
  • Certaines bibliothèques tierces
  • Le runtime du langage lui-même

De plus, même sans embarquer complètement les DLL à l’intérieur de l’EXE, la distribution app-local, qui place les DLL à côté de l’EXE, est une option tout à fait solide sous Windows. En pratique,

  • un seul app.exe
  • ou app.exe accompagné de quelques DLL adjacentes
  • mais sans installateur, sans droits administrateur, distribuable par xcopy

Il n’est pas rare que cette forme soit plus facile à maintenir que de tout compresser de force en un seul EXE.

4. Les dépendances à Windows qui subsistent même avec un seul EXE

Si l’on se dit « avec un seul EXE, on ne dépend plus du Windows cible », c’est là que les problèmes surviennent. En réalité, des dépendances subsistent même avec un seul EXE.

4.1 Dépendance à la version de l’OS

Chaque API Windows a sa propre version d’OS minimale supportée. Il existe aussi des différences entre x64 et Arm64. Autrement dit, même avec un seul EXE, il faut fixer dès le départ :

  • L’application doit-elle fonctionner jusqu’à Windows 10 ?
  • Windows 11 est-il un prérequis ?
  • Doit-elle aussi fonctionner sous Windows Server ?
  • Quelle(s) architecture(s) parmi x86 / x64 / Arm64 sont ciblées ?

4.2 Dépendance aux DLL système

Même si l’on pense avoir réduit l’application à un seul EXE, à l’exécution elle utilise bien entendu des composants fournis par l’OS.

  • kernel32.dll
  • user32.dll
  • advapi32.dll
  • L’infrastructure COM
  • L’infrastructure de contrôle des services

Tout cela relève du périmètre de responsabilité de Windows.

4.3 Dépendance au modèle de sécurité

  • UAC
  • Les ACL de fichiers
  • Le gestionnaire de contrôle des services
  • Le Registre
  • La politique de signature des pilotes

Ce genre d’éléments ne peut pas être pris en charge par l’application seule.

4.4 Dépendance à l’hôte ou au runtime

Si la conception n’est pas celle d’un EXE autonome, mais d’une application qui repose sur un hôte, les dépendances augmentent d’un coup.

  • Utiliser WebView2 : le WebView2 Runtime est nécessaire
  • Utiliser WinUI 3 / Windows App SDK : il faut clarifier le mode de distribution
  • Créer une extension Shell : un enregistrement côté Explorateur est nécessaire

Autrement dit, il arrive souvent que le choix de l’interface ou de l’intégration devienne directement la difficulté de la distribution.

5. Un point de vue réaliste par technologie

5.1 C/C++ natif

Le C/C++ natif se situe du côté où le passage en binaire unique offre le plus de liberté. Il est possible d’opter pour la liaison statique, et un EXE autonome peut assez facilement être ramené à un seul fichier.

Cependant, plus important en pratique que de tout entasser dans un seul fichier, il y a :

  • Que faire de l’UCRT et du runtime VC++
  • Placer ou non les DLL tierces en app-local
  • Jusqu’où restreindre le CPU / l’OS cible

5.2 .NET

.NET dispose de single-file, self-contained et Native AOT, ce qui permet de rendre l’unité de distribution apparente assez réduite.

Il faut cependant garder ces distinctions claires.

  • framework-dependent : dépend du .NET présent dans l’environnement cible
  • self-contained : embarque le runtime .NET
  • single-file : regroupe la distribution en un seul artefact
  • Native AOT : réduit encore les dépendances au démarrage, mais avec des contraintes fonctionnelles

Ce n’est pas parce que c’est du single-file que les dépendances au système diminuent. Ce qui diminue, c’est avant tout la dispersion des artefacts de distribution de l’application.

5.3 WebView2

Adopter WebView2 change complètement la difficulté du binaire unique. Le vrai sujet ici n’est pas le nombre d’EXE, mais la façon de gérer le WebView2 Runtime.

Avant même de se demander « peut-on en faire un seul EXE », il y a des questions à se poser en premier.

  • Suppose-t-on que le Runtime existe déjà dans l’environnement ?
  • Utilise-t-on Evergreen ?
  • Embarque-t-on une Fixed Version ?
  • Jusqu’où prend-on la responsabilité de la distribution hors ligne ?

5.4 WinUI 3 / Windows App SDK

WinUI 3 aussi voit ses exigences de distribution changer dès l’adoption. Le choix de la technologie d’interface devient, de fait, le choix de la méthode de distribution.

Si le binaire unique est la priorité absolue, il est souvent plus rapide de remettre d’abord en question le choix de la technologie d’interface.

6. Les domaines qui exigent fondamentalement un enregistrement et des dépendances

6.1 Extensions Shell

Une extension Shell chargée dans l’Explorateur est un objet différent d’un simple « EXE à déposer ». Ici, le sujet n’est pas le nombre de fichiers mais la façon de l’enregistrer auprès de l’Explorateur.

6.2 Services Windows

Même si l’exe du service lui-même peut tenir en un seul fichier, la distribution reste un problème à part.

  • L’enregistrement auprès du SCM
  • Les privilèges
  • Le compte de démarrage
  • Les paramètres de récupération

doivent être pris en compte. Autrement dit, pour un service, le vrai travail à approfondir n’est pas « en faire un seul EXE » mais « comment l’installer ».

6.3 Pilotes

Pour les pilotes, c’est encore plus net. Ils ne peuvent exister qu’en intégrant le fichier INF, la signature et la procédure d’installation, ce qui fait qu’ils se prêtent mal, dès le départ, au terrain du binaire unique.

7. Un tableau de décision pour la pratique

Pour un jugement rapide, ce tableau est pratique.

Ce que l’on veut construire Faisabilité d’un seul EXE Ce qu’il faut d’abord examiner
Outil Win32 / C++ autonome Élevée Liaison statique, OS / architecture cible
Outil WinForms / WPF autonome Élevée Pertinence de self-contained, single-file, Native AOT
Application WinUI 3 / Windows App SDK Moyenne Mode de distribution, dépendances supplémentaires
Interface de bureau basée sur WebView2 Faible à moyenne Mode de distribution du Runtime
Extension clic droit ou aperçu de l’Explorateur Faible Enregistrement COM / Registre
Service Windows Moyenne Enregistrement SCM, privilèges, procédure de mise à jour
Application intégrant un pilote Faible INF, signature, installation

Ce qui compte le plus dans ce tableau, c’est de comprendre que « le nombre de binaires » et « le périmètre de responsabilité de la distribution » sont deux choses différentes.

8. Ce qu’il faut décider en amont dans la conception de la distribution

Pour réussir le passage en binaire unique, il y a des points à trancher avant même l’implémentation.

8.1 Décider ce que l’on veut vraiment ramener à « un seul »

  • Veut-on un seul artefact de distribution ?
  • Veut-on supprimer la préinstallation du runtime ?
  • Veut-on se passer d’installateur ?
  • Veut-on faciliter les mises à jour hors ligne ?

La réponse à ces questions détermine la technologie à choisir.

8.2 Fixer dès le départ le Windows minimal supporté et l’architecture

Le single-file comme Native AOT sont, par nature, spécifiques à un OS et une architecture. Si l’on avance en laissant ce point flou, avec pour seule consigne « un seul fichier, coûte que coûte », on finit par se heurter à des API manquantes ou des incompatibilités de runtime.

8.3 Formaliser ce qui est embarqué et ce qui est laissé à Windows

En pratique, le simple fait de rédiger ce tableau réduit considérablement les incidents.

  • Ce que l’application embarque
    • L’exe principal
    • Les DLL propres
    • Les modèles de configuration
    • Le runtime self-contained
  • Ce que l’on laisse à Windows
    • Les DLL système
    • Les API de l’OS
    • Le SCM / le Registre / l’Explorateur
    • L’infrastructure des pilotes
  • Ce que l’on présuppose séparément
    • Le WebView2 Runtime
    • Le VC++ Redistributable
    • Office / Excel
    • Les pilotes dédiés

8.4 Si l’on priorise le binaire unique, réduire l’intégration à l’hôte

Cela a un effet notable.

  • Abandonner l’extension Shell au profit d’un EXE ordinaire
  • Ne pas en faire un service, et se contenter du Planificateur de tâches ou d’un lancement explicite
  • Utiliser une interface native plutôt que WebView2
  • Garder COM confiné à son propre processus

En somme, plus on réduit les conceptions qui font que l’OS doit « charger » ou « enregistrer » son code, plus on se rapproche du binaire unique.

9. Résumé

Le passage en binaire unique sous Windows est réalisable dans une large mesure. Mais tout se ramène en définitive à cette phrase :

On peut faire d’une application un seul EXE. Mais on ne peut pas faire du Windows dont cette application dépend un seul EXE.

Voici cinq points à retenir en particulier.

  • Pour un EXE ordinaire lancé de manière autonome, on peut pousser la distribution en un seul fichier remarquablement loin
  • La liaison statique en C/C++, le single-file de .NET et Native AOT sont des options solides
  • Cependant, les dépendances à la version de l’OS, à l’architecture, aux DLL système et au modèle de sécurité ne disparaissent pas
  • Pour les extensions Shell, les services, les pilotes, WebView2 et une partie de WinUI 3, l’enregistrement auprès de l’OS et les runtimes supplémentaires deviennent le sujet principal
  • La réussite du binaire unique se joue en clarifiant dès le départ « ce que l’on veut vraiment ramener à un seul »

Si l’on privilégie fortement le binaire unique, concevoir dès le choix des technologies dans le sens d’une réduction du couplage avec l’OS rend le succès bien plus probable.

10. Références

</content>

Articles récents partageant les mêmes étiquettes, pour approfondir des sujets proches.

Ces pages replacent le sujet dans un contexte plus large de services et de décisions.

Cet article est directement lié aux services suivants.

Développement d'applications Windows

Pour la distribution d'une application Windows, concevoir dès le départ le passage en single-file, l'intégration du runtime, le choix d'adopter WebView2 ou WinUI, et l'opportunité de la faire fonctionner en service permet de réduire les retours en arrière.

Questions fréquentes

Questions souvent posées lors d’une consultation sur le sujet de cet article.

Peut-on distribuer une application Windows sous la forme d'un seul fichier EXE ?
Pour un outil de bureau lancé de manière autonome, c'est possible jusqu'à un niveau assez élevé. En utilisant la liaison statique en C/C++, le mode self-contained ou single-file de .NET, ou encore Native AOT, on peut regrouper la distribution en un seul EXE. Cependant, pouvoir tenir en un seul EXE et ne pas dépendre du Windows cible sont deux choses différentes : les dépendances à la version de l'OS, à l'architecture, aux DLL système et au modèle de sécurité ne disparaissent pas.
Le mode single-file de .NET fait-il disparaître les dépendances au système ?
Non. Ce que le single-file réduit avant tout, c'est la dispersion des artefacts de distribution de l'application, pas les dépendances au système. Le mode framework-dependent dépend du .NET installé dans l'environnement cible, le mode self-contained embarque le runtime .NET, et Native AOT réduit encore davantage les dépendances au démarrage, mais au prix de certaines contraintes fonctionnelles. Le single-file comme Native AOT sont par nature spécifiques à un OS et une architecture, il faut donc fixer dès le départ la version minimale de Windows et l'architecture cible.
Quels types d'applications sont difficiles à ramener à un seul EXE ?
Les extensions Shell, les services Windows, les pilotes, les interfaces basées sur WebView2, et une partie de WinUI 3. Pour ces applications, le vrai sujet n'est pas le nombre de fichiers mais l'enregistrement auprès de l'OS et la gestion des runtimes supplémentaires. Par exemple, une extension Shell doit être enregistrée auprès de l'Explorateur, un service nécessite un enregistrement auprès du SCM ainsi qu'une conception des privilèges et du compte de démarrage, et un pilote ne peut exister qu'avec un fichier INF et une signature, si bien qu'une distribution qui se limite à « déposer le fichier » ne fonctionne pas. Pour WebView2, il faut d'abord décider du mode de distribution du WebView2 Runtime.
Existe-t-il une meilleure méthode de distribution que de tout forcer dans un seul EXE ?
La distribution app-local, qui place les DLL à côté de l'EXE, est une option solide. Même sous la forme d'un app.exe accompagné de quelques DLL adjacentes, si la distribution peut se faire par xcopy sans installateur ni droits administrateur, il n'est pas rare que ce soit plus facile à maintenir que de tout compresser de force en un seul EXE. L'important est de distinguer dès le départ si l'on veut un seul artefact de distribution, si l'on veut supprimer la préinstallation du runtime, ou si l'on veut se passer d'un installateur.

Profil de l’auteur

Page de présentation de l’auteur de l’article.

Go Komura

Représentant de KomuraSoft LLC

Spécialisé dans le développement de logiciels Windows, le conseil technique et l’analyse de pannes, notamment pour les systèmes existants et les incidents difficiles à reproduire.

Retour au blog