Tạo ứng dụng Todo gốc cho Windows bằng C thuần (278KB, không dùng framework)
(github.com/Efeckc17)- 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
binvàoPATHcủ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
- Cài đặt MinGW-w64 và thêm thư mục
- 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ụngsrc/todo.c,src/todo.h: logic quản lý Todo và cấu trúc dữ liệusrc/gui.c: triển khai GUIsrc/utils/search.c,src/utils/search.h: triển khai và khai báo chức năng tìm kiếmsrc/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
Đúng là rất có chất lãng mạn.
Ý 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áoWin32 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 chomemset(),CopyMemory()thay chomemcpy()Đế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
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
strncpythay chostrcpy, 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ậyCá 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
ZeroMemory()thay chomemset(),CopyMemory()thay chomemcpy(), nhưng MSVC intrinsic có lẽ sẽ dùng lệnhrep stos/movsCó 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
fopen/fread/fclose, nhưng có vẻ không dùng Win32 API nhưCreateFile/ReadFile/WriteFileThay 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ùngCreateDialog()thay choCreateWindow()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
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
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
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
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ì
Mã này kiểm tra động và gọi cái nào dùng được trong số
user32:SetProcessDpiAwarenessContext,shcore:SetProcessDpiAwareness,user32:SetProcessDPIAwareNế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ả
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ẹ
Trở ngại đầu tiên gặp phải là nếu git được cấu hình
core.autocrlf=falsethì filebuild.batkhông chạy. Đổi sangcore.autocrlf=truerồi clone lại thì build đượcToolchain dùng là
x86_64-15.1.0-release-win32-seh-msvcrt-rt_v12-rev0.7ztừ https://github.com/niXman/mingw-builds-binaries/releases/tag...Trong trường hợp này tạo ra file
.exe102KB. 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ốtTruyền thêm vài switch cho GCC có thể giảm nữa
gcc -Os=> 100KBgcc -Oz=> 99KBgcc -flto=> 101KBgcc -s=> 51KBgcc -s -Oz -flto=> 47KBNếu chỉ quan tâm đến kích thước
.exenhỏ thì vẫn còn nhiều chỗ để cải thiệnNế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
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?
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
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
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
Ở đâ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
Ứ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
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
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ễuTuy 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ể
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
Nếu link động với
MSVCRxx/VCRUNTIME, người dùng phải tải đúng DLL đó từ MicrosoftNếu link động với
MSVCRTthì không có vấn đề đó, nhưng rất khó làm trong Visual StudioTrường hợp thật sự không nên link tĩnh với CRT có lẽ chỉ là để tuân thủ LGPL
Hơi thở phả ra như muốn truyền tới tận đây vậy...