10 điểm bởi GN⁺ 2025-05-12 | 3 bình luận | Chia sẻ qua WhatsApp
  • Dự án mã nguồn mở này là một ứng dụng Todo Windows native gọn nhẹ được xây dựng chỉ bằng C và Win32 API
  • Ứng dụng hoạt động với dung lượng tối thiểu (tối đa 26.5KB) không phụ thuộc vào framework, đồng thời tự triển khai trực tiếp GUI Windows nâng cao và tích hợp hệ thống
  • Ngoài các tính năng cơ bản như thêm, sửa, xóa, đánh dấu hoàn thành các mục Todo, ứng dụng còn cung cấp các tính năng năng suất thực tế như tích hợp khay hệ thống và tùy chọn tự khởi động
  • Kho lưu trữ mang tính bền vững bằng tệp nhị phân, lưu tối đa 100 danh sách việc cần làm trong thư mục AppData
  • Điểm mạnh là phong cách lập trình cổ điển bám rất sát hệ điều hành, không dùng framework lớn và có môi trường chạy cực nhẹ

🌟 Simple Todo (C / WinAPI)

Tổng quan dự án

  • Dự án này tạo ra một ứng dụng Todo Windows native hiện đại chỉ bằng C và Win32 API
  • Thể hiện năng lực lập trình GUI Windows nâng cao và tích hợp hệ thống
  • Dung lượng dự án rất nhỏ (tối đa 26.5KB) và giữ nguyên giao diện đặc trưng của Windows

✨ Tính năng chính

  • Có thể tạo, chỉnh sửa, xóa mục việc cần làm
  • Có thể đánh dấu hoàn thành công việc
  • Lưu bền vững trong AppData để dữ liệu luôn được bảo toàn
  • Tích hợp với khay hệ thống, khi thu nhỏ sẽ chuyển vào khay
  • Mang giao diện theo phong cách Windows native
  • Cung cấp tùy chọn tự chạy khi Windows khởi động

🛠️ Chi tiết kỹ thuật

  • Toàn bộ được viết bằng C thuần
  • Chỉ dùng Win32 API để triển khai GUI
  • Kích thước tệp thực thi cực nhỏ (26.5KB khi nén bằng UPX)
  • Tính năng tích hợp khay hệ thống
  • Áp dụng visual style hiện đại thông qua manifest

💾 Lưu trữ dữ liệu

  • Toàn bộ việc cần làm được lưu trong một tệp nhị phân duy nhất
  • Đường dẫn lưu: %APPDATA%\TodoApp\todos.dat
  • Định dạng nhị phân và có thể lưu tối đa 100 mục

📋 Yêu cầu bắt buộc

  • Cần môi trường hệ điều hành Windows
  • Cần MinGW-w64 (trình biên dịch GCC) và Windows SDK

🎮 Cách sử dụng

  • Chạy bin/todo.exe rồi dùng giao diện để thực hiện các thao tác sau
  • Thêm việc cần làm mới bằng nút "Add"
  • Chọn mục rồi nhấn "Edit" để chỉnh sửa
  • Xóa mục bằng "Delete"
  • Đánh dấu hoàn thành bằng "Complete"
  • Có thể đặt mức ưu tiên cho từng mục

🏗️ Cấu trúc dự án

  • Thư mục src/ chứa điểm vào chính (main.c), logic quản lý việc cần làm (todo.c), khai báo struct (todo.h), phần triển khai GUI (gui.c)
  • bin/ chứa tệp thực thi đã biên dịch
  • Bao gồm script build (build.bat) và tài liệu dự án

🔧 Thành phần phát triển

  • Win32 API: triển khai quản lý cửa sổ và toàn bộ GUI
  • Common Controls: sử dụng các thành phần UI hiện đại
  • UXTheme: hỗ trợ áp dụng visual style Windows
  • File I/O: hiện thực lưu trữ dữ liệu bền vững

📝 Giấy phép

  • Có thể tự do sử dụng và chỉnh sửa theo giấy phép MIT

🤝 Hướng dẫn đóng góp

  • Hoan nghênh Pull Request
  • Bất kỳ ai cũng có thể tham gia dự án

📫 Liên hệ và liên kết

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
  • Có vài điểm ở lập trình GUI win32 mà tôi rất thích, dù hơi khác thường, nhưng nếu đọc blog của Raymond Chen thì sẽ hiểu vì sao. Win32 API bắt đầu từ thời bộ xử lý 8088, và có những kiểu làm giúp tiết kiệm 40 byte mã hoặc bớt dùng một thanh ghi. Ngày trước tôi từng đọc sách của mingw và Petzold rồi tự viết nhiều ứng dụng GUI đơn giản, và việc tự tay làm mọi thứ như custom control, vẽ đồ họa/văn bản, xử lý cuộn, hit test, v.v. thật sự rất vui. Tôi thấy ứng dụng của bạn dùng strcpy, sprintf, nhưng nếu lập trình nghiêm túc thì nhất định phải dùng các biến thể có kiểm tra độ dài. Cũng lạ là trình biên dịch không cảnh báo ngay. Win32 API có nhiều hàm thay thế các hàm thư viện C tiêu chuẩn; nếu muốn giảm kích thước file thực thi hơn nữa thì tôi khuyên thử chỉ dùng <Windows.h> và viết mà không cần cstdlib. Có thể dùng ZeroMemory thay cho memset, CopyMemory thay cho memcpy. Tất nhiên đến một lúc nào đó viết C thuần sẽ trở nên cực kỳ đau khổ, nhưng vài lần đầu tự làm bằng C thuần là cách học tốt nhất, vì nó giúp tích lũy cảm giác về cách cấu thành khi xử lý những chi tiết nhỏ như vậy. Nếu muốn tìm hiểu thêm về lập trình GUI win32 thì tôi cũng muốn giới thiệu WTL (Windows Template Library), nó bọc Win32 API bằng C++ nên giúp nắm nguyên lý hoạt động dễ hơn nhiều
    • Giờ thì ít nhất cũng nên dùng strncpy thay cho strcpy, nếu không ai cũng sẽ cứ liên tục chỉ ra chuyện đó. Một trong những lý do lớn để dùng zig là nó làm giảm những lỗi phổ biến kiểu này. Tất nhiên C cũng ổn
    • Về ý ZeroMemory thay cho memset, CopyMemory thay cho memcpy, intrinsic của MSVC dùng lệnh rep stos/movs nên mã còn nhỏ hơn gọi hàm và cũng giảm kích thước import table
    • Tôi cũng từng làm việc này rất nhiều, và thành thật là tôi nhớ cảm giác có thể phát triển UI native bằng mã native
    • Đây là câu hỏi về lý do có ZeroMemoryCopyMemory thay cho memsetmemcpy: tại sao họ lại phải tạo những thứ này thay vì dùng luôn thư viện C chuẩn?
  • Ngày xưa thay vì cực khổ gọi CreateWindow mỗi lần, người ta thường viết tài nguyên hộp thoại bằng file .rc (Visual Studio còn có cả dialog editor) rồi dùng CreateDialog. Khi đó mọi control được tạo một lần luôn. Chỉ cần thêm application manifest là có thể hỗ trợ giao diện hiện đại và DPI độ phân giải cao
    • Dùng cách này thì hỗ trợ phím tắt bàn phím như di chuyển Tab giữa các control cũng có sẵn luôn. Tất nhiên những thứ như đổi kích thước vẫn phải tự làm, nhưng code cũng dễ mở rộng và không khó
    • Đây cũng là cách có trong sách của Petzold, nên đáng để tìm đọc
  • Tôi cũng từng làm thứ tương tự cho Linux bằng assembly dưới 2KiB. Nếu viết bằng C và liên kết động thì trên Linux rất dễ làm dưới 20KiB. Tôi nghĩ Windows còn dễ hơn vì có nhiều thứ tích hợp sẵn, nên tôi muốn ủng hộ những thử nghiệm như thế này. Nếu xem các tùy chọn liên kết ở cuối bài thì sẽ còn dễ giảm dung lượng hơn nữa
  • Làm không dùng framework thì sẽ có các vấn đề như font bị mờ khi scale DPI, không hỗ trợ Tab, thiếu hầu hết các tính năng mặc định của framework tiền hiện đại như Ctrl-A để chọn toàn bộ trong ô văn bản, lỗi khi thêm dòng, v.v. Vậy thì không rõ ở điểm nào mà cái này được gọi là "hiện đại"
    • Đính kèm ví dụ cấu hình DPI awareness: mã sẽ thử thiết lập DPI awareness bằng nhiều hàm Windows khác nhau tùy phiên bản (user32:SetProcessDpiAwarenessContext, shcore:SetProcessDpiAwareness, user32:SetProcessDPIAware), còn nếu quá cũ thì sẽ không gọi gì cả
    • Từ "hiện đại" không hợp lắm, vì nó quá to chỉ về dung lượng còn tính năng thì lại thiếu (di chuyển Tab giữa các control thì tự triển khai cũng dễ)
  • Với người từng lập trình bằng 6502 như tôi thì thực tế bây giờ 278KB cũng được xem là nhẹ thật sự thấy đau lòng
    • Vì tôi hay phân tích kích thước nhị phân, trở ngại đầu tiên là build.bat không chạy đúng khi cấu hình core.autocrlf=false. Đổi sang core.autocrlf=true rồi clone lại thì build thành công. Một toolchain mingw cụ thể tạo ra file .exe 102KB, tức hiệu quả hơn nhiều so với 278KB. Nếu muốn giảm thêm thì có thể thêm cờ cho GCC; với gcc -s -Oz -flto thậm chí còn xuống được 47KB. Nếu chỉ quan tâm đến kích thước binary thì vẫn còn rất nhiều chỗ để tối ưu
    • Mức dung lượng này là do nền tảng và định dạng file thực thi; thông tin stack trace, hạ tầng liên kết động, bảng xử lý ngoại lệ và nhiều thứ khác đều chiếm chỗ
    • Tôi muốn đề nghị demoscene mở thêm hạng mục thi "ứng dụng TODO 64KB"
    • Thành thật mà nói tôi ngạc nhiên vì nó lớn thế này; trước đây tôi nhớ còn nhỏ hơn nếu bỏ phần icon. Không biết có phải do MinGw không
    • 6502 à? Thế còn sang chán, thời của tôi nhiều khi còn chẳng có CPU
    • Điều này làm tôi nhớ đến thời lập trình assembly win32 đột nhiên thành mốt, đặc biệt khi dung lượng tải shareware bắt đầu tăng lên. Cũng gợi nhớ thời lập trình Palm Pilot 68k đầu tiên, như ngọn lửa cuối cùng của assembly không-hoài-cổ
    • Có người còn làm quickrun.exe chỉ 15KB, chỉ dùng C và Win32 API thuần. Không có mẹo gì đặc biệt để giảm binary, dùng trình biên dịch Mingw32, đây là ứng dụng GUI để chạy app nhanh bằng alias
    • Tối nay tôi đang debug trình giả lập của mình, nó mô phỏng một hệ Z80 với 64KB RAM. Thỉnh thoảng những lúc như vậy mới thật sự thấy thời thế và môi trường đã thay đổi nhiều đến mức nào, dù vậy cũng phải thừa nhận là đi kèm với việc tăng kích thước là những tiến bộ rất lớn
    • Chỉ riêng con trỏ địa chỉ từ kiến trúc 8-bit lên 64-bit đã tăng gấp 8 lần rồi; đừng than phiền nữa mà hãy xem bản thân sự thay đổi này như một tác phẩm nghệ thuật
    • 278KB là dung lượng chỉ vừa đủ nhét vào đĩa mềm 5 1/4 inch
  • Ứng dụng này chỉ là thứ tôi làm thử cho vui. Như mọi người chỉ ra trong bình luận, hợp lý hơn thì có thể dùng C++ hay ngôn ngữ khác, nhưng với tôi đơn giản là làm vậy thấy vui
    • Khoảng 30 năm trước tôi cũng gần như đã làm chương trình Windows đầu tiên của mình theo đúng kiểu này. Khác biệt là tôi dùng trình biên dịch C++. Thời đó cách được tài liệu chính thức hướng dẫn là viết kiểu C nhưng biên dịch bằng C++, vì C++ là siêu tập của C nên Microsoft cũng có xu hướng như vậy
    • Thành thật mà nói tôi muốn dùng ứng dụng của bạn hơn nhiều so với app to-do mặc định của Windows 11
    • Khi dùng Win32 API thì bất kể dùng ngôn ngữ nào, bản chất cũng không khác nhiều. Thậm chí đổi ngôn ngữ còn có thể làm rối hơn. Nếu quá bám vào phong cách C++ thì với người mới học Win32 API lại càng dễ thêm bối rối. Việc làm một dự án cá nhân đơn giản/dễ thương kiểu này để quen với Win32 API, theo tôi, là một phần căn bản trong nền tảng của lập trình viên
    • Tôi còn có một ứng dụng khác tên là YoutubeGO, nếu bạn xem thử thì tôi sẽ rất vui
    • Những dự án giao diện native gọn gàng kiểu này cũng chính là động lực khiến tôi học lập trình, nên tôi rất đồng cảm và muốn khen
  • Trong bối cảnh web hay phần mềm bây giờ thường tải cả đống JS hoặc C# cỡ hàng megabyte chỉ để gửi 278KB telemetry, những thử nghiệm như thế này thấy rất mới mẻ
    • Một ứng dụng tương tự làm bằng C# + WinForms có thể chiếm dưới 10KB trên đĩa và chỉ dùng 6MB RAM; ứng dụng này dùng 1.5MB RAM, cả hai đều mở lên gần như ngay lập tức
  • Nhìn vào thì có vẻ như họ đã liên kết thư viện tĩnh; nếu liên kết bằng DLL thì có thể giảm kích thước ứng dụng đi rất mạnh
    • Thật ra gần như ngược lại. Nếu bạn buộc phải phân phối DLL kèm theo chương trình (tức DLL đó không có sẵn trong OS) thì mỗi DLL lại mang theo runtime C riêng của nó nên còn phình hơn. Nếu nhét tất cả tĩnh vào một EXE thì chỉ có một bản C runtime được đưa vào, và các hàm không dùng đến cũng dễ bị loại bỏ. DLL chỉ giúp giảm dung lượng khi có nhiều chương trình cùng dùng chung một DLL
    • Liên kết tĩnh CRT (runtime library) thực ra còn có lợi trong việc loại bỏ mã thừa. Nếu liên kết động DLL thì đôi khi còn phải bắt người dùng cài riêng VCRUNTIME DLL từ Microsoft. Trong Visual Studio, liên kết động với MSVCRT cũng không hẳn dễ, dù có ngoại lệ khi cần tuân thủ LGPL
  • Tôi nhớ đến File Pilot, trình khám phá tệp nhanh mới ra gần đây, viết bằng C và chỉ nặng 1.8MB
  • Bài viết gọi đây là ứng dụng Todo Windows "hiện đại", "native", nhưng thật sự khó hiểu điểm nào là hiện đại. Nếu viết bằng C++ thì còn tránh được nhiều vấn đề và bỏ được biến toàn cục. Dùng std::string, std::array, std::list, anonymous namespace và bỏ malloc đi thì có lẽ bạn sẽ thấy code ngắn đi một nửa và bug cũng ít hơn
    • Với một ứng dụng chỉ khoảng 500 dòng thì biến toàn cục chẳng ảnh hưởng mấy, mà mục đích của chúng cũng rất rõ. Việc đổi sang std::string, std::list không có nghĩa assembly sinh ra sẽ giống nhau; ý kiến đó cho thấy là chưa thật sự hiểu cách mọi thứ hoạt động bên dưới
    • Ngay cả sách Petzold ở bản mới cũng build bằng Visual C++ ở chế độ C++ và khuyến nghị dùng cú pháp chung giữa C/C++. Từ thời Windows 95 đã hiếm ai còn viết gần như hoàn toàn bằng C; ngôn ngữ chủ đạo lúc đó đã chuyển sang VB, Delphi và C++ rồi
    • Về ý nên dùng string hay array/list chuẩn, nếu đã dùng trực tiếp winapi thì dùng LPWSTR (wide string) sẽ hợp với API hơn std::string và cũng đáng khuyến nghị hơn. So với cách cũ như char[] thì LPWSTR hợp lý hơn, còn std::array hay list có lẽ cũng không làm code tốt hơn bao nhiêu
    • Không phải là hiện đại, nhưng kiểu lập trình gần với nền tảng như vậy giúp hiểu rõ cơ chế gốc hoạt động thế nào. Bài toán cũng đơn giản nên dễ nắm bắt. Tôi nghĩ đây là một dự án học tập tốt. Tôi cũng tò mò muốn xem những ứng dụng kiểu này được viết bằng assembly sẽ ra sao
 
roxie 2025-05-16

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