10 điểm bởi GN⁺ 2025-05-12 | 3 bình luận | Chia sẻ qua WhatsApp
  • Simple Todo là ứng dụng Todo gốc cho Windows được xây dựng bằng C và Win32 API, là một dự án trình diễn lập trình GUI trên Windows và khả năng tích hợp hệ thống
  • Các tính năng chính gồm tạo·sửa·xóa Todo, đánh dấu hoàn thành, thu nhỏ xuống system tray, tùy chọn tự khởi động cùng Windows và tìm kiếm thời gian thực không phân biệt hoa thường
  • Dữ liệu được lưu dưới dạng tệp nhị phân tại %APPDATA%\TodoApp\todos.dat, chứa toàn bộ danh sách Todo và hỗ trợ tối đa 100 Todo
  • Việc build trên Windows yêu cầu MinGW-w64 và Windows SDK; sau khi clone kho lưu trữ, biên dịch bằng .\build.bat
  • Cấu trúc được chia thành main.c, todo.c, gui.c, utils/search.c; GUI sử dụng Win32 API, Common Controls và UXTheme

Tính chất dự án

  • Simple Todo là ứng dụng Todo gốc cho Windows được viết bằng C và Win32 API
  • Mục tiêu là tạo ra tệp thực thi gốc nhẹ, sử dụng giao diện và cảm giác mặc định của Windows cùng phong cách hình ảnh hiện đại của Windows
  • GUI dựa trên Win32 API, bao gồm tích hợp system tray và khả năng lưu trữ dữ liệu bền vững thông qua file I/O

Tính năng Todo

  • Có thể tạo, sửa, xóa các mục Todo
  • Có thể đánh dấu công việc là đã hoàn thành
  • Có thể đặt mức độ ưu tiên cho từng Todo
  • Chức năng tìm kiếm hoạt động theo thời gian thực và hỗ trợ khớp không phân biệt hoa thường
  • Có thể thu nhỏ ứng dụng xuống system tray
  • Cung cấp tùy chọn tự động chạy khi Windows khởi động

Cách lưu trữ dữ liệu

  • Dữ liệu Todo được lưu tại %APPDATA%\TodoApp\todos.dat
  • Định dạng lưu là tệp nhị phân chứa toàn bộ danh sách Todo
  • Dung lượng lưu bị giới hạn tối đa 100 Todo

Build và chạy

  • Yêu cầu
    • Windows OS
    • Trình biên dịch GCC MinGW-w64
    • Windows SDK đi kèm MinGW
  • Quy trình build
    • Cài đặt MinGW-w64 và thêm thư mục bin vào PATH của hệ thống
    • Clone kho lưu trữ rồi chuyển vào thư mục simple-todo-c
    • Chạy .\build.bat để build
  • Chạy ứng dụng bằng bin/todo.exe

Cấu trúc triển khai

  • Cấu trúc dự án được chia thành các tệp C theo từng chức năng
    • src/main.c: điểm vào của ứng dụng
    • src/todo.c, src/todo.h: logic quản lý Todo và cấu trúc dữ liệu
    • src/gui.c: triển khai GUI
    • src/utils/search.c, src/utils/search.h: triển khai và khai báo chức năng tìm kiếm
    • src/app.manifest: manifest cho phong cách hình ảnh của Windows
  • Các thành phần cốt lõi gồm Win32 API, Common Controls, UXTheme và File I/O
  • Việc gỡ lỗi có hướng dẫn trong DEBUGGING.md, có thể dùng với Visual Studio, VS Code, v.v.

Giấy phép và đóng góp

  • Giấy phép là MIT License
  • Chấp nhận đóng góp qua Pull Request

3 bình luận

 
aer0700 2025-05-13

Đúng là rất có chất lãng mạn.

 
GN⁺ 2025-05-12
Ý kiến trên Hacker News
  • Lập trình GUI Win32 có điều gì đó lạ lùng nhưng rất dễ thích
    Hơi đặc biệt một chút, nhưng nếu đọc blog của Raymond Chen thì sẽ thấy lý do
    Win32 API có gốc rễ từ thời bộ xử lý 8088, nên có những bối cảnh kiểu như làm theo một cách nhất định sẽ tiết kiệm được 40 byte mã hoặc dùng bớt một thanh ghi
    Trước đây tôi từng làm khá nhiều ứng dụng GUI đồ chơi bằng mingw và sách của Petzold; viết control tùy chỉnh, vẽ đồ họa và văn bản, xử lý cuộn, hit testing, tất cả đều khá thú vị
    Thấy ứng dụng dùng strcpy, sprintf, nhưng nếu là lập trình nghiêm túc thì nên dùng các biến thể có kiểm tra độ dài. Thật ngạc nhiên là trình biên dịch không xả ra hàng loạt cảnh báo
    Win32 API có nhiều hàm thay thế cho những thứ có trong thư viện chuẩn C. Nếu thực sự muốn giảm kích thước file thực thi, có thể thử viết ứng dụng mà không dùng thư viện chuẩn C. Có ZeroMemory() thay cho memset(), CopyMemory() thay cho memcpy()
    Đến một lúc nào đó việc tự viết mã C thuần sẽ trở nên đau đớn, nhưng tôi nghĩ vài lần đầu làm bằng C thuần là cách tốt nhất để học. Khi tự quản lý các chi tiết, trong lúc học bạn sẽ dễ nắm được bên trong đang xảy ra chuyện gì
    Nếu muốn thử thêm lập trình GUI Win32, tôi khuyên nên xem WTL(Windows Template Library). Đây là wrapper C++ bọc Win32 API, giúp hiểu hành vi dễ hơn nhiều

    • Tôi dùng một ứng dụng PC rất hay tên là AlomWare Toolbox, gần như là mẫu mực của thiết kế Win32(https://www.alomware.com/images/tab-automation.png)
      Dù có rất nhiều tính năng, nhờ vậy kích thước chỉ khoảng 3MB. Không framework, chỉ một file thực thi duy nhất; ước gì mọi phần mềm đến giờ vẫn theo kiểu này
    • Thời nay nếu ít nhất không dùng strncpy thay cho strcpy, bạn phải chấp nhận việc không chỉ con người mà cả công cụ AI cũng liên tục bảo rằng không nên làm vậy
      Cá nhân tôi dùng Zig một phần lớn là vì nó giảm được nhiều cạm bẫy phổ biến như thế. Dĩ nhiên C cũng ổn
    • Nói là có ZeroMemory() thay cho memset(), CopyMemory() thay cho memcpy(), nhưng MSVC intrinsic có lẽ sẽ dùng lệnh rep stos/movs
      Có thể còn nhỏ hơn cả gọi hàm, vì gọi hàm còn bao gồm cả kích thước mục trong import table
    • Tôi đã làm nhiều việc như vậy, và thành thật mà nói, tôi nhớ khả năng có thể phát triển UI native bằng mã native
    • Cũng thấy dùng fopen/fread/fclose, nhưng có vẻ không dùng Win32 API như CreateFile/ReadFile/WriteFile
  • Thay vì vất vả gọi CreateWindow() cho từng control, theo truyền thống người ta sẽ bố trí tài nguyên hộp thoại trong file .rc (Visual Studio vẫn còn trình chỉnh sửa hộp thoại trực quan để làm việc này) và dùng CreateDialog() thay cho CreateWindow()
    Làm vậy sẽ tự động tạo tất cả control. Nếu thêm application manifest, bạn cũng có được kiểu UI hiện đại và hỗ trợ DPI cao

    • Cách này cũng tự động có di chuyển bằng phím Tab giữa các control và một số phím tắt bàn phím
      Tuy nhiên nếu muốn thay đổi kích thước thì vẫn phải xử lý thủ công, nhưng thường khá dễ và mã cũng không quá vài trăm byte
    • Việc thư viện chuẩn C và thư viện hệ điều hành chồng lấn thực ra gần như chỉ có ở UNIX, và vào năm 1985 khi Windows 1.0 ra mắt thì vẫn chưa có thứ gì thật sự gọi là chuẩn
      K&R C thì có, nhưng ngoài UNIX, mỗi hệ điều hành tự chọn các chức năng mình cung cấp
      Hơn nữa ở những nơi không thuộc họ UNIX, có truyền thống là thư viện chuẩn do trình biên dịch C chịu trách nhiệm, chứ không phải nền tảng
      Vì vậy thư viện C được cung cấp bởi Borland, Watcom, Symantec, Microsoft, Green Hills, Zortech, v.v.
      Mac OS trước khi có MPW cũng vậy, IBM và Unisys, máy vi tính và mainframe, VMS trước OpenVMS cũng vậy
      Từ Windows 10 trở đi cũng có Universal C Runtime
    • Cách tiếp cận này có thể dễ dàng chuyển sang các ngôn ngữ có giao diện hàm ngoại lai(FFI) tốt, và ngoài ra không cần gì khác
      Không cần resource compiler, cũng không cần linker để tạo resource DLL
      File resource và định dạng nhị phân của nó không phải là API tốt
      Nếu trong một ngôn ngữ đủ cao cấp có các lời gọi CreateWindow, có vẻ có thể dùng metaprogramming để tạo một DSL kiểu resource hòa nhập tốt vào ngôn ngữ đó
  • Trước đây tôi từng làm một thứ tương tự bằng assembly trên Linux, dưới 2KiB: https://gaultier.github.io/blog/x11_x64.html
    Như những người khác đã nói, nếu làm bằng C thuần và dùng dynamic linking, ít nhất trên Linux có thể dễ dàng giữ dưới 20KiB. Windows có nhiều thứ được tích hợp sẵn trong hệ điều hành hơn nhiều, nên có khi còn dễ hơn
    Dù sao thì tôi ủng hộ nỗ lực này. Thử các tùy chọn liên kết tôi ghi ở cuối bài của mình có thể giúp giảm kích thước

    • Một chương trình TUI TODO dựa trên ncurses được mở rộng thêm một chút có kích thước 15K. Chạy trên Linux và không liên kết tĩnh
      Tôi vẫn chưa thử build ncurses bằng musl
  • Nghe nói “look and feel Windows native”, nên trước khi chạy thật, tôi đã kỳ vọng list view có menu ngữ cảnh, và khi nhấp đúp vào một trường cụ thể thì sẽ hiện menu thả xuống hoặc hộp văn bản gần như inline
    Giờ thì không biết là người ta không còn biết cách thiết kế chương trình cho đúng với UI/UX Win32 nữa, hay chỉ là tôi đã quá già

  • Bảo là “không framework”, vậy mà ở phần DPI scaling thì phông chữ bị mờ, cũng không có hỗ trợ Tab, trong trường văn bản Ctrl-A không chọn toàn bộ được, thiếu nhiều tính năng mà cả các framework tiền hiện đại cũng có, và khi thêm hàng còn phát sinh lỗi
    Không rõ “hiện đại” ở đây nghĩa là gì

    • Ví dụ thiết lập nhận biết DPI ở đây: https://github.com/Dwedit/GameStretcher/blob/master/Stretche...
      Mã này kiểm tra động và gọi cái nào dùng được trong số user32:SetProcessDpiAwarenessContext, shcore:SetProcessDpiAwareness, user32:SetProcessDPIAware
      Nếu phiên bản Windows quá cũ nên không triển khai cái nào trong số này (trước Windows XP) thì không gọi gì cả
    • Có vẻ “hiện đại” nghĩa là lớn hơn rất nhiều so với cần thiết mà lại thiếu nhiều tính năng
      Hầu hết các tính năng bị nói là thiếu đều rất dễ thêm, đặc biệt là di chuyển bằng Tab giữa các control
  • Lập trình viên 6502 trong tôi đang chết dần trong lòng khi thấy 278KB giờ lại được xem là nhẹ

    • Tôi đã thử tái tạo binary này để xem 278KB được dùng vào đâu
      Trở ngại đầu tiên gặp phải là nếu git được cấu hình core.autocrlf=false thì file build.bat không chạy. Đổi sang core.autocrlf=true rồi clone lại thì build được
      Toolchain dùng là x86_64-15.1.0-release-win32-seh-msvcrt-rt_v12-rev0.7z từ https://github.com/niXman/mingw-builds-binaries/releases/tag...
      Trong trường hợp này tạo ra file .exe 102KB. Ngay từ đầu đã tốt hơn nhiều so với con số 278KB được nêu. Có thể tác giả dùng toolchain hoặc thiết lập khác, nên nếu có quy trình tái tạo chính xác thì tốt
      Truyền thêm vài switch cho GCC có thể giảm nữa
      gcc -Os => 100KB
      gcc -Oz => 99KB
      gcc -flto => 101KB
      gcc -s => 51KB
      gcc -s -Oz -flto => 47KB
      Nếu chỉ quan tâm đến kích thước .exe nhỏ thì vẫn còn nhiều chỗ để cải thiện
    • Một phần đáng kể là do nền tảng và định dạng file thực thi
      Nếu không có thông tin stack trace, hạ tầng liên kết động, bảng xử lý ngoại lệ, v.v. thì có thể nhẹ hơn nhiều. Bảng xử lý ngoại lệ cũng cần trong C vì ngoại lệ có thể đi xuyên qua các hàm C
    • Có lẽ cũng nên kiến nghị các cuộc thi demoscene lập hạng mục ứng dụng TODO 64KB
    • Thành thật mà nói, tôi ngạc nhiên là nó lớn đến vậy. Tôi tưởng nó sẽ nhỏ hơn, hoặc khoảng một nửa là do icon của ứng dụng chiếm
      Tôi nhớ hồi trước khi viết thứ kiểu này thì nó nhỏ hơn. Có khi nào là do MinGW không?
    • Đột nhiên nhớ lại thời lập trình Win32 bằng assembly trở nên khá “hip”
      Có lẽ đó là phản ứng trước xu hướng dung lượng tải shareware ngày càng phình to. Đó là thời kỳ đen tối của MFC
      Cùng với lập trình Palm Pilot 68k đời đầu, tôi nhớ đó như tiếng reo hò cuối cùng của assembly không thuộc retrocomputing
  • Các bạn à, đây chỉ là app làm thử cho vui thôi. Các bình luận đều đúng, những thứ như thế này có lẽ làm bằng C++ hoặc ngôn ngữ khác sẽ hợp lý hơn

    • Khoảng 30 năm trước khi lần đầu làm chương trình Windows, tôi cũng học đúng theo cách này. Chỉ là chắc tôi đã dùng compiler C++
      Không rõ vì sao, nhưng tài liệu Windows API dường như hướng dẫn viết mã kiểu C bằng compiler C++. Có vẻ Microsoft coi C++ là siêu tập cải tiến của C, và cho rằng dùng nó cho cả mã kiểu C là đúng
    • Không đùa đâu, tôi muốn dùng app này hơn app TODO mặc định của Windows 11
    • Đây đúng là cách nên làm
      Nếu dùng Win32 API thì ngôn ngữ khác về cơ bản không thay đổi được gì, thậm chí còn có thể làm rối hơn
      Người ta hay sa vào lối nghĩ kiểu C++, và với người không quen Win32 API thì có lẽ nó sẽ biến toàn bộ thành một mớ còn khó hiểu hơn
      Một thử nghiệm rất dễ thương, và bất kể người khác nghĩ gì, sự quen thuộc với Win32 API là một năng lực nền tảng ổn
    • Tham khảo https://github.com/Efeckc17/YoutubeGO, tôi sẽ rất vui nếu ứng dụng này cũng được review hoặc xem qua
    • Những dự án UI native gọn gàng như thế này chính là động lực khiến tôi học lập trình. Tuyệt
  • Ở đây thấy khá nhiều người châm biếm dù chính họ đang tạo phần mềm hoặc website tải vài MB JS hay C# chỉ để gửi 278KB telemetry mỗi khi người dùng di chuyển chuột

    • Một ứng dụng tương tự viết bằng C# + WinForms chiếm dưới 10KB trên đĩa và dùng 6MB RAM
      Ứng dụng này dùng 1,5MB RAM, và cả hai đều khởi động tức thì
  • Khi phát triển ứng dụng Windows, việc biết rằng có thể làm mọi thứ bằng C luôn khiến tôi thấy dễ chịu
    Cách xây dựng từ những thành phần nền tảng ở tầng thấp nhất cứ tự nhiên mà hợp lý
    Ngược lại, trên macOS mọi thứ đều là đối tượng Objective-C, nên dù có thể viết ứng dụng bằng C thuần, bạn gần như phải dài dòng hơn gấp 1000 lần vì thực chất đang đi xuống một tầng thấp hơn so với lớp trừu tượng mà Apple dự định. Muốn làm gì đó thì về cơ bản phải điều khiển cả cây phân cấp lớp Objective-C như con rối, cảm giác thật sự rất khó chịu
    Tôi không hiểu vì sao không thể đặt API dựa trên lớp của Objective-C lên trên một API thủ tục cơ bản kiểu Win32. Về mặt kỹ thuật Win32 cũng “dựa trên lớp”, nhưng ở mức tối thiểu
    Vì vậy trong thời gian tới tôi không mấy muốn port mã C của mình sang macOS

    • Điều thú vị là Windows có COM, và WinRT được xây trên COM, tức API/ABI hướng đối tượng riêng của nó
      Nhưng trình biên dịch MIDL cho giao diện COM tạo ra các header cho phép dùng và hiện thực giao diện trong C: https://www.codeproject.com/KB/com/com_in_c1.aspx
      Theo tôi biết thì ngày nay nó vẫn hoạt động với WinRT. Chỉ có điều tên struct và hàm C được sinh ra phải chứa toàn bộ namespace nên trông rất xấu: https://stackoverflow.com/a/7437006/111335
    • Đã từng có một API thủ tục, tên là Carbon
      Nó gần như là bản thay thế drop-in cho API Macintosh Toolbox mà user32.dll đã sao chép một cách lộ liễu
      Tuy nhiên, ngoài cảm giác ấm áp đối với lập trình viên, UI thật sự muốn hướng đối tượng. Một cây gồm các đối tượng khác nhau chia sẻ một phần hành vi chung gần như mô tả nguyên vẹn 99% mã UI
      Toolbox và Carbon thiếu tính hướng đối tượng đó nên khá gượng ép. Một ý tưởng thực sự độc đáo hơn mà Windows có chính là window class
      Cá nhân tôi, cảm giác kỳ lạ khi thao tác trực tiếp với các lớp Objective-C trong C cũng giống cảm giác tôi có mỗi lần phải định nghĩa window class hay window procedure trong mã user32.dll[0]
      Cũng như UI muốn hướng đối tượng, hướng đối tượng muốn có tính năng ngôn ngữ chuyên dụng. Không có vẫn làm được, nhưng tính khả dụng tệ hơn 2000 lần
      [0] Hoặc mọi thứ liên quan đến GTK/GObject
  • Có vẻ như đang link với thư viện tĩnh
    Không nên link với thư viện tĩnh mà phải link với DLL, như vậy kích thước ứng dụng sẽ giảm đáng kể

    • Theo tôi thì ngược lại
      Nếu dù sao cũng phải phân phối DLL kèm chương trình (vì nó không phải một phần của hệ điều hành), thì DLL chứa toàn bộ chức năng và mỗi DLL thậm chí có thể có cả C runtime riêng
      Nếu biên dịch tĩnh mọi thứ vào một EXE duy nhất thì chỉ có một C runtime, và có thể dễ dàng loại bỏ các hàm không dùng
      DLL chỉ giúp giảm kích thước khi mã đó được chia sẻ giữa nhiều chương trình
    • Không, link với phiên bản tĩnh của CRT là lựa chọn tốt. Có thể cắt bỏ mã không dùng
      Nếu link động với MSVCRxx/VCRUNTIME, người dùng phải tải đúng DLL đó từ Microsoft
      Nếu link động với MSVCRT thì không có vấn đề đó, nhưng rất khó làm trong Visual Studio
      Trường hợp thật sự không nên link tĩnh với CRT có lẽ chỉ là để tuân thủ LGPL
 
roxie 2025-05-16

Hơi thở phả ra như muốn truyền tới tận đây vậy...