10 pontos por GN⁺ 2025-05-12 | 3 comentários | Compartilhar no WhatsApp
  • 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.dat como 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.c e utils/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 bin ao PATH do sistema
    • Clone o repositório e acesse o diretório simple-todo-c
    • Execute .\build.bat para fazer a build
  • 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ção
    • src/todo.c, src/todo.h: lógica de gerenciamento de Todos e estruturas de dados
    • src/gui.c: implementação da GUI
    • src/utils/search.c, src/utils/search.h: implementação e declaração da funcionalidade de busca
    • src/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

 
aer0700 2025-05-13

Tem um certo romantismo nisso.

 
GN⁺ 2025-05-12
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 strcpy e sprintf; em programação séria, deveriam ser usadas variantes que verificam o comprimento. Surpreende o compilador não ter despejado avisos
    A 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 de memset() e CopyMemory() no lugar de memcpy()
    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

    • Uso um excelente app de PC chamado AlomWare Toolbox, que é quase um exemplo clássico do design Win32(https://www.alomware.com/images/tab-automation.png)
      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
    • Hoje em dia, se você não usar ao menos strncpy em vez de strcpy, terá de aguentar não só pessoas, mas até ferramentas de IA dizendo continuamente que não deve fazer isso
      Pessoalmente, um dos principais motivos pelos quais uso Zig é que ele reduz muitas dessas armadilhas comuns. Claro, C também é ok
    • Foi dito que há ZeroMemory() no lugar de memset() e CopyMemory() no lugar de memcpy(), mas os intrinsics do MSVC provavelmente usariam as instruções rep stos/movs
      Isso 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
    • Fiz muito esse tipo de coisa e, sinceramente, sinto falta da capacidade de desenvolver UI nativa com código nativo
    • Também estão usando fopen/fread/fclose, mas não parece que usem APIs Win32 como CreateFile/ReadFile/WriteFile
  • Em 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 usava CreateDialog() no lugar de CreateWindow()
    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

    • Com esse método, você também ganha automaticamente a navegação por Tab entre controles e alguns atalhos de teclado
      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
    • A sobreposição entre a biblioteca padrão de C e a biblioteca do sistema operacional existe, na prática, apenas no UNIX, e em 1985, quando o Windows 1.0 saiu, ainda não havia nada que pudesse ser chamado de padrão
      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
    • Essa abordagem pode ser facilmente levada para linguagens que tenham uma boa interface de funções externas(FFI), e nada mais é necessário
      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 à linguagem
  • Antigamente, 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

    • Um programa TODO TUI baseado em ncurses um pouco mais expandido tem 15K. É Linux e não é linkado estaticamente
      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

    • Há um exemplo de configuração de reconhecimento de DPI aqui: https://github.com/Dwedit/GameStretcher/blob/master/Stretche...
      Esse código verifica dinamicamente qual chamada está disponível entre user32:SetProcessDpiAwarenessContext, shcore:SetProcessDpiAwareness e user32:SetProcessDPIAware, e a invoca
      Se a versão do Windows for tão antiga que nenhuma delas esteja implementada (anterior ao Windows XP), ele não chama nada
    • “Moderno” parece querer dizer que é muito maior do que precisa ser e, ao mesmo tempo, falta muita funcionalidade
      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

    • Tentei reproduzir esse binário para ver onde os 278 KB estavam sendo usados
      O primeiro obstáculo que encontrei foi que, se o git estiver configurado com core.autocrlf=false, o arquivo build.bat não funciona. Depois de mudar para core.autocrlf=true e clonar de novo, consegui compilar
      Usei o toolchain x86_64-15.1.0-release-win32-seh-msvcrt-rt_v12-rev0.7z de https://github.com/niXman/mingw-builds-binaries/releases/tag...
      Nesse caso, sai um arquivo .exe de 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 reproduzir
      Passando algumas opções ao GCC, dá para reduzir mais
      gcc -Os => 100 KB
      gcc -Oz => 99 KB
      gcc -flto => 101 KB
      gcc -s => 51 KB
      gcc -s -Oz -flto => 47 KB
      Se o único interesse for um .exe pequeno, ainda há bastante espaço para melhorar
    • Boa parte disso se deve à plataforma e ao formato do executável
      Sem 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
    • Dá até para fazer uma petição para criarem uma categoria de app TODO de 64 KB nas competições da demoscene
    • Sinceramente, surpreende que seja tão grande. Eu esperava algo menor, ou que metade fosse ocupada pelo ícone do app
      Lembro que, quando escrevi algo assim antigamente, era menor. Será que é por causa do MinGW?
    • De repente me veio à mente a época em que programar Win32 em assembly ficou bem descolado
      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

    • Cerca de 30 anos atrás, quando fiz meu primeiro programa para Windows, aprendi exatamente desse jeito. Só que provavelmente usei um compilador C++
      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
    • Sem brincadeira, eu preferiria usar este app ao app TODO padrão do Windows 11
    • É exatamente assim que deveria ser
      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
    • https://github.com/Efeckc17/YoutubeGO Como referência, eu ficaria muito feliz se vocês também revisassem ou dessem uma olhada neste aplicativo
    • Projetos de UI nativa limpa como este foram o que me motivou a aprender programação. Muito legal
  • 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

    • Um app parecido feito em C# + WinForms tem menos de 10 KB em disco e usa 6 MB de RAM
      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

    • Curiosamente, o Windows tem sua própria API/ABI orientada a objetos, chamada COM, e o WinRT construído sobre COM
      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
    • Existia sim uma API procedural, chamada Carbon
      Era um substituto quase drop-in para a API Macintosh Toolbox, que a user32.dll copiou descaradamente
      Só 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

    • Parece o contrário
      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
    • Não, linkar com a versão estática da CRT é uma boa escolha. Dá para eliminar código não usado
      Se você fizer link dinâmico com MSVCRxx/VCRUNTIME, o usuário precisa baixar exatamente aquela DLL da Microsoft
      Se fizer link dinâmico com MSVCRT, esse problema não existe, mas é muito difícil fazer isso no Visual Studio
      O único caso em que linkar estaticamente com a CRT realmente não dá certo talvez seja por conformidade com a LGPL
 
roxie 2025-05-16

Irmãos, dá até para sentir o bafo de vocês chegando até aqui...