Criação de um app nativo de Todo para Windows em C puro (278 KB, sem framework)
(github.com/Efeckc17)- Simple Todo é um app nativo de Todo para Windows feito com C e a API Win32, um projeto que demonstra programação de GUI no Windows e recursos de integração com o sistema
- Os principais recursos incluem criar, editar e excluir Todos, marcar como concluído, minimizar para a bandeja do sistema, opção de iniciar automaticamente com o Windows e busca em tempo real sem diferenciar maiúsculas de minúsculas
- Os dados são salvos em
%APPDATA%\TodoApp\todos.datcomo um arquivo binário, contendo a lista completa de Todos e com suporte a até 100 Todos - A build no Windows requer MinGW-w64 e o Windows SDK; após clonar o repositório, compile com
.\build.bat - A estrutura é dividida em
main.c,todo.c,gui.ceutils/search.c; a GUI usa a API Win32, Common Controls e UXTheme
Natureza do projeto
- Simple Todo é um aplicativo nativo de Todo para Windows escrito em C e na API Win32
- Tem como objetivo ser um executável nativo leve, usando o visual e a experiência padrão do Windows e os estilos visuais modernos do Windows
- A GUI é baseada na API Win32 e inclui integração com a bandeja do sistema e persistência de dados por meio de entrada e saída de arquivos
Recursos de Todo
- É possível criar, editar e excluir itens de Todo
- É possível marcar tarefas como concluídas
- É possível definir prioridade para cada Todo
- A busca funciona em tempo real e oferece suporte a correspondência sem diferenciar maiúsculas de minúsculas
- O app pode ser minimizado para a bandeja do sistema
- Oferece uma opção para executar automaticamente ao iniciar o Windows
Forma de armazenamento dos dados
- Os dados de Todo são salvos em
%APPDATA%\TodoApp\todos.dat - O formato de armazenamento é um arquivo binário que contém a lista completa de Todos
- A capacidade de armazenamento é limitada a no máximo 100 Todos
Build e execução
- Requisitos
- Windows OS
- Compilador GCC MinGW-w64
- Windows SDK incluído no MinGW
- Procedimento de build
- Instale o MinGW-w64 e adicione o diretório
binaoPATHdo sistema - Clone o repositório e acesse o diretório
simple-todo-c - Execute
.\build.batpara fazer a build
- Instale o MinGW-w64 e adicione o diretório
- A execução começa com
bin/todo.exe
Composição da implementação
- A estrutura do projeto é dividida em arquivos C por funcionalidade
src/main.c: ponto de entrada da aplicaçãosrc/todo.c,src/todo.h: lógica de gerenciamento de Todos e estruturas de dadossrc/gui.c: implementação da GUIsrc/utils/search.c,src/utils/search.h: implementação e declaração da funcionalidade de buscasrc/app.manifest: manifesto para estilos visuais do Windows
- Os principais componentes são API Win32, Common Controls, UXTheme e File I/O
- Para depuração, fornece o guia
DEBUGGING.md, que pode ser usado no Visual Studio, VS Code etc.
Licença e contribuições
- A licença é a MIT License
- Aceita contribuições por meio de Pull Requests
3 comentários
Tem um certo romantismo nisso.
Opiniões no Hacker News
Há algo curiosamente agradável na programação de GUI Win32
É um pouco peculiar, mas, lendo o blog do Raymond Chen, dá para entender o motivo
A API Win32 tem raízes na época do processador 8088, então há contextos do tipo economizar 40 bytes de código ou usar um registrador a menos se fizer de determinada maneira
Antigamente, fiz muitos apps GUI de brinquedo com mingw e o livro do Petzold, e coisas como criar controles customizados, desenhar gráficos e texto, tratar rolagem e fazer hit testing eram todas bem divertidas
Dá para ver que o app usa
strcpyesprintf; em programação séria, deveriam ser usadas variantes que verificam o comprimento. Surpreende o compilador não ter despejado avisosA API Win32 tem muitas funções que substituem coisas da biblioteca padrão de C. Se você realmente quiser reduzir o tamanho do executável, pode tentar escrever o app sem a biblioteca padrão de C. Há
ZeroMemory()no lugar dememset()eCopyMemory()no lugar dememcpy()Em algum momento, escrever código C puro diretamente começa a ficar doloroso, mas acho que fazer isso em C puro nas primeiras vezes é o melhor para aprender. Ao gerenciar os detalhes diretamente, você pega uma boa noção do que acontece por dentro enquanto aprende
Se quiser se aprofundar mais em programação de GUI Win32, recomendo olhar a WTL(Windows Template Library). É um wrapper em C++ em torno da API Win32, o que torna o comportamento muito mais fácil de entender
Mesmo tendo muitos recursos, por causa disso ele tem só cerca de 3 MB. Sem framework e com apenas um único executável; eu gostaria que todos os softwares ainda fossem assim
strncpyem vez destrcpy, terá de aguentar não só pessoas, mas até ferramentas de IA dizendo continuamente que não deve fazer issoPessoalmente, um dos principais motivos pelos quais uso Zig é que ele reduz muitas dessas armadilhas comuns. Claro, C também é ok
ZeroMemory()no lugar dememset()eCopyMemory()no lugar dememcpy(), mas os intrinsics do MSVC provavelmente usariam as instruçõesrep stos/movsIsso pode ser menor do que uma chamada de função, porque a chamada de função também inclui o tamanho da entrada na tabela de importação
fopen/fread/fclose, mas não parece que usem APIs Win32 comoCreateFile/ReadFile/WriteFileEm vez de chamar
CreateWindow()trabalhosamente para cada controle, tradicionalmente se colocavam recursos de caixa de diálogo em um arquivo.rc(o Visual Studio ainda tem um editor de diálogos para fazer isso visualmente) e se usavaCreateDialog()no lugar deCreateWindow()Assim, todos os controles são criados automaticamente. Ao adicionar um manifesto de aplicação, você também obtém o estilo moderno de UI e suporte a alto DPI
Porém, se quiser redimensionamento, ainda precisa tratar isso manualmente; em geral é fácil e o código não passa de algumas centenas de bytes
Havia K&R C, mas cada sistema operacional fora do UNIX escolhia por conta própria quais recursos oferecer
Além disso, em ambientes não UNIX, havia a tradição de a biblioteca padrão ficar sob responsabilidade do compilador C, não da plataforma
Por isso, a biblioteca C era fornecida por Borland, Watcom, Symantec, Microsoft, Green Hills, Zortech etc.
O Mac OS também foi assim até o MPW surgir, e o mesmo valia para IBM e Unisys, microcomputadores e mainframes, e o VMS antes do OpenVMS
Desde o Windows 10, também existe o Universal C Runtime
Não é preciso compilador de recursos nem linker para criar uma DLL de recursos
Arquivos de recurso e seu formato binário não são uma boa API
Se uma linguagem de nível razoavelmente alto tiver chamadas a
CreateWindow, parece que daria para criar, com metaprogramação, uma DSL parecida com recursos que se integre bem à linguagemAntigamente, fiz algo parecido em assembly no Linux com menos de 2 KiB: https://gaultier.github.io/blog/x11_x64.html
Como outras pessoas disseram, se fizer em C puro e usar linkagem dinâmica, dá para manter facilmente abaixo de 20 KiB, pelo menos no Linux. No Windows, talvez seja ainda mais fácil, já que há muito mais coisas incluídas por padrão no sistema operacional
De todo modo, apoio a tentativa. Se experimentar as opções de linkagem que deixei no fim do meu texto, elas devem ajudar a reduzir o tamanho
Ainda não consegui compilar ncurses com musl
Quando disseram “look and feel nativo do Windows”, antes de executar de fato eu esperava que a visualização de lista tivesse um menu de contexto e que, ao dar duplo clique em certos campos, aparecesse um menu suspenso quase inline ou uma caixa de texto
Agora não sei se as pessoas já não sabem mais como projetar programas de acordo com a UI/UX do Win32, ou se sou eu que estou velho demais
Dizem “sem framework”, mas no escalonamento de DPI a fonte fica borrada, não há suporte a Tab, Ctrl-A não seleciona tudo nos campos de texto, faltam vários recursos que até frameworks pré-modernos já ofereciam, e ainda dá erro ao adicionar linhas
Não sei o que “moderno” quer dizer nesse caso
Esse código verifica dinamicamente qual chamada está disponível entre
user32:SetProcessDpiAwarenessContext,shcore:SetProcessDpiAwarenesseuser32:SetProcessDPIAware, e a invocaSe a versão do Windows for tão antiga que nenhuma delas esteja implementada (anterior ao Windows XP), ele não chama nada
A maioria dos recursos citados como ausentes, especialmente a navegação com Tab entre controles, é muito fácil de adicionar
O programador de 6502 dentro de mim está morrendo por dentro ao ver 278 KB agora passar por algo leve
O primeiro obstáculo que encontrei foi que, se o git estiver configurado com
core.autocrlf=false, o arquivobuild.batnão funciona. Depois de mudar paracore.autocrlf=truee clonar de novo, consegui compilarUsei o toolchain
x86_64-15.1.0-release-win32-seh-msvcrt-rt_v12-rev0.7zde https://github.com/niXman/mingw-builds-binaries/releases/tag...Nesse caso, sai um arquivo
.exede 102 KB. Já é muito melhor que os 278 KB alegados. Pode ser que o autor esteja usando outro toolchain ou outras configurações, então seria bom ter passos exatos para reproduzirPassando algumas opções ao GCC, dá para reduzir mais
gcc -Os=> 100 KBgcc -Oz=> 99 KBgcc -flto=> 101 KBgcc -s=> 51 KBgcc -s -Oz -flto=> 47 KBSe o único interesse for um
.exepequeno, ainda há bastante espaço para melhorarSem informações de stack trace, infraestrutura de link dinâmico, tabelas de tratamento de exceções e coisas do tipo, poderia ser muito mais leve. As tabelas de tratamento de exceções também são necessárias em C, porque exceções podem atravessar funções C
Lembro que, quando escrevi algo assim antigamente, era menor. Será que é por causa do MinGW?
Provavelmente foi uma reação à tendência de aumento no tamanho dos downloads de shareware. Foram os tempos sombrios do MFC
Junto com a programação inicial do Palm Pilot 68k, lembro disso como o último suspiro do assembly fora do retrocomputing
Pessoal, foi só um app feito para experimentar e se divertir. Os comentários estão certos, e algo assim provavelmente poderia ter sido feito de forma mais razoável em C++ ou outra linguagem
Não sei bem o motivo, mas acho que a documentação da API do Windows orientava a escrever código em estilo C usando um compilador C++. Parece que a Microsoft via C++ como um superconjunto aprimorado de C e achava correto usá-lo até para código em estilo C
Se você vai usar a API Win32, outra linguagem não muda nada fundamentalmente e pode até deixar tudo mais confuso
As pessoas frequentemente caem no modo de pensar de C++, e para alguém que não está familiarizado com a API Win32 isso teria transformado tudo em uma massa ainda mais confusa
É uma tentativa muito simpática e, independentemente do que os outros pensem, familiaridade com a API Win32 é uma boa competência básica
Vejo aqui bastante sarcasmo vindo de pessoas que criam softwares ou sites que carregam vários MB de JS ou C# para tentar enviar 278 KB de telemetria a cada movimento do mouse do usuário
Este app usa 1,5 MB de RAM, e ambos iniciam instantaneamente
Ao desenvolver apps para Windows, o fato de dá para fazer tudo em C sempre dá uma sensação boa
A abordagem de construir tudo a partir dos elementos básicos de mais baixo nível simplesmente parece natural
Já no macOS, tudo é um objeto Objective-C; então, embora seja possível escrever um app em C puro, você acaba descendo um nível abaixo da abstração que a Apple pretendia, e isso fica quase 1000 vezes mais verboso. Para fazer qualquer coisa, na prática é preciso manipular a hierarquia de classes do Objective-C como se fosse uma marionete, o que é bem desconfortável
Não entendo por que não dá para colocar a API baseada em classes do Objective-C por cima de uma API procedural básica no estilo Win32. Tecnicamente, Win32 também é “baseado em classes”, mas é o mínimo
Por isso, por enquanto, não fico muito inclinado a portar meu código C para macOS
Mas o compilador MIDL para interfaces COM gera headers que permitem usar e implementar interfaces em C: https://www.codeproject.com/KB/com/com_in_c1.aspx
Até onde sei, isso ainda funciona hoje no WinRT. Só que os nomes das structs e funções C geradas ficam muito feios, porque precisam incluir o namespace inteiro: https://stackoverflow.com/a/7437006/111335
Era um substituto quase drop-in para a API Macintosh Toolbox, que a
user32.dllcopiou descaradamenteSó que, tirando o conforto que isso dá ao programador, UI realmente pede orientação a objetos. Uma árvore de objetos diferentes que compartilham alguns comportamentos comuns descreve quase exatamente 99% do código de UI
Toolbox e Carbon forçavam bastante a barra por não terem essa orientação a objetos. Uma ideia realmente original que o Windows acrescentou foi justamente a classe de janela
Pessoalmente, a estranheza que sinto ao lidar diretamente com classes Objective-C em C é a mesma que sinto toda vez que preciso definir uma classe de janela ou um procedimento de janela em código com
user32.dll[0]Assim como UI pede orientação a objetos, orientação a objetos pede recursos dedicados na linguagem. Dá para fazer sem eles, mas a usabilidade fica 2000 vezes pior
[0] Ou qualquer coisa relacionada a GTK/GObject
Parece que está linkando com uma biblioteca estática
Não deveria ser com uma biblioteca estática, e sim linkar com uma DLL; assim o tamanho da aplicação cai bastante
Se de qualquer forma você precisa distribuir a DLL junto com o programa (porque ela não faz parte do sistema operacional), a DLL contém a funcionalidade inteira, e cada DLL pode até ter seu próprio runtime C
Se você compilar tudo estaticamente em um único EXE, há apenas um runtime C, e funções não usadas podem ser removidas com facilidade
DLLs só reduzem o tamanho quando aquele código é compartilhado entre vários programas
Se você fizer link dinâmico com
MSVCRxx/VCRUNTIME, o usuário precisa baixar exatamente aquela DLL da MicrosoftSe fizer link dinâmico com
MSVCRT, esse problema não existe, mas é muito difícil fazer isso no Visual StudioO único caso em que linkar estaticamente com a CRT realmente não dá certo talvez seja por conformidade com a LGPL
Irmãos, dá até para sentir o bafo de vocês chegando até aqui...