Native Windows-Todo-App in reinem C (278 KB, ohne Framework)
(github.com/Efeckc17)- 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.datgespeichert; 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.batkompiliert - Die Struktur ist in
main.c,todo.c,gui.cundutils/search.caufgeteilt; 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.datgespeichert - 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
binzum System-PATHhinzufügen - Das Repository klonen und in das Verzeichnis
simple-todo-cwechseln .\build.batausführen, um den Build zu erstellen
- MinGW-w64 installieren und das Verzeichnis
- Gestartet wird die Anwendung mit
bin/todo.exe
Implementierungsstruktur
- Die Projektstruktur ist nach Funktionen in C-Dateien aufgeteilt
src/main.c: Einstiegspunkt der Anwendungsrc/todo.c,src/todo.h: Todo-Verwaltungslogik und Datenstrukturensrc/gui.c: GUI-Implementierungsrc/utils/search.c,src/utils/search.h: Implementierung und Deklarationen der Suchfunktionsrc/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
Das hat einfach Charme.
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
strcpyundsprintf; bei ernsthafter Programmierung sollte man Varianten mit Längenprüfung verwenden. Es überrascht mich, dass der Compiler nicht eine Flut von Warnungen ausgegeben hatIn 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 esZeroMemory(), stattmemcpy()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
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
strncpystattstrcpyverwendet, muss man damit rechnen, dass einem nicht nur Menschen, sondern sogar AI-Tools ständig sagen, dass man das nicht tun sollteEiner der Hauptgründe, warum ich persönlich Zig verwende, ist, dass es viele dieser üblichen Fallstricke reduziert. C ist natürlich auch okay
memset()ZeroMemory()und stattmemcpy()CopyMemory()gibt, aber MSVC-Intrinsics würden vermutlich die Befehlerep stos/movsverwendenDas kann kleiner sein als ein Funktionsaufruf, weil zu einem Funktionsaufruf auch die Größe des Importtabellen-Eintrags gehört
fopen/fread/fcloseverwendet, aber offenbar nicht die Win32-APIs wieCreateFile/ReadFile/WriteFileStatt 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 stattCreateWindow()CreateDialog()verwendetDadurch werden alle Controls automatisch erzeugt. Wenn man ein Anwendungsmanifest hinzufügt, bekommt man außerdem moderne UI-Stile und High-DPI-Unterstützung
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
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
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
CreateWindowhat, könnte man per Metaprogrammierung wohl eine ressourcenähnliche DSL bauen, die sich gut in die Sprache einfügtIch 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
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
Dieser Code prüft dynamisch, welche von
user32:SetProcessDpiAwarenessContext,shcore:SetProcessDpiAwarenessunduser32:SetProcessDPIAwareverfügbar ist, und ruft sie aufWenn die Windows-Version so alt ist, dass keine davon implementiert ist (vor Windows XP), wird nichts aufgerufen
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
Das erste Hindernis war, dass die Datei
build.batnicht funktioniert, wenn git mitcore.autocrlf=falsekonfiguriert ist. Nachdem ich aufcore.autocrlf=trueumgestellt und neu geklont hatte, ließ es sich bauenAls Toolchain habe ich
x86_64-15.1.0-release-win32-seh-msvcrt-rt_v12-rev0.7zvon https://github.com/niXman/mingw-builds-binaries/releases/tag... verwendetDamit 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 hilfreichMit ein paar GCC-Schaltern lässt sich das weiter verkleinern
gcc -Os=> 100 KBgcc -Oz=> 99 KBgcc -flto=> 101 KBgcc -s=> 51 KBgcc -s -Oz -flto=> 47 KBWenn es nur um eine kleine
.exe-Größe geht, gibt es noch viel VerbesserungspotenzialOhne 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
Als ich früher so etwas geschrieben habe, war es meiner Erinnerung nach kleiner. Liegt es vielleicht an MinGW?
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
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
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
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
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
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
Sie war ein nahezu Drop-in-Ersatz für die Macintosh-Toolbox-API, die
user32.dllunverhohlen kopiert hatteAbgesehen 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
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
Wenn man dynamisch gegen
MSVCRxx/VCRUNTIMElinkt, müssen Nutzer genau diese DLL von Microsoft herunterladenWenn man dynamisch gegen
MSVCRTlinkt, gibt es dieses Problem nicht, aber in Visual Studio ist das sehr schwierigDer einzige Fall, in dem statisches Linken gegen die CRT wirklich nicht geht, dürfte die Einhaltung der LGPL sein
Leute, ich habe das Gefühl, als würde ich den Atem der alten Hasen bis hierher spüren ...