10 Punkte von GN⁺ 2025-05-12 | 3 Kommentare | Auf WhatsApp teilen
  • Simple Todo ist eine native Windows-Todo-App, entwickelt mit C und der Win32 API, und ein Projekt, das Windows-GUI-Programmierung sowie Funktionen zur Systemintegration zeigt
  • Zu den wichtigsten Funktionen gehören das Erstellen, Bearbeiten und Löschen von Todos, das Markieren als erledigt, Minimieren in den System Tray, eine Option für den Autostart unter Windows sowie eine Live-Suche ohne Beachtung der Groß-/Kleinschreibung
  • Die Daten werden als Binärdatei unter %APPDATA%\TodoApp\todos.dat gespeichert; sie enthält die gesamte Todo-Liste und unterstützt maximal 100 Todos
  • Für den Build werden unter Windows MinGW-w64 und das Windows SDK benötigt; nach dem Klonen des Repositorys wird mit .\build.bat kompiliert
  • Die Struktur ist in main.c, todo.c, gui.c und utils/search.c aufgeteilt; die GUI verwendet die Win32 API, Common Controls und UXTheme

Projektcharakter

  • Simple Todo ist eine native Windows-Todo-Anwendung, geschrieben in C und mit der Win32 API
  • Ziel ist eine leichtgewichtige native ausführbare Datei, die den standardmäßigen Windows-Look-and-Feel und moderne visuelle Windows-Stile nutzt
  • Die GUI basiert auf der Win32 API und umfasst System-Tray-Integration sowie Datenpersistenz über Datei-I/O

Todo-Funktionen

  • Todo-Einträge können erstellt, bearbeitet und gelöscht werden
  • Aufgaben können als erledigt markiert werden
  • Für jedes Todo kann eine Priorität festgelegt werden
  • Die Suchfunktion arbeitet in Echtzeit und unterstützt Matching ohne Beachtung der Groß-/Kleinschreibung
  • Die App kann in den System Tray minimiert werden
  • Es gibt eine Option, die App beim Windows-Start automatisch auszuführen

Datenspeicherung

  • Todo-Daten werden unter %APPDATA%\TodoApp\todos.dat gespeichert
  • Das Speicherformat ist eine Binärdatei, die die gesamte Todo-Liste enthält
  • Die Speicherkapazität ist auf maximal 100 Todos begrenzt

Build und Ausführung

  • Anforderungen
    • Windows OS
    • MinGW-w64-GCC-Compiler
    • Das in MinGW enthaltene Windows SDK
  • Build-Schritte
    • MinGW-w64 installieren und das Verzeichnis bin zum System-PATH hinzufügen
    • Das Repository klonen und in das Verzeichnis simple-todo-c wechseln
    • .\build.bat ausführen, um den Build zu erstellen
  • Gestartet wird die Anwendung mit bin/todo.exe

Implementierungsstruktur

  • Die Projektstruktur ist nach Funktionen in C-Dateien aufgeteilt
    • src/main.c: Einstiegspunkt der Anwendung
    • src/todo.c, src/todo.h: Todo-Verwaltungslogik und Datenstrukturen
    • src/gui.c: GUI-Implementierung
    • src/utils/search.c, src/utils/search.h: Implementierung und Deklarationen der Suchfunktion
    • src/app.manifest: Manifest für visuelle Windows-Stile
  • Zentrale Komponenten sind Win32 API, Common Controls, UXTheme und File I/O
  • Für das Debugging wird ein DEBUGGING.md-Guide bereitgestellt, der mit Visual Studio, VS Code usw. genutzt werden kann

Lizenz und Beiträge

  • Die Lizenz ist die MIT License
  • Beiträge werden per Pull Request angenommen

3 Kommentare

 
aer0700 2025-05-13

Das hat einfach Charme.

 
GN⁺ 2025-05-12
Hacker-News-Meinungen
  • Win32-GUI-Programmierung hat etwas seltsam Ansprechendes
    Sie ist zwar etwas eigenwillig, aber wenn man Raymond Chens Blog liest, versteht man warum
    Die Win32-API hat ihre Wurzeln in der Zeit der 8088-Prozessoren, und manches ist so entstanden, weil man auf bestimmte Weise 40 Byte Code sparen oder ein Register weniger verwenden konnte
    Früher habe ich mit mingw und dem Petzold-Buch viele Spielzeug-GUI-Apps gebaut, und Dinge wie eigene Controls schreiben, Grafik und Text zeichnen, Scrolling behandeln und Hit-Testing haben allesamt ziemlich Spaß gemacht
    In der App sieht man die Verwendung von strcpy und sprintf; bei ernsthafter Programmierung sollte man Varianten mit Längenprüfung verwenden. Es überrascht mich, dass der Compiler nicht eine Flut von Warnungen ausgegeben hat
    In der Win32-API gibt es viele Funktionen, die Dinge aus der C-Standardbibliothek ersetzen. Wenn man die Größe der ausführbaren Datei wirklich reduzieren will, kann man versuchen, die App ohne C-Standardbibliothek zu schreiben. Statt memset() gibt es ZeroMemory(), statt memcpy() CopyMemory()
    Irgendwann wird es schmerzhaft, reinen C-Code direkt zu schreiben, aber für die ersten paar Versuche halte ich reines C für den besten Lernweg. Wenn man die Details selbst verwaltet, bekommt man beim Lernen ein gutes Gefühl dafür, was intern passiert
    Wenn man mehr Win32-GUI-Programmierung ausprobieren will, empfehle ich einen Blick auf WTL (Windows Template Library). Das ist ein C++-Wrapper um die Win32-API, wodurch das Verhalten viel leichter verständlich wird

    • Ich nutze eine hervorragende PC-App namens AlomWare Toolbox, die dem klassischen Win32-Design ziemlich nahekommt (https://www.alomware.com/images/tab-automation.png)
      Obwohl sie sehr viele Funktionen hat, ist sie dadurch nur etwa 3 MB groß. Kein Framework, nur eine einzelne ausführbare Datei; ich wünschte, alle Software wäre immer noch so
    • Wenn man heutzutage nicht zumindest strncpy statt strcpy verwendet, muss man damit rechnen, dass einem nicht nur Menschen, sondern sogar AI-Tools ständig sagen, dass man das nicht tun sollte
      Einer der Hauptgründe, warum ich persönlich Zig verwende, ist, dass es viele dieser üblichen Fallstricke reduziert. C ist natürlich auch okay
    • Es wurde gesagt, dass es statt memset() ZeroMemory() und statt memcpy() CopyMemory() gibt, aber MSVC-Intrinsics würden vermutlich die Befehle rep stos/movs verwenden
      Das kann kleiner sein als ein Funktionsaufruf, weil zu einem Funktionsaufruf auch die Größe des Importtabellen-Eintrags gehört
    • Ich habe viel von dieser Art Arbeit gemacht, und ehrlich gesagt vermisse ich die Möglichkeit, native UIs mit nativem Code zu entwickeln
    • Es werden auch fopen/fread/fclose verwendet, aber offenbar nicht die Win32-APIs wie CreateFile/ReadFile/WriteFile
  • Statt mühsam für jedes Control CreateWindow() aufzurufen, hat man traditionell Dialogressourcen in einer .rc-Datei angelegt (Visual Studio hat dafür immer noch einen visuellen Dialogeditor) und statt CreateWindow() CreateDialog() verwendet
    Dadurch werden alle Controls automatisch erzeugt. Wenn man ein Anwendungsmanifest hinzufügt, bekommt man außerdem moderne UI-Stile und High-DPI-Unterstützung

    • Mit diesem Ansatz erhält man auch Tab-Navigation zwischen Controls und einige Tastenkürzel automatisch
      Wenn man Größenänderung möchte, muss man sie zwar weiterhin manuell behandeln, aber das ist normalerweise einfach und braucht kaum mehr als ein paar hundert Byte Code
    • Dass sich C-Standardbibliothek und Betriebssystembibliothek überschneiden, trifft im Grunde nur auf UNIX zu, und 1985, als Windows 1.0 erschien, gab es noch nichts, das man wirklich als Standard bezeichnen konnte
      K&R C gab es, aber außerhalb von UNIX wählte jedes Betriebssystem selbst aus, welche Funktionen es bereitstellen wollte
      Außerdem gab es auf Nicht-UNIX-Systemen die Tradition, dass nicht die Plattform, sondern der C-Compiler für die Standardbibliothek verantwortlich war
      Daher wurden C-Bibliotheken von Borland, Watcom, Symantec, Microsoft, Green Hills, Zortech und anderen bereitgestellt
      Bei Mac OS war es bis zum Erscheinen von MPW genauso, ebenso bei IBM und Unisys, bei Mikrocomputern und Mainframes sowie bei VMS vor OpenVMS
      Seit Windows 10 gibt es außerdem die Universal C Runtime
    • Dieser Ansatz lässt sich leicht auf Sprachen mit einem ordentlichen Foreign Function Interface (FFI) übertragen, und sonst braucht man nichts
      Kein Ressourcen-Compiler, kein Linker zum Erstellen einer Ressourcen-DLL
      Ressourcendateien und ihr Binärformat sind keine gute API
      Wenn man in einer hinreichend hohen Sprache Aufrufe von CreateWindow hat, könnte man per Metaprogrammierung wohl eine ressourcenähnliche DSL bauen, die sich gut in die Sprache einfügt
  • Ich habe früher unter Linux etwas Ähnliches in Assembly mit unter 2 KiB gebaut: https://gaultier.github.io/blog/x11_x64.html
    Wie andere gesagt haben: Wenn man es in reinem C baut und dynamisch linkt, kann man es zumindest unter Linux leicht unter 20 KiB halten. Unter Windows könnte es sogar einfacher sein, weil im Betriebssystem von Haus aus viel mehr enthalten ist
    Jedenfalls unterstütze ich den Versuch an sich. Die Linker-Optionen, die ich am Ende meines Artikels notiert habe, könnten helfen, die Größe zu reduzieren

    • Ein etwas erweitertes ncurses-basiertes TUI-TODO-Programm ist 15K groß. Es läuft unter Linux und ist nicht statisch gelinkt
      Ich habe es noch nicht geschafft, ncurses mit musl zu bauen
  • Bei „nativem Windows-Look-and-Feel“ hatte ich vor dem tatsächlichen Ausführen erwartet, dass die Listenansicht ein Kontextmenü hat und dass beim Doppelklick auf bestimmte Felder ein nahezu inline eingeblendetes Dropdown-Menü oder Textfeld erscheint
    Inzwischen weiß ich nicht, ob man einfach nicht mehr weiß, wie man Programme passend zur Win32-UI/UX entwirft, oder ob ich nur zu alt bin

  • „Kein Framework“, hieß es, aber bei DPI-Skalierung sind die Schriften unscharf, es gibt keine Tab-Unterstützung, in Textfeldern funktioniert Ctrl-A zum Alles-Auswählen nicht, viele Funktionen fehlen, die schon vormoderne Frameworks geboten haben, und beim Hinzufügen von Zeilen treten Fehler auf
    Ich weiß nicht, was „modern“ hier bedeuten soll

    • Ein Beispiel für DPI-Awareness-Einstellungen gibt es hier: https://github.com/Dwedit/GameStretcher/blob/master/Stretche...
      Dieser Code prüft dynamisch, welche von user32:SetProcessDpiAwarenessContext, shcore:SetProcessDpiAwareness und user32:SetProcessDPIAware verfügbar ist, und ruft sie auf
      Wenn die Windows-Version so alt ist, dass keine davon implementiert ist (vor Windows XP), wird nichts aufgerufen
    • „Modern“ scheint wohl zu bedeuten, dass es viel größer als nötig ist und trotzdem viele Funktionen fehlen
      Die meisten der als fehlend genannten Funktionen wären sehr leicht hinzuzufügen, besonders Tab-Navigation zwischen Controls
  • Der 6502-Programmierer in mir stirbt innerlich, wenn er sieht, dass 278 KB inzwischen als leichtgewichtig durchgehen

    • Ich wollte dieses Binary reproduzieren, um zu sehen, wofür die 278 KB verwendet werden
      Das erste Hindernis war, dass die Datei build.bat nicht funktioniert, wenn git mit core.autocrlf=false konfiguriert ist. Nachdem ich auf core.autocrlf=true umgestellt und neu geklont hatte, ließ es sich bauen
      Als Toolchain habe ich x86_64-15.1.0-release-win32-seh-msvcrt-rt_v12-rev0.7z von https://github.com/niXman/mingw-builds-binaries/releases/tag... verwendet
      Damit entsteht eine 102-KB-.exe-Datei. Das ist von Anfang an deutlich besser als die behaupteten 278 KB. Vielleicht verwendet der Autor eine andere Toolchain oder andere Einstellungen; genaue Reproduktionsschritte wären daher hilfreich
      Mit ein paar GCC-Schaltern lässt sich das weiter verkleinern
      gcc -Os => 100 KB
      gcc -Oz => 99 KB
      gcc -flto => 101 KB
      gcc -s => 51 KB
      gcc -s -Oz -flto => 47 KB
      Wenn es nur um eine kleine .exe-Größe geht, gibt es noch viel Verbesserungspotenzial
    • Ein großer Teil liegt an der Plattform und dem ausführbaren Dateiformat
      Ohne Dinge wie Stack-Trace-Informationen, dynamische Link-Infrastruktur und Exception-Handling-Tabellen könnte es deutlich leichter sein. Exception-Handling-Tabellen werden auch in C benötigt, weil Exceptions C-Funktionen durchqueren können
    • Man könnte bei Demoszene-Wettbewerben eine Kategorie für 64-KB-TODO-Apps beantragen
    • Ehrlich gesagt überrascht mich, dass es so groß ist. Ich hätte erwartet, dass es kleiner ist oder dass ungefähr die Hälfte vom App-Icon belegt wird
      Als ich früher so etwas geschrieben habe, war es meiner Erinnerung nach kleiner. Liegt es vielleicht an MinGW?
    • Plötzlich erinnere ich mich an die Zeit, als Win32-Programmierung in Assembler ziemlich hip wurde
      Das war vermutlich eine Reaktion auf den Trend zu immer größeren Shareware-Downloads. Es waren die dunklen Zeiten von MFC
      Zusammen mit der frühen Palm-Pilot-68k-Programmierung erinnere ich mich daran als den letzten Aufschrei von Assembler außerhalb des Retrocomputings
  • Leute, das ist einfach eine App, die ich ausprobiert und zum Spaß gebaut habe. Die Kommentare haben recht; so etwas hätte man wahrscheinlich vernünftiger in C++ oder einer anderen Sprache bauen können

    • Vor etwa 30 Jahren habe ich beim Schreiben meines ersten Windows-Programms auf genau diese Weise gelernt. Allerdings hätte ich wohl einen C++-Compiler verwendet
      Ich weiß nicht genau warum, aber die Windows-API-Dokumentation schien dazu anzuleiten, C-artigen Code mit einem C++-Compiler zu schreiben. Microsoft sah C++ wohl als verbesserte Obermenge von C und hielt es für richtig, es auch für C-artigen Code zu verwenden
    • Ohne Witz: Ich würde diese App lieber benutzen als die Standard-TODO-App von Windows 11
    • Genau so sollte es sein
      Wenn man die Win32 API benutzt, ändert eine andere Sprache grundsätzlich nichts daran und kann es eher noch verwirrender machen
      Leute verfallen oft in C++-Denkmuster, und für jemanden, der mit der Win32 API nicht vertraut ist, hätte das Ganze nur zu einem noch verworreneren Klumpen gemacht
      Ein sehr niedlicher Versuch, und was andere auch denken: Vertrautheit mit der Win32 API ist eine solide Grundkompetenz
    • https://github.com/Efeckc17/YoutubeGO Als Referenz: Ich würde mich wirklich freuen, wenn ihr auch diese Anwendung reviewen oder euch ansehen würdet
    • Solche Projekte mit sauberer nativer UI waren der Grund, warum ich Programmieren lernen wollte. Cool
  • Ich sehe hier einige Leute ziemlich spötteln, die Software oder Websites bauen, die mehrere MB JS oder C# laden, um jedes Mal 278 KB Telemetrie zu senden, wenn der Nutzer die Maus bewegt

    • Eine ähnliche App in C# + WinForms ist auf der Platte unter 10 KB groß und nutzt 6 MB RAM
      Diese App nutzt 1,5 MB RAM, und beide starten sofort
  • Beim Entwickeln von Windows-Apps macht es immer gute Laune, dass man alles in C machen kann
    Es fühlt sich einfach natürlich an, von den grundlegendsten Bausteinen auf niedrigster Ebene aus aufzubauen
    Unter macOS dagegen ist alles ein Objective-C-Objekt. Man kann zwar eine App in reinem C schreiben, aber damit liegt man eine Ebene unter der von Apple vorgesehenen Abstraktion, wodurch es fast 1000-mal umständlicher wird. Um irgendetwas zu tun, muss man praktisch die Objective-C-Klassenhierarchie wie eine Marionette steuern, was sich wirklich unangenehm anfühlt
    Ich verstehe nicht, warum man die klassenbasierte API von Objective-C nicht wieder auf eine grundlegende, Win32-artige prozedurale API aufsetzen kann. Technisch gesehen ist Win32 zwar auch „klassenbasiert“, aber nur minimal
    Deshalb habe ich vorerst wenig Lust, meinen C-Code auf macOS zu portieren

    • Interessanterweise hat Windows mit COM und dem darauf aufgebauten WinRT eine eigene objektorientierte API/ABI
      Der MIDL-Compiler für COM-Interfaces erzeugt aber Header, mit denen man Interfaces in C verwenden und implementieren kann: https://www.codeproject.com/KB/com/com_in_c1.aspx
      Soweit ich weiß, funktioniert das auch heute noch mit WinRT. Allerdings werden die generierten C-Strukturen und Funktionsnamen sehr hässlich, weil sie den vollständigen Namespace enthalten müssen: https://stackoverflow.com/a/7437006/111335
    • Es gab durchaus eine prozedurale API, sie hieß Carbon
      Sie war ein nahezu Drop-in-Ersatz für die Macintosh-Toolbox-API, die user32.dll unverhohlen kopiert hatte
      Abgesehen von dem warmen Gefühl, das Programmierer dabei bekommen, will UI aber wirklich objektorientiert sein. Ein Baum unterschiedlicher Objekte, die sich teilweise gemeinsames Verhalten teilen, beschreibt praktisch 99 % des UI-Codes fast exakt
      Toolbox und Carbon hatten zu wenig von dieser Objektorientierung und mussten sich deshalb ziemlich verrenken. Eine tatsächlich eigenständige Idee, die Windows hinzugefügt hat, waren die Fensterklassen
      Persönlich empfinde ich die Merkwürdigkeit, Objective-C-Klassen direkt aus C heraus zu handhaben, jedes Mal in user32.dll-Code, wenn ich Fensterklassen oder Fensterprozeduren definieren muss[0]
      So wie UI Objektorientierung will, will Objektorientierung eigene Sprachfeatures. Man kann sie auch ohne bauen, aber die Benutzbarkeit wird 2000-mal schlechter
      [0] Oder alles, was mit GTK/GObject zu tun hat
  • Es sieht so aus, als würde gegen statische Bibliotheken gelinkt
    Man sollte nicht gegen statische Bibliotheken, sondern gegen DLLs linken; dann wird die Anwendung deutlich kleiner

    • Das scheint mir umgekehrt zu sein
      Wenn man die DLL ohnehin zusammen mit dem Programm ausliefern muss (weil sie nicht Teil des Betriebssystems ist), steckt in der DLL der gesamte Funktionsumfang, und jede DLL kann sogar ihre eigene C-Runtime mitbringen
      Wenn man alles statisch in eine einzelne EXE kompiliert, gibt es nur eine C-Runtime, und unbenutzte Funktionen lassen sich leicht entfernen
      DLLs reduzieren die Größe nur dann, wenn dieser Code von mehreren Programmen gemeinsam genutzt wird
    • Nein, gegen die statische Version der CRT zu linken ist eine gute Wahl. Nicht verwendeter Code kann entfernt werden
      Wenn man dynamisch gegen MSVCRxx/VCRUNTIME linkt, müssen Nutzer genau diese DLL von Microsoft herunterladen
      Wenn man dynamisch gegen MSVCRT linkt, gibt es dieses Problem nicht, aber in Visual Studio ist das sehr schwierig
      Der einzige Fall, in dem statisches Linken gegen die CRT wirklich nicht geht, dürfte die Einhaltung der LGPL sein
 
roxie 2025-05-16

Leute, ich habe das Gefühl, als würde ich den Atem der alten Hasen bis hierher spüren ...