Créer une application native Todo pour Windows en pur C (278 Ko, sans framework)
(github.com/Efeckc17)- Simple Todo est une application native Todo pour Windows développée en C et avec l’API Win32, conçue pour illustrer la programmation d’interfaces graphiques Windows et les fonctions d’intégration système
- Les principales fonctionnalités incluent la création, la modification et la suppression de tâches, le marquage comme terminées, la réduction dans la zone de notification, l’option de lancement automatique au démarrage de Windows et la recherche en temps réel sans distinction de casse
- Les données sont enregistrées sous forme de fichier binaire dans
%APPDATA%\TodoApp\todos.dat, contenant la liste complète des tâches, avec une limite de 100 éléments - La compilation sous Windows nécessite MinGW-w64 et le SDK Windows ; après avoir cloné le dépôt, il suffit de compiler avec
.\build.bat - La structure se répartit entre
main.c,todo.c,gui.cetutils/search.c, et l’interface utilise l’API Win32, Common Controls et UXTheme
Nature du projet
- Simple Todo est une application Todo native pour Windows écrite en C avec l’API Win32
- Elle vise un exécutable natif léger et utilise l’apparence native de Windows ainsi que les styles visuels modernes de Windows
- L’interface graphique repose sur l’API Win32 et inclut l’intégration à la zone de notification ainsi que la persistance des données via les entrées/sorties fichier
Fonctionnalités Todo
- Il est possible de créer, modifier et supprimer des tâches
- Les tâches peuvent être marquées comme terminées
- Une priorité peut être définie pour chaque tâche
- La recherche fonctionne en temps réel et prend en charge une correspondance insensible à la casse
- L’application peut être réduite dans la zone de notification
- Une option permet son lancement automatique au démarrage de Windows
Stockage des données
- Les données Todo sont enregistrées dans
%APPDATA%\TodoApp\todos.dat - Le format de stockage est un fichier binaire contenant l’ensemble de la liste des tâches
- La capacité est limitée à 100 tâches maximum
Compilation et exécution
- Prérequis
- Système d’exploitation Windows
- Compilateur GCC MinGW-w64
- SDK Windows inclus avec MinGW
- Procédure de compilation
- Installer MinGW-w64 et ajouter le répertoire
binauPATHdu système - Cloner le dépôt puis se déplacer dans le répertoire
simple-todo-c - Exécuter
.\build.batpour compiler
- Installer MinGW-w64 et ajouter le répertoire
- Le lancement se fait via
bin/todo.exe
Organisation de l’implémentation
- La structure du projet est répartie en fichiers C par fonctionnalité
src/main.c: point d’entrée de l’applicationsrc/todo.c,src/todo.h: logique de gestion des tâches et structures de donnéessrc/gui.c: implémentation de l’interface graphiquesrc/utils/search.c,src/utils/search.h: implémentation et déclaration de la fonction de recherchesrc/app.manifest: manifeste pour les styles visuels Windows
- Les composants clés sont l’API Win32, Common Controls, UXTheme et les entrées/sorties fichier
- Un guide
DEBUGGING.mdest fourni pour le débogage dans Visual Studio, VS Code, etc.
Licence et contribution
- La licence est la MIT License
- Les contributions sont acceptées via Pull Request
3 commentaires
Il y a un certain romantisme là-dedans.
Avis sur Hacker News
La programmation d’interfaces graphiques Win32 a quelque chose d’étrangement plaisant
C’est un peu particulier, mais si l’on lit le blog de Raymond Chen, on comprend pourquoi
L’API Win32 a ses racines à l’époque du processeur 8088, avec tout un contexte où faire les choses d’une certaine manière permettait d’économiser 40 octets de code ou d’utiliser un registre de moins
À une époque, j’ai beaucoup bricolé des applis GUI jouets avec mingw et le livre de Petzold, et écrire des contrôles personnalisés, dessiner des graphismes et du texte, gérer le défilement, le hit testing, tout cela était assez amusant
On voit que l’appli utilise
strcpy,sprintf; pour une programmation sérieuse, il faudrait utiliser des variantes qui vérifient les longueurs. Je suis surpris que le compilateur n’ait pas émis une avalanche d’avertissementsL’API Win32 comporte beaucoup de fonctions qui remplacent celles de la bibliothèque standard C. Si l’on veut vraiment réduire la taille de l’exécutable, on peut essayer d’écrire l’appli sans la bibliothèque standard C. Il y a
ZeroMemory()à la place dememset(),CopyMemory()à la place dememcpy()À un moment, écrire directement du C pur devient pénible, mais je pense que le faire en C pur les premières fois est ce qu’il y a de mieux pour apprendre. En gérant soi-même les détails, on comprend bien ce qui se passe sous le capot pendant l’apprentissage
Si vous voulez faire davantage de programmation GUI Win32, je recommande de regarder WTL (Windows Template Library). C’est un wrapper C++ autour de l’API Win32, ce qui rend son fonctionnement beaucoup plus facile à comprendre
Malgré ses très nombreuses fonctionnalités, elle ne fait qu’environ 3 Mo grâce à cela. Pas de framework, un seul exécutable ; j’aimerais que tous les logiciels fonctionnent encore ainsi
strncpyau lieu destrcpy, il faut s’attendre à ce que les gens, et même les outils d’IA, vous répètent qu’il ne faut pas faire çaPersonnellement, l’une des principales raisons pour lesquelles j’utilise Zig est qu’il réduit beaucoup de ces pièges courants. Bien sûr, C reste très bien
ZeroMemory()à la place dememset()etCopyMemory()à la place dememcpy(), mais les intrinsics MSVC utiliseront probablement les instructionsrep stos/movsCela peut être plus petit qu’un appel de fonction, car un appel de fonction inclut aussi la taille de l’entrée dans la table d’imports
fopen/fread/fclose, mais apparemment pas les API Win32 commeCreateFile/ReadFile/WriteFileAu lieu d’appeler péniblement
CreateWindow()pour chaque contrôle, l’approche traditionnelle consistait à placer des ressources de boîte de dialogue dans un fichier.rc(Visual Studio dispose encore d’un éditeur de boîtes de dialogue visuel pour cela) et à utiliserCreateDialog()à la place deCreateWindow()Cela crée automatiquement tous les contrôles. En ajoutant un manifeste d’application, on obtient aussi un style d’UI moderne et la prise en charge du DPI élevé
En revanche, si l’on veut gérer le redimensionnement, il faut toujours le faire manuellement, mais c’est généralement simple et cela ne dépasse pas quelques centaines d’octets de code
K&R C existait, mais hors d’UNIX, chaque système d’exploitation choisissait à sa façon les fonctionnalités à fournir
De plus, sur les systèmes non apparentés à UNIX, la tradition voulait que la bibliothèque standard soit de la responsabilité du compilateur C, et non de la plateforme
Les bibliothèques C étaient donc fournies par Borland, Watcom, Symantec, Microsoft, Green Hills, Zortech, etc.
Mac OS était dans le même cas jusqu’à l’arrivée de MPW, tout comme IBM et Unisys, les micro-ordinateurs et les mainframes, ainsi que VMS avant OpenVMS
Depuis Windows 10, il existe aussi le Universal C Runtime
Pas besoin de compilateur de ressources ni de linker pour créer une DLL de ressources
Les fichiers de ressources et leur format binaire ne constituent pas une bonne API
Si, dans un langage suffisamment haut niveau, on dispose d’appels à
CreateWindow, on pourrait sans doute créer par métaprogrammation un DSL de type ressources, bien intégré au langageIl y a quelque temps, j’ai fait quelque chose de similaire en assembleur sous Linux, en moins de 2 Kio : https://gaultier.github.io/blog/x11_x64.html
Comme d’autres l’ont dit, si l’on fait cela en C pur avec de l’édition de liens dynamique, on peut facilement rester sous les 20 Kio, au moins sous Linux. Windows inclut beaucoup plus de choses de base dans le système d’exploitation, donc cela pourrait même être plus facile
Quoi qu’il en soit, j’encourage la tentative. Essayer les options de linking mentionnées à la fin de mon article devrait aider à réduire la taille
Je n’ai pas encore réussi à compiler ncurses avec musl
Avec l’expression « look and feel Windows natif », avant de l’exécuter réellement, je m’attendais à ce que la vue en liste ait un menu contextuel et qu’un double-clic sur certains champs fasse apparaître un menu déroulant ou une zone de texte presque inline
Je ne sais plus si les gens ne savent plus concevoir des programmes conformément à l’UI/UX Win32, ou si c’est moi qui suis trop vieux
Il était censé n’y avoir « pas de framework », mais la police est floue avec la mise à l’échelle DPI, il n’y a pas non plus de prise en charge de Tab, Ctrl-A ne sélectionne pas tout dans les champs de texte, il manque plusieurs fonctionnalités que même des frameworks pré-modernes fournissaient, et l’ajout de lignes produit aussi des erreurs
Je ne sais pas ce que « moderne » est censé vouloir dire
Ce code vérifie dynamiquement lequel de
user32:SetProcessDpiAwarenessContext,shcore:SetProcessDpiAwareness,user32:SetProcessDPIAwareest disponible et l’appelleSi la version de Windows est très ancienne et n’en implémente aucun (avant Windows XP), il n’appelle rien
La plupart des fonctionnalités signalées comme manquantes sont très faciles à ajouter, surtout le déplacement au Tab entre les contrôles
Le programmeur 6502 en moi meurt intérieurement en voyant que 278 Ko passe désormais pour léger
Le premier obstacle rencontré était que, si git est configuré avec
core.autocrlf=false, le fichierbuild.batne fonctionne pas. En passant àcore.autocrlf=truepuis en clonant à nouveau, j’ai pu compilerJ’ai utilisé la toolchain
x86_64-15.1.0-release-win32-seh-msvcrt-rt_v12-rev0.7zde https://github.com/niXman/mingw-builds-binaries/releases/tag...Dans ce cas, on obtient un fichier
.exede 102 Ko. C’est déjà bien mieux que les 278 Ko annoncés. L’auteur utilise peut-être une autre toolchain ou d’autres réglages ; une procédure de reproduction exacte serait appréciableEn passant quelques options à GCC, on peut réduire encore davantage
gcc -Os=> 100 Kogcc -Oz=> 99 Kogcc -flto=> 101 Kogcc -s=> 51 Kogcc -s -Oz -flto=> 47 KoSi seule la petite taille du
.execompte, il reste encore pas mal de marge d’améliorationSans les informations de trace de pile, l’infrastructure de liaison dynamique, les tables de gestion des exceptions, etc., ce pourrait être beaucoup plus léger. Les tables de gestion des exceptions sont nécessaires même en C, car des exceptions peuvent traverser des fonctions C
Quand j’avais écrit ce genre de chose autrefois, il me semble que c’était plus petit. Est-ce que ce serait dû à MinGW ?
C’était probablement une réaction à l’augmentation de la taille des téléchargements de sharewares. C’était l’âge sombre de MFC
Avec la programmation 68k des premiers Palm Pilot, je m’en souviens comme du dernier baroud d’honneur de l’assembleur hors rétrocomputing
Les amis, c’est juste une appli faite pour essayer et s’amuser. Les commentaires ont raison, ce genre de chose aurait sans doute pu être fait plus raisonnablement en C++ ou dans un autre langage
Je ne sais pas trop pourquoi, mais il me semble que la documentation de l’API Windows guidait vers une utilisation de code de style C avec un compilateur C++. Microsoft semblait considérer C++ comme un sur-ensemble amélioré de C et penser qu’il était aussi approprié pour du code de style C
Utiliser un autre langage ne change rien de fondamental quand on utilise l’API Win32, et peut même rendre les choses plus déroutantes
Les gens tombent souvent dans une manière de penser à la C++, et pour quelqu’un qui ne connaît pas l’API Win32, cela aurait rendu l’ensemble encore plus confus
C’est une tentative très sympathique et, quoi qu’en pensent les autres, une familiarité avec l’API Win32 est une compétence de base tout à fait valable
Je vois ici pas mal de sarcasmes venant de gens qui créent des logiciels ou des sites web chargeant plusieurs Mo de JS ou de C# pour envoyer 278 Ko de télémétrie à chaque mouvement de souris de l’utilisateur
Celle-ci utilise 1,5 Mo de RAM, et les deux démarrent instantanément
Quand on développe une application Windows, le fait de pouvoir tout faire en C fait toujours plaisir
Construire à partir des briques de base du plus bas niveau paraît tout simplement naturel
À l’inverse, sur macOS, tout est un objet Objective-C ; on peut bien écrire une application en pur C, mais on se retrouve alors un niveau en dessous de l’abstraction prévue par Apple, ce qui rend le code quasiment 1000 fois plus verbeux. Pour faire quoi que ce soit, il faut en pratique manipuler comme une marionnette toute la hiérarchie de classes Objective-C, et c’est vraiment désagréable
Je ne vois pas pourquoi on ne pourrait pas remettre l’API à base de classes d’Objective-C par-dessus une API procédurale de base à la Win32. Techniquement, Win32 est aussi « basée sur des classes », mais au moins c’est minimal
Du coup, je ne suis pas vraiment tenté de porter mon code C vers macOS pour le moment
Mais le compilateur MIDL pour les interfaces COM génère des en-têtes qui permettent d’utiliser et d’implémenter les interfaces en C : https://www.codeproject.com/KB/com/com_in_c1.aspx
À ma connaissance, cela fonctionne encore aujourd’hui avec WinRT. En revanche, les structures C et les noms de fonctions générés doivent inclure l’espace de noms complet, ce qui devient très laid : https://stackoverflow.com/a/7437006/111335
C’était un substitut presque drop-in de l’API Macintosh Toolbox que
user32.dlla copiée de façon flagranteCela dit, à part la chaleur qu’elle procure aux programmeurs, l’UI veut vraiment de l’orienté objet. Un arbre d’objets différents partageant certains comportements communs décrit presque exactement 99 % du code d’UI
Toolbox et Carbon manquaient de cet aspect orienté objet, et cela les poussait assez loin dans leurs limites. La vraie idée originale que Windows a ajoutée, ce sont justement les classes de fenêtres
Personnellement, l’étrangeté que l’on ressent en manipulant directement des classes Objective-C depuis du C, je la ressens chaque fois que je dois définir des classes de fenêtres ou des procédures de fenêtre dans du code
user32.dll[0]Tout comme l’UI veut de l’orienté objet, l’orienté objet veut des fonctionnalités dédiées dans le langage. On peut s’en passer, mais l’ergonomie devient 2000 fois pire
[0] Ou tout ce qui touche à GTK/GObject
On dirait qu’il lie contre une bibliothèque statique
Il faudrait lier contre une DLL, pas contre une bibliothèque statique, ce qui réduirait fortement la taille de l’application
Si de toute façon il faut distribuer la DLL avec le programme (puisqu’elle ne fait pas partie du système d’exploitation), la DLL contient toutes les fonctionnalités, et chaque DLL peut même embarquer son propre runtime C
En compilant tout statiquement dans un seul EXE, il n’y a qu’un seul runtime C, et les fonctions inutilisées peuvent facilement être supprimées
Les DLL ne réduisent la taille que lorsque ce code est partagé entre plusieurs programmes
En liant dynamiquement contre
MSVCRxx/VCRUNTIME, l’utilisateur doit télécharger exactement cette DLL auprès de MicrosoftEn liant dynamiquement contre
MSVCRT, ce problème disparaît, mais c’est très difficile à faire dans Visual StudioLe seul cas où lier statiquement contre le CRT pose vraiment problème, ce serait probablement pour respecter la LGPL
On dirait presque que son souffle chaud arrive jusqu’ici…