Creación de una app nativa de Todo para Windows en C puro (278 KB, sin frameworks)
(github.com/Efeckc17)- Simple Todo es una app nativa de Todo para Windows hecha con C y la API de Win32, y es un proyecto que muestra programación de GUI en Windows y funciones de integración con el sistema
- Sus funciones principales son crear, editar y eliminar tareas, marcarlas como completadas, minimizar a la bandeja del sistema, opción de inicio automático con Windows y búsqueda en tiempo real sin distinción entre mayúsculas y minúsculas
- Los datos se guardan como archivo binario en
%APPDATA%\TodoApp\todos.dat, contienen la lista completa de tareas y admiten hasta 100 tareas - La compilación en Windows requiere MinGW-w64 y el SDK de Windows; después de clonar el repositorio, se compila con
.\build.bat - La estructura se divide en
main.c,todo.c,gui.cyutils/search.c, y la GUI usa la API de Win32, Common Controls y UXTheme
Naturaleza del proyecto
- Simple Todo es una aplicación nativa de Todo para Windows escrita en C y la API de Win32
- Apunta a ser un ejecutable nativo ligero y usa el look and feel predeterminado de Windows junto con los estilos visuales modernos de Windows
- La GUI está basada en la API de Win32 e incluye integración con la bandeja del sistema y persistencia de datos mediante entrada/salida de archivos
Funciones de Todo
- Se pueden crear, editar y eliminar elementos Todo
- Las tareas se pueden marcar como completadas
- Se puede establecer una prioridad para cada Todo
- La función de búsqueda opera en tiempo real y admite coincidencia sin distinción entre mayúsculas y minúsculas
- La app se puede minimizar a la bandeja del sistema
- Ofrece una opción para ejecutarse automáticamente al iniciar Windows
Método de almacenamiento de datos
- Los datos de Todo se guardan en
%APPDATA%\TodoApp\todos.dat - El formato de almacenamiento es un archivo binario que contiene la lista completa de tareas
- La capacidad de almacenamiento está limitada a un máximo de 100 tareas
Compilación y ejecución
- Requisitos
- Windows OS
- Compilador GCC de MinGW-w64
- SDK de Windows incluido en MinGW
- Proceso de compilación
- Instalar MinGW-w64 y agregar el directorio
binalPATHdel sistema - Clonar el repositorio y luego moverse al directorio
simple-todo-c - Ejecutar
.\build.batpara compilar
- Instalar MinGW-w64 y agregar el directorio
- La ejecución se inicia con
bin/todo.exe
Estructura de la implementación
- La estructura del proyecto se divide en archivos C por función
src/main.c: punto de entrada de la aplicaciónsrc/todo.c,src/todo.h: lógica de gestión de Todo y estructura de datossrc/gui.c: implementación de la GUIsrc/utils/search.c,src/utils/search.h: implementación y declaración de la función de búsquedasrc/app.manifest: manifiesto para los estilos visuales de Windows
- Los componentes principales son la API de Win32, Common Controls, UXTheme y File I/O
- Para depuración, ofrece una guía
DEBUGGING.mdque puede usarse en Visual Studio, VS Code, etc.
Licencia y contribuciones
- La licencia es la MIT License
- Se aceptan contribuciones mediante Pull Request
3 comentarios
Tiene su encanto.
Opiniones de Hacker News
La programación de GUI con Win32 tiene algo curiosamente atractivo.
Es un poco peculiar, pero si lees el blog de Raymond Chen, se entiende por qué.
La API de Win32 tiene raíces en la época del procesador 8088, así que hay trasfondos como ahorrar 40 bytes de código o usar un registro menos si se hace de cierta manera.
Antes hice muchas apps GUI de juguete con mingw y el libro de Petzold, y cosas como escribir controles personalizados, dibujar gráficos y texto, manejar scroll y hacer hit testing eran bastante divertidas.
Veo que la app usa
strcpyysprintf, pero para programación seria habría que usar variantes con verificación de longitud. Me sorprende que el compilador no haya arrojado un montón de advertencias.La API de Win32 tiene muchas funciones que reemplazan cosas de la biblioteca estándar de C. Si de verdad quieres reducir el tamaño del ejecutable, puedes intentar escribir la app sin la biblioteca estándar de C. Está
ZeroMemory()en lugar dememset(), yCopyMemory()en lugar dememcpy().En algún momento escribir código C puro a mano se vuelve doloroso, pero creo que hacerlo en C puro las primeras veces es lo mejor para aprender. Si administras los detalles directamente, mientras aprendes te haces una buena idea de lo que ocurre por dentro.
Si quieres seguir probando la programación GUI con Win32, recomiendo ver WTL (Windows Template Library). Es un wrapper de C++ sobre la API de Win32, así que hace mucho más fácil entender el comportamiento.
Aunque tiene muchísimas funciones, gracias a eso pesa apenas unos 3 MB. Sin frameworks y con un único ejecutable; ojalá todo el software siguiera siendo así.
strncpyen vez destrcpy, tienes que aguantar que tanto personas como herramientas de IA te digan una y otra vez que no deberías hacerlo.Personalmente, una de las principales razones por las que uso Zig es que reduce mucho estas trampas comunes. Claro que C también está bien.
ZeroMemory()en lugar dememset()yCopyMemory()en lugar dememcpy(), pero los intrinsics de MSVC probablemente usen instruccionesrep stos/movs.Podrían ser más pequeños que una llamada a función, porque una llamada a función también incluye el tamaño de la entrada en la tabla de importaciones.
fopen/fread/fclose, pero parece que no usa APIs de Win32 comoCreateFile/ReadFile/WriteFile.En vez de llamar laboriosamente a
CreateWindow()para cada control, tradicionalmente se colocaban recursos de cuadro de diálogo en un archivo.rc(Visual Studio todavía tiene un editor de diálogos para hacerlo visualmente) y se usabaCreateDialog()en lugar deCreateWindow().Así se crean automáticamente todos los controles. Si agregas un manifiesto de aplicación, también obtienes estilos de UI modernos y soporte para alta DPI.
Eso sí, si quieres redimensionamiento, todavía tienes que manejarlo manualmente, pero normalmente es fácil y el código no pasa de unos pocos cientos de bytes.
Existía K&R C, pero fuera de UNIX cada sistema operativo elegía por su cuenta qué funciones ofrecer.
Además, fuera de los sistemas tipo UNIX, existía la tradición de que la biblioteca estándar fuera responsabilidad del compilador de C, no de la plataforma.
Por eso las bibliotecas de C las proporcionaban Borland, Watcom, Symantec, Microsoft, Green Hills, Zortech y otros.
Mac OS fue igual hasta que apareció MPW, y lo mismo pasaba con IBM y Unisys, microcomputadoras y mainframes, y VMS antes de OpenVMS.
Desde Windows 10 también existe Universal C Runtime.
No hace falta un compilador de recursos ni un linker para crear una DLL de recursos.
Los archivos de recursos y su formato binario no son una buena API.
Si en un lenguaje de nivel razonablemente alto tienes llamadas a
CreateWindow, parece posible crear mediante metaprogramación un DSL parecido a recursos que se integre bien con el lenguaje.Hace tiempo hice algo parecido en ensamblador en Linux en menos de 2 KiB: https://gaultier.github.io/blog/x11_x64.html
Como dijeron otros, si lo haces en C puro y usas enlace dinámico, al menos en Linux es fácil mantenerlo por debajo de 20 KiB. En Windows quizá sea todavía más fácil, porque el sistema operativo trae muchas más cosas incluidas por defecto.
En cualquier caso, apoyo el intento. Probar las opciones de enlace que dejé al final de mi artículo debería ayudar a reducir el tamaño.
Todavía no he logrado compilar ncurses con musl.
Al leer “look and feel nativo de Windows”, antes de ejecutarlo esperaba que la vista de lista tuviera un menú contextual y que al hacer doble clic en ciertos campos apareciera un menú desplegable casi en línea o un cuadro de texto.
Ya no sé si ahora la gente no sabe diseñar programas según la UI/UX de Win32, o si simplemente yo estoy demasiado viejo.
Decían “sin frameworks”, pero con el escalado de DPI las fuentes se ven borrosas, no hay soporte para Tab, en los campos de texto Ctrl-A no selecciona todo, faltan varias funciones que incluso ofrecían frameworks premodernos, y al agregar filas también aparecen errores
No sé qué significa “moderno”
Este código verifica dinámicamente cuál está disponible entre
user32:SetProcessDpiAwarenessContext,shcore:SetProcessDpiAwarenessyuser32:SetProcessDPIAware, y lo llamaSi la versión de Windows es tan antigua que no implementa ninguno de ellos (anterior a Windows XP), no llama a nada
La mayoría de las funciones que mencioné como faltantes, en especial cosas como moverse entre controles con Tab, son muy fáciles de agregar
El programador de 6502 que llevo dentro se está muriendo por dentro al ver que 278KB ahora se considera liviano
El primer obstáculo que encontré fue que, si git está configurado con
core.autocrlf=false, el archivobuild.batno funciona. Al cambiarlo acore.autocrlf=truey clonar de nuevo, pude compilarUsé el toolchain
x86_64-15.1.0-release-win32-seh-msvcrt-rt_v12-rev0.7zde https://github.com/niXman/mingw-builds-binaries/releases/tag...En este caso sale un archivo
.exede 102KB. De entrada es mucho mejor que los 278KB afirmados. Puede que el autor use otro toolchain u otra configuración, así que estaría bien tener pasos exactos para reproducirloPasándole algunos flags a GCC se puede reducir más
gcc -Os=> 100KBgcc -Oz=> 99KBgcc -flto=> 101KBgcc -s=> 51KBgcc -s -Oz -flto=> 47KBSi lo único que importa es un
.exepequeño, todavía hay mucho margen de mejoraSin cosas como información de stack trace, infraestructura de enlazado dinámico y tablas de manejo de excepciones, podría ser mucho más liviano. Las tablas de manejo de excepciones son necesarias incluso en C, porque una excepción puede atravesar funciones C
Recuerdo que cuando escribí algo así hace tiempo era más pequeño. ¿Será por MinGW?
Probablemente fue una reacción a la tendencia de que las descargas de shareware fueran cada vez más grandes. Eran los años oscuros de MFC
Junto con la programación temprana para Palm Pilot 68k, lo recuerdo como el último gran momento del assembler fuera de la retrocomputación
Amigos, es simplemente una app hecha para probar y divertirse. Los comentarios tienen razón, y algo así probablemente habría sido más sensato hacerlo en C++ u otro lenguaje
No sé bien por qué, pero creo que la documentación de la API de Windows guiaba a escribir código estilo C usando un compilador de C++. Parece que Microsoft veía C++ como un superconjunto mejorado de C, y consideraba correcto usarlo incluso para código estilo C
Si vas a usar la API de Win32, otro lenguaje no cambia nada fundamentalmente, y de hecho puede hacerlo más confuso
La gente suele caer en la mentalidad de C++, y para alguien que no está familiarizado con la API de Win32 eso habría convertido todo en un amasijo aún más confuso
Es un intento muy lindo, y más allá de lo que piensen otros, la familiaridad con la API de Win32 es una buena habilidad base
Veo aquí a varias personas burlándose bastante que hacen software o sitios web que cargan varios MB de JS o C# para mandar 278KB de telemetría cada vez que el usuario mueve el mouse
Esta app usa 1.5MB de RAM, y ambas arrancan al instante
Al desarrollar apps para Windows, siempre resulta agradable saber que se puede hacer todo en C
Simplemente se siente natural construir desde los elementos básicos de más bajo nivel
En cambio, en macOS todo es un objeto Objective-C, así que aunque se puede escribir una app en C puro, es como bajar un nivel por debajo de la abstracción que Apple pretendía, y termina siendo casi 1000 veces más verboso. Para hacer algo, en la práctica hay que manipular como marionetas la jerarquía de clases de Objective-C, y eso se siente realmente incómodo
No entiendo por qué no se puede volver a montar la API basada en clases de Objective-C encima de una API procedural básica al estilo Win32. Técnicamente Win32 también está “basado en clases”, pero es mínimo
Por eso, por ahora no me dan muchas ganas de portar mi código C a macOS
Pero el compilador MIDL para interfaces COM genera headers que permiten usar e implementar interfaces desde C: https://www.codeproject.com/KB/com/com_in_c1.aspx
Hasta donde sé, hoy sigue funcionando también con WinRT. Eso sí, los structs y nombres de funciones generados tienen que incluir todo el namespace, así que quedan muy feos: https://stackoverflow.com/a/7437006/111335
Era un reemplazo casi directo de la API Macintosh Toolbox, que
user32.dllcopió descaradamentePero, aparte de la calidez que pueda sentir el programador, la UI realmente quiere orientación a objetos. Un árbol de distintos objetos que comparten parte del comportamiento común describe casi tal cual el 99% del código de UI
Toolbox y Carbon carecían de esa orientación a objetos, así que estaban bastante forzados. Una idea realmente más original que agregó Windows fueron precisamente las clases de ventana
Personalmente, la rareza que se siente al manipular clases Objective-C directamente desde C es la misma que siento cada vez que tengo que definir clases de ventana o procedimientos de ventana en código de
user32.dll[0]Así como la UI quiere orientación a objetos, la orientación a objetos quiere funcionalidades dedicadas del lenguaje. Se puede construir sin ellas, pero la usabilidad se vuelve 2000 veces peor
[0] O cualquier cosa relacionada con GTK/GObject
Parece que está enlazando contra una biblioteca estática
No debería enlazar contra una biblioteca estática, sino contra una DLL, y así el tamaño de la aplicación se reduciría bastante
Si de todos modos hay que distribuir la DLL junto con el programa (porque no forma parte del sistema operativo), la DLL contiene toda la funcionalidad, y cada DLL incluso puede tener su propio runtime de C
Si se compila todo estáticamente en un único EXE, hay un solo runtime de C y es fácil eliminar las funciones que no se usan
Una DLL solo reduce el tamaño cuando ese código se comparte entre varios programas
Si enlazas dinámicamente contra
MSVCRxx/VCRUNTIME, el usuario tiene que descargar exactamente esa DLL desde MicrosoftSi enlazas dinámicamente contra
MSVCRT, no tienes ese problema, pero hacerlo en Visual Studio es muy difícilEl único caso en que enlazar estáticamente contra el CRT realmente podría estar mal sería algo como cumplir con la LGPL
Se siente como si el aliento nasal de los panas llegara hasta aquí...