10 points par GN⁺ 2025-05-12 | 3 commentaires | Partager sur WhatsApp
  • Ce projet open source est une application Todo Windows native légère développée uniquement avec C et l’API Win32
  • Elle fonctionne avec une taille minimale (26,5 KB au maximum) sans dépendre d’un framework, tout en implémentant directement une GUI Windows avancée et l’intégration au système
  • En plus des fonctions de base comme ajouter, modifier, supprimer et marquer comme terminées les tâches, elle propose aussi des fonctions de productivité réelles comme l’intégration à la zone de notification et l’option de lancement automatique
  • Le stockage persistant se fait dans un fichier binaire, avec jusqu’à 100 tâches enregistrées dans le dossier AppData
  • Son point fort est une approche de programmation classique, très proche de l’OS, sans gros framework, avec un environnement d’exécution léger

🌟 Simple Todo (C / WinAPI)

Présentation du projet

  • Ce projet crée une application Todo Windows native moderne en utilisant uniquement C et l’API Win32
  • Il met en avant des capacités avancées de programmation GUI Windows et d’intégration au système
  • Le projet est très léger (26,5 KB au maximum) tout en conservant l’apparence propre à Windows

✨ Fonctionnalités principales

  • Création, modification et suppression de tâches
  • Possibilité de marquer une tâche comme terminée
  • Sauvegarde persistante dans AppData pour conserver les données en permanence
  • Intégration à la zone de notification avec réduction dans le tray lors de la minimisation
  • Apparence en style Windows natif
  • Option de lancement automatique au démarrage de Windows

🛠️ Détails techniques

  • Entièrement codé en pur C
  • Utilise uniquement l’API Win32 pour l’implémentation de la GUI
  • Taille d’exécutable minuscule (26,5 KB avec compression UPX)
  • Fonction d’intégration à la zone de notification
  • Application de styles visuels modernes via un manifeste

💾 Stockage des données

  • Toutes les tâches sont stockées dans un seul fichier binaire
  • Chemin de stockage : %APPDATA%\\TodoApp\\todos.dat
  • Format binaire avec prise en charge de 100 éléments maximum

📋 Prérequis

  • Un environnement Windows est nécessaire
  • MinGW-w64 (compilateur GCC) et le Windows SDK sont requis

🎮 Utilisation

  • Lancez bin/todo.exe, puis utilisez l’interface pour effectuer les actions suivantes
  • Ajouter une nouvelle tâche avec le bouton "Add"
  • Sélectionner un élément puis cliquer sur "Edit" pour le modifier
  • Supprimer un élément avec "Delete"
  • Marquer comme terminée avec "Complete"
  • Possibilité de définir une priorité pour chaque élément

🏗️ Structure du projet

  • Le dossier src/ contient le point d’entrée principal (main.c), la logique de gestion des tâches (todo.c), les déclarations de structures (todo.h) et l’implémentation de la GUI (gui.c)
  • L’exécutable compilé se trouve dans bin/
  • Le script de build (build.bat) et la documentation du projet sont inclus

🔧 Éléments de développement

  • API Win32 : implémentation de la gestion des fenêtres et de toute la GUI
  • Common Controls : utilisation d’éléments d’interface modernes
  • UXTheme : prise en charge de l’application des styles visuels Windows
  • File I/O : mise en place de la sauvegarde persistante des données

📝 Licence

  • Utilisation et modification libres sous licence MIT

🤝 Contribution

  • Pull Requests bienvenues
  • Tout le monde peut contribuer au projet

📫 Contact et liens

3 commentaires

 
aer0700 2025-05-13

Il y a un certain romantisme là-dedans.

 
GN⁺ 2025-05-12
Réactions sur Hacker News
  • Il y a un aspect de la programmation d’interface Win32 que j’aime bien, même si c’est un peu particulier ; en lisant le blog de Raymond Chen, on comprend pourquoi. L’API Win32 remonte à l’époque du processeur 8088, et certaines façons de faire permettent d’économiser 40 octets de code ou d’utiliser un registre de moins. J’ai autrefois écrit moi-même beaucoup de petites applis GUI en regardant MinGW et le livre de Petzold. C’était vraiment amusant de tout faire à la main : contrôles personnalisés, dessin graphique/texte, gestion du défilement, hit testing, etc. J’ai vu que ton appli utilise strcpy et sprintf ; si tu programmes sérieusement, il faut absolument utiliser des variantes avec vérification de longueur. Je suis surpris que le compilateur n’ait pas immédiatement affiché d’avertissement. L’API Win32 propose beaucoup de fonctions qui remplacent celles de la bibliothèque standard C. Si tu veux encore réduire la taille de l’exécutable, je te recommande d’essayer avec seulement <Windows.h>, sans cstdlib. Tu peux utiliser ZeroMemory à la place de memset et CopyMemory à la place de memcpy. Bien sûr, coder en C brut devient extrêmement pénible à partir d’un certain moment, mais les premières fois, faire les choses directement en pur C aide le plus à apprendre. On développe ce sens de la composition en manipulant tous ces petits détails. Si tu veux continuer à explorer la programmation GUI Win32, je recommanderais aussi WTL (Windows Template Library), qui encapsule l’API Win32 en C++ et facilite beaucoup la compréhension de son fonctionnement.
    • Aujourd’hui, il faut au minimum utiliser strncpy au lieu de strcpy, sinon tout le monde va continuer à le signaler. L’une des grandes raisons d’utiliser Zig, c’est justement de réduire ce type d’erreurs courantes. Cela dit, le C reste très bien.
    • À propos du remplacement de memset par ZeroMemory et de memcpy par CopyMemory, les intrinsics MSVC utilisent les instructions rep stos/movs, ce qui produit un code plus compact qu’un appel de fonction et réduit aussi la taille de la table d’import.
    • Moi aussi, je faisais souvent ça autrefois, et honnêtement, la capacité à développer une UI native avec du code natif me manque.
    • À propos de ZeroMemory et CopyMemory à la place de memset et memcpy, je me demande pourquoi ils ont créé ça au lieu d’utiliser simplement la bibliothèque standard C existante.
  • À l’époque, au lieu d’appeler laborieusement CreateWindow à chaque fois, on écrivait souvent des ressources de dialogue dans un fichier .rc (Visual Studio fournit aussi un éditeur de dialogues) puis on utilisait CreateDialog. Tous les contrôles étaient alors créés d’un coup. Il suffit ensuite d’ajouter un manifest d’application pour prendre en charge un style d’UI moderne ainsi que le DPI élevé.
    • Avec cette méthode, on obtient aussi automatiquement la prise en charge des raccourcis clavier comme la navigation au Tab entre les contrôles. En revanche, pour le redimensionnement, il faut toujours le gérer soi-même, mais le code s’étend facilement et ce n’est pas difficile.
    • C’est une méthode qui apparaît aussi dans le livre de Petzold, donc ça vaut le coup d’y jeter un œil.
  • J’ai déjà fait quelque chose de similaire pour Linux en assembleur, en moins de 2 KiB. En C avec liaison dynamique, on peut facilement rester sous les 20 KiB sur Linux. Je pense que Windows devrait être encore plus simple grâce à toutes les fonctionnalités intégrées. Donc j’ai envie d’encourager ce genre d’essai. En regardant les options de linking à la fin de l’article, il devrait être facile de réduire encore la taille.
  • Sans framework, on se retrouve avec des polices floues lors du scaling DPI, pas de prise en charge de Tab, pas de sélection Ctrl-A dans les champs de texte et l’absence de la plupart des fonctions de base fournies par les frameworks pré-modernes, plus des erreurs lors de l’ajout de lignes. Du coup, je me demande en quel sens c’est « moderne ».
    • Voici un exemple de configuration DPI awareness : le code essaie de régler la prise en charge DPI via différentes fonctions Windows selon la version (user32:SetProcessDpiAwarenessContext, shcore:SetProcessDpiAwareness, user32:SetProcessDPIAware) ; sur des versions vraiment anciennes, il n’appelle rien du tout.
    • Le mot « moderne » ne convient pas : c’est trop volumineux pour trop peu de fonctionnalités (même si la navigation au Tab entre contrôles est facile à implémenter soi-même).
  • Ayant programmé sur 6502, je souffre devant la réalité où 278 KB sont maintenant considérés comme légers.
    • En analysant la taille du binaire, le premier obstacle que j’ai rencontré, c’est que build.bat ne fonctionne pas correctement quand core.autocrlf=false. En repassant à core.autocrlf=true puis en reclonant, la compilation a marché. Une certaine toolchain MinGW produit un .exe de 102 KB, donc bien plus efficace que 278 KB. Et si on veut réduire davantage, on peut ajouter des flags à GCC : gcc -s -Oz -flto permet même de descendre à 47 KB. Si seule la taille du binaire t’intéresse, il y a encore beaucoup de marge d’amélioration.
    • Si on arrive à cette taille, c’est à cause de la plateforme et du format d’exécutable : informations de stack trace, infrastructure de liaison dynamique, tables de gestion d’exceptions, etc., tout cela prend de la place.
    • J’aimerais demander l’ouverture d’une catégorie « appli TODO en 64KB » dans les concours de demoscene.
    • Franchement, je suis surpris que ce soit aussi gros ; dans mon souvenir, c’était plus petit autrefois, même en retirant la taille de l’icône. Je me demande si MinGW y est pour quelque chose.
    • Le 6502 ? C’était le grand luxe. À mon époque, il n’y avait souvent même pas de CPU.
    • Ça me rappelle la période où la programmation assembleur Win32 est soudain devenue à la mode, surtout quand les téléchargements shareware commençaient à grossir. Ça me fait aussi penser au début du développement 68k sur Palm Pilot. C’était comme le dernier éclat de l’assembleur non rétro.
    • Quelqu’un a même créé un quickrun.exe de 15 KB, uniquement en C et avec l’API Win32 pure. Il n’y a pas d’astuce secrète pour réduire le binaire, juste le compilateur Mingw32. C’est une appli GUI qui lance rapidement des applis via des alias.
    • Ce soir, je suis en train de déboguer mon émulateur qui simule un système Z80 avec 64 KB de RAM. Par moments, ça me fait vraiment sentir à quel point les époques et les environnements ont changé. Mais en même temps, je me dis qu’on a accompli d’énormes progrès en échange de cette augmentation de taille.
    • Entre les architectures 8 bits et 64 bits, rien qu’un pointeur d’adresse est devenu 8 fois plus gros ; au lieu de te plaindre, admire l’élégance de cette évolution.
    • 278 KB, c’est à peine de quoi tenir sur une disquette 5"1/4.
  • Cette appli a juste été faite pour le plaisir. Comme certains commentaires le soulignent, il aurait peut-être été plus raisonnable d’utiliser C++ ou un autre langage, mais de mon côté, c’est simplement le fait d’essayer qui m’a amusé.
    • Il y a une trentaine d’années, j’ai moi aussi fait presque exactement mon premier programme Windows comme ça. La différence, c’est que j’utilisais un compilateur C++. À l’époque, écrire du code de style C avec un compilateur C++ était la méthode recommandée par la documentation officielle. Comme C++ était compatible ascendant avec C, Microsoft avait aussi cette tendance.
    • Honnêtement, j’ai bien plus envie d’utiliser ton appli que l’appli To-do par défaut de Windows 11.
    • Quand on utilise l’API Win32, le langage ne change pas fondamentalement la nature du travail. En fait, changer de langage peut même rendre les choses plus confuses. Si on s’obstine sur le style C++, ça peut embrouiller encore davantage quelqu’un qui découvre l’API Win32. Se familiariser avec Win32 API via ce genre de petit projet simple et mignon, c’est à mon avis une base importante dans la formation d’un développeur.
    • J’ai aussi une autre appli, YoutubeGO, donc si tu veux y jeter un œil, ça me ferait plaisir.
    • Je comprends et j’apprécie ce genre de projet d’UI native propre : c’est exactement le type de chose qui m’a donné envie d’apprendre à programmer.
  • À une époque où, sur le web ou dans le logiciel, on charge des mégaoctets de JS ou de C# pour envoyer 278 KB de télémétrie, ce genre d’essai est rafraîchissant.
    • Une appli similaire faite en C# + WinForms fait moins de 10 KB sur disque et n’utilise que 6 MB de RAM ; cette appli en utilise 1,5 MB. Les deux s’ouvrent instantanément.
  • Là, on dirait qu’une bibliothèque statique a été liée ; si tu relies via des DLL, tu peux réduire drastiquement la taille de l’appli.
    • C’est plutôt l’inverse. Si tu dois obligatoirement distribuer les DLL avec le programme (si elles ne sont pas incluses dans l’OS), chaque DLL embarque son propre runtime C, donc au final ça gonfle davantage. Si tu mets tout statiquement dans un seul EXE, tu n’inclus qu’un seul runtime C et tu peux facilement éliminer les fonctions inutilisées. Les DLL ne réduisent la taille que quand plusieurs programmes partagent la même DLL.
    • Lier statiquement la CRT (bibliothèque d’exécution) est en fait avantageux pour supprimer du code inutile. Avec des DLL liées dynamiquement, il faut parfois en plus faire installer séparément la DLL VCRUNTIME de Microsoft. Dans Visual Studio, la liaison dynamique à MSVCRT n’est pas forcément simple non plus. Il y a toutefois une exception lorsqu’il faut respecter la LGPL.
  • Ça me fait penser à File Pilot, un explorateur de fichiers rapide lancé récemment, écrit en C et qui ne fait que 1,8 MB.
  • On parle ici d’une appli Todo Windows « moderne » et « native », mais je me demande vraiment ce qu’elle a de moderne. Et en C++, on pourrait éviter plusieurs problèmes et supprimer les variables globales. Si on remplace malloc et qu’on utilise std::string, std::array, std::list et des espaces de noms anonymes, on obtiendra probablement un code deux fois plus court avec moins de bugs.
    • Les variables globales n’ont pratiquement aucun impact dans une appli d’environ 500 lignes, et leur usage est clair. Passer à std::string ou std::list ne signifie pas que l’assembleur généré sera identique ; ce commentaire montre surtout une mauvaise compréhension du fonctionnement interne.
    • Même le livre de Petzold, dans ses éditions récentes, compile en mode C++ avec Visual C++ et recommande une syntaxe commune au C et au C++. À l’époque de Windows 95, presque plus personne n’écrivait exclusivement en C ; les langages dominants avaient déjà basculé vers VB, Delphi et C++.
    • À propos de l’idée d’utiliser string ou array/list standard : si on utilise directement WinAPI, il est plus logique et recommandé d’utiliser LPWSTR (chaîne wide) plutôt que std::string, car c’est mieux adapté à l’API. LPWSTR est recommandé plutôt qu’une méthode ancienne comme char[]. Je ne pense pas que std::array ou list rendraient vraiment le code meilleur.
    • Ce n’est pas moderne, mais ce type de programmation proche des bases aide à bien comprendre le fonctionnement fondamental. Le problème est simple, donc facile à saisir. Je pense que c’est un bon projet d’apprentissage. Je serais aussi curieux de voir une version assembleur de ce genre d’appli.
 
roxie 2025-05-16

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