10 points par GN⁺ 2025-05-12 | 3 commentaires | Partager sur WhatsApp
  • 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.c et utils/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 bin au PATH du système
    • Cloner le dépôt puis se déplacer dans le répertoire simple-todo-c
    • Exécuter .\build.bat pour compiler
  • 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’application
    • src/todo.c, src/todo.h : logique de gestion des tâches et structures de données
    • src/gui.c : implémentation de l’interface graphique
    • src/utils/search.c, src/utils/search.h : implémentation et déclaration de la fonction de recherche
    • src/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.md est 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

 
aer0700 2025-05-13

Il y a un certain romantisme là-dedans.

 
GN⁺ 2025-05-12
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’avertissements
    L’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 de memset(), CopyMemory() à la place de memcpy()
    À 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

    • J’utilise une excellente appli PC appelée AlomWare Toolbox, qui est presque un archétype de la conception Win32 (https://www.alomware.com/images/tab-automation.png)
      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
    • De nos jours, si l’on n’utilise pas au minimum strncpy au lieu de strcpy, 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 ça
      Personnellement, 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
    • Il a été dit qu’il existe ZeroMemory() à la place de memset() et CopyMemory() à la place de memcpy(), mais les intrinsics MSVC utiliseront probablement les instructions rep stos/movs
      Cela 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
    • J’ai beaucoup fait ce genre de choses, et honnêtement, la possibilité de développer une UI native en code natif me manque
    • Le code utilise aussi fopen/fread/fclose, mais apparemment pas les API Win32 comme CreateFile/ReadFile/WriteFile
  • Au 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 à utiliser CreateDialog() à la place de CreateWindow()
    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é

    • Avec cette méthode, on obtient aussi automatiquement le déplacement au clavier avec Tab entre les contrôles, ainsi que quelques raccourcis clavier
      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
    • Le chevauchement entre la bibliothèque standard C et la bibliothèque du système d’exploitation n’existe en pratique que sur UNIX, et en 1985, lorsque Windows 1.0 est sorti, il n’y avait encore rien que l’on puisse vraiment appeler une norme
      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
    • Cette approche peut facilement être portée vers un langage disposant d’une bonne interface de fonctions étrangères (FFI), et elle ne nécessite rien d’autre
      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 langage
  • Il 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

    • Un programme TODO en TUI basé sur ncurses, un peu plus étendu, fait 15 Ko. C’est sous Linux et ce n’est pas lié statiquement
      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

    • Un exemple de configuration de la prise en compte du DPI se trouve ici : https://github.com/Dwedit/GameStretcher/blob/master/Stretche...
      Ce code vérifie dynamiquement lequel de user32:SetProcessDpiAwarenessContext, shcore:SetProcessDpiAwareness, user32:SetProcessDPIAware est disponible et l’appelle
      Si la version de Windows est très ancienne et n’en implémente aucun (avant Windows XP), il n’appelle rien
    • « Moderne » signifie apparemment beaucoup plus gros que nécessaire tout en manquant pas mal de fonctionnalités
      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

    • J’ai essayé de reproduire ce binaire pour voir à quoi servent les 278 Ko
      Le premier obstacle rencontré était que, si git est configuré avec core.autocrlf=false, le fichier build.bat ne fonctionne pas. En passant à core.autocrlf=true puis en clonant à nouveau, j’ai pu compiler
      J’ai utilisé la toolchain x86_64-15.1.0-release-win32-seh-msvcrt-rt_v12-rev0.7z de https://github.com/niXman/mingw-builds-binaries/releases/tag...
      Dans ce cas, on obtient un fichier .exe de 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éciable
      En passant quelques options à GCC, on peut réduire encore davantage
      gcc -Os => 100 Ko
      gcc -Oz => 99 Ko
      gcc -flto => 101 Ko
      gcc -s => 51 Ko
      gcc -s -Oz -flto => 47 Ko
      Si seule la petite taille du .exe compte, il reste encore pas mal de marge d’amélioration
    • Une grande partie vient de la plateforme et du format d’exécutable
      Sans 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
    • On pourrait aussi pétitionner pour que les compétitions de démoscène créent une catégorie application TODO en 64 Ko
    • Honnêtement, je suis surpris que ce soit aussi gros. Je m’attendais à plus petit, ou à ce que l’icône de l’application en occupe à peu près la moitié
      Quand j’avais écrit ce genre de chose autrefois, il me semble que c’était plus petit. Est-ce que ce serait dû à MinGW ?
    • Ça me rappelle soudain l’époque où programmer Win32 en assembleur était devenu assez branché
      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

    • Il y a environ 30 ans, quand j’ai écrit mes premiers programmes Windows, j’ai appris exactement de cette façon. Même si j’ai probablement utilisé un compilateur C++
      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
    • Sans plaisanter, je préférerais utiliser cette application plutôt que l’application TODO par défaut de Windows 11
    • C’est exactement comme ça qu’il faut faire
      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
    • https://github.com/Efeckc17/YoutubeGO Pour référence, je serais vraiment ravi si quelqu’un pouvait aussi passer en revue ou examiner cette application
    • Ce genre de projet avec une UI native propre est ce qui m’a donné envie d’apprendre la programmation. Super
  • 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

    • Une application similaire en C# + WinForms fait moins de 10 Ko sur disque et utilise 6 Mo de RAM
      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

    • Fait intéressant, Windows dispose de sa propre API/ABI orientée objet avec COM, puis WinRT construit par-dessus COM
      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
    • Il existait bien une API procédurale, elle s’appelait Carbon
      C’était un substitut presque drop-in de l’API Macintosh Toolbox que user32.dll a copiée de façon flagrante
      Cela 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

    • Il me semble que c’est l’inverse
      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
    • Non, lier contre la version statique du CRT est un bon choix. Le code inutilisé peut être éliminé
      En liant dynamiquement contre MSVCRxx/VCRUNTIME, l’utilisateur doit télécharger exactement cette DLL auprès de Microsoft
      En liant dynamiquement contre MSVCRT, ce problème disparaît, mais c’est très difficile à faire dans Visual Studio
      Le seul cas où lier statiquement contre le CRT pose vraiment problème, ce serait probablement pour respecter la LGPL
 
roxie 2025-05-16

On dirait presque que son souffle chaud arrive jusqu’ici…