Bài giảng Lập trình Windows

pdf 96 trang phuongnguyen 7780
Bạn đang xem 20 trang mẫu của tài liệu "Bài giảng Lập trình Windows", để tải tài liệu gốc về máy bạn click vào nút DOWNLOAD ở trên

Tài liệu đính kèm:

  • pdfbai_giang_lap_trinh_windows.pdf

Nội dung text: Bài giảng Lập trình Windows

  1. BỘ GIAO THÔNG VẬN TẢI TRƢỜNG ĐẠI HỌC HÀNG HẢI BỘ MÔN: KHOA HOC̣ MÁ Y TÍNH KHOA: CÔNG NGHỆ THÔNG TIN BÀI GIẢNG LẬP TRÌNH WINDOWS TÊN HỌC PHẦN : Lập trình Windows MÃ HỌC PHẦN : 17214 TRÌNH ĐỘ ĐÀO TẠO : ĐẠI HỌC CHÍNH QUY DÙNG CHO SV NGÀNH : CÔNG NGHỆ THÔNG TIN HẢI PHÕNG - 2010
  2. Bài giảng môn học: Lâp̣ triǹ h Windows Tên học phần: Lập trình Windows Loại học phần: 2 Bộ môn phụ trách giảng dạy: Khoa học Máy tính Khoa phụ trách: CNTT Mã học phần: 17214 Tổng số TC: 3 TS tiết Lý thuyết Thực hành/Xemina Tự học Bài tập lớn Đồ án môn học 60 30 30 0 0 0 Điều kiện tiên quyết: Sinh viên phải học xong các học phần sau mới đƣợc đăng ký học phần này: Lâp̣ trình hƣớ ng đối tƣơṇ g, Cấu trúc dƣ̃ liêụ Mục tiêu của học phần: - Cung cấp các kiến thức cơ bản về lâp̣ trình trƣc̣ quan trên hê ̣điều hành Windows - Cung cấp các kiến thƣ́ c về truy câp̣ và can thiêp̣ vào các t hành phần của hệ điều hành Windows Nội dung chủ yếu Các kiến thức về thao tác với file và thƣ mục , cơ sở dƣ̃ liêụ registry , các luồng, tiến trình, dịch vụ, các thƣ viện liên kết động và lập trình sockets trên Windows. Nội dung chi tiết của học phần: PHÂN PHỐI SỐ TIẾT TÊN CHƢƠNG MỤC TS LT TH/Xemina BT KT Chƣơng I. Các khái niệm cơ bản 3 0 0 0 1.1. Giới thiệu về môi trƣờ ng lâp̣ trình trên 1 Windows 1.1.1. Cở sở về hê ̣điều hành Windows 1.1.2. Các phiên bản của hê ̣điều hành Windows 1 1.1.3. Vai trò của Windows trên thi ̣trƣờ ng phần mềm 1.2. Thƣ viêṇ Win32 và Win64 1 1.2.1. Win32 API 1.2.2. Win64 API 1.3. Giớ i thiêụ về bô ̣công cu ̣Visual Studio 2005 Chƣơng II. Hê ̣thố ng file và thƣ mục 4 4 0 2.1. Truy câp̣ và sƣ̉ duṇ g hê ̣thống file trên môi 2 2 trƣờ ng Windows 2.1.1. Hê ̣thống file và thƣ muc̣ của Windows 2.1.2. Các thao tác với file và thƣ mục trên Windows 2.1.3. Các vấn đề liên quan tới Unicode 1 1 2.2. Các ví dụ về thao tác vớ i file 2.2.1. Tạo file và xử lý các lỗi liên quan i
  3. Bài giảng môn học: Lâp̣ triǹ h Windows PHÂN PHỐI SỐ TIẾT TÊN CHƢƠNG MỤC TS LT TH/Xemina BT KT 2.2.2. Copy file 2.2.3. Hiển thi ̣danh sách các file trong thƣ muc̣ 1 1 hiêṇ thờ i 2.3. Quản lý file và thƣ mục nâng cao 2.3.1. Con trỏ file 2.3.2. Truy câp̣ tớ i các thuôc̣ tính của file và thƣ muc̣ Chƣơng III. Hê ̣thố ng cơ sở dƣ̃ liêụ Registry 4 6 0 1 3.1. Khái niệm và vai trò của CSDL Registry 1 1 3.1.1. Các khóa, các hive 3.1.2. Các kiểu dữ liệu 3.2. Quản lý CSDL Registry 1 2 3.2.1. Thay đổi khóa 3.2.2. Thêm mớ i khóa 3.2.3. Liêṭ kê các khóa 3.3. Can thiêp̣ Windows qua Registry 2 3 3.3.1. Thay đổi giao diêṇ 3.3.2. Thay đổi các thiết lâp̣ đối vớ i các ổ điã 3.3.3. Thay đổi các thiết lâp̣ vớ i ngƣờ i dùng 1 Chƣơng IV. Quản lý các tiến trình và luồng 4 6 0 1 4.1. Các tiến trình và luồng trên Windows 2 2 4.2. Các thao tác với tiến trình 4.2.1. Tạo tiến trình 4.2.2. Kết thúc và thoát khỏi môṭ tiến trình 4.2.3. Các thao tác với biến môi trƣờng của Windows 4.2.4. Ví dụ : Ghi nhâṭ ký thờ i gian thƣc̣ hiêṇ của các tiến trình 1 2 4.3. Quản lý luồng (thread) trên Windows 4.3.1. Các khái niệm cơ bản 4.3.2. Mô hình Boss /Worker và các mô hình khác 4.3.3. Bô ̣nhớ dành cho luồng 1 2 4.3.4. Độ ƣu tiên và các trạng thái của luồng 4.4. Môṭ số ví du ̣về tiến trình và luồng 1 4.4.1. Tìm kiếm song song với các tiến trình 4.4.2. Thuâṭ toán sắp xếp trôṇ bằng đa luồng Chƣơng V. Các dịch vụ của Windows 4 6 0 1 5.1. Tổng quan về dic̣ h vu ̣trên Windows 1 2 5.2. Các thành phần của một dịch vụ 5.2.1. Hàm main() 5.2.2. Hàm ServiceMain() 5.2.3. Kiểm soát dic̣ h vu ̣qua các Handler 5.3. Ví du: dịch vụ đơn giản trên Windows 1 2 ii
  4. Bài giảng môn học: Lâp̣ triǹ h Windows PHÂN PHỐI SỐ TIẾT TÊN CHƢƠNG MỤC TS LT TH/Xemina BT KT 5.4. Quản lý các dịch vụ của Windows 2 2 5.4.1. Các phƣơng pháp kiểm soát các dịch vụ của Windows 5.4.2. Ví dụ : Điều khiển các dic̣ h vu ̣của 1 Windows Chƣơng VI. Lâp̣ triǹ h maṇ g vớ i Sockets 4 4 0 0 6.1. Khái niệm sockets trên Windows 0,5 1 6.2. Các hàm sockets phía server 0,5 0,5 6.3. Các hàm sockets phía client 0,5 0,5 6.4. Ứng dụng mạng đơn giản 2 2 6.4.1. Phía server 6.4.2. Phía client 6.5. Windows Sockets 2.0 0,5 Chƣơng VII. Thƣ viêṇ liên kết đôṇ g 4 4 0 0 7.1. Khái niệm và ứng dụng của thƣ viện liên 1 0,5 kết đôṇ g 1 1 7.2. Hê ̣thống thƣ viêṇ DLL của Windows 2 2,5 7.3. Các bƣớc tạo một thƣ viện DLL 7.3.1. Tạo thƣ viêṇ DLL 7.3.2. Viết ƣ́ ng duṇ g goị tớ i thƣ viêṇ DLL Nhiệm vụ của sinh viên : Tham dự các buổi thuyết trình của giáo viên, tự học, tự làm bài tập do giáo viên giao, tham dự các bài kiểm tra định kỳ và cuối kỳ. Tài liệu học tập : - Lê Hƣ̃u Đaṭ . Lâp̣ trình Windows. NXB Giáo duc̣ . - Charles Petzold. Programming Windows, fifth edition. Microsoft Press. 1998. - Johnson M. Hart. Windows System Programming Third Edition. Addison Wesley Professional. 2004. Hình thức và tiêu chuẩn đánh giá sinh viên: - Hình thức thi cuối kỳ : Thi vấn đáp. - Sinh viên phải đảm bảo các điều kiện theo Quy chế của Nhà trƣờng và của Bộ Thang điểm: Thang điểm chữ A, B, C, D, F Điểm đánh giá học phần: Z = 0,3X + 0,7Y. iii
  5. Bài giảng môn học: Lâp̣ triǹ h Windows MỤC LỤC LỜI NÓI ĐẦU 1 CHƢƠNG 1: CÁC KHÁI NIỆM CƠ BẢN 2 1. Giới thiệu về môi trƣờng lập trình Windows 2 1.1 Cơ sở về hệ điều hành Windows 2 1.2 Các phiên bản của hệ điều hành Windows 2 1.3 Vai trò của Windows trên thị trƣờng phần mềm 3 2. Thƣ viện Win32 và Win64 3 2.1 Win32 API 3 2.2 Win64 API 4 3. Các bƣớc phát triển ứng dụng trên Windows 4 3.1 Chƣơng trình Win32 đơn giản nhất. 4 3.2 Chƣơng trình cƣ̉ a sổ đơn giản 5 3.3 Quản lý các thông điệp 14 3.4 Vòng lặp xử lý thông điệp 17 Bài tập: 20 CHƢƠNG 2: HÊ ̣ THỐ NG FILE VÀ THƢ MUC̣ 21 1. Truy câp̣ và sƣ̉ duṇ g hê ̣thống file trên môi trƣờ ng Windows 21 2. Các ví dụ về thao tác với file 21 2.1 Serialization 21 2.2 Cài đặt một lớp Serializable 24 3. Quản lý file và thƣ mục nâng cao 40 Bài tập: 40 CHƢƠNG 3: HÊ ̣ THỐ NG CSDL REGISTRY 41 1. Khái niệm và vai trò của CSDL Registry 41 1.1 Các khóa, các hive 41 1.2 Các kiểu dữ liệu 42 2. Quản lý CSDL Registry 43 2.1 Thay đổi khóa 43 2.2 Thêm mớ i khóa 43 2.3 Liêṭ kê các khóa 44 3. Can thiêp̣ Windows qua Registry 44 3.1 Thay đổi giao diêṇ 44 3.2 Thay đổi các thiết lâp̣ đối vớ i các ổ điã 44 i
  6. Bài giảng môn học: Lâp̣ triǹ h Windows 3.3 Thay đổi các thiết lâp̣ vớ i ngƣờ i dùng 44 Bài tập: 44 CHƢƠNG 4: QUẢN LÝ CÁC TIẾN TRÌNH VÀ LUỒNG 45 1. Các tiến trình và luồng trên Windows 45 2. Các thao tác với tiến trình 46 2.1. Tạo tiến trình 46 2.2. Kết thúc và thoát khỏi một tiến trình 47 2.3. Các thao tác với biến môi trƣờng của Windows 47 2.4. Ví dụ: Ghi nhâṭ ký thờ i gian thƣc̣ hiêṇ của các tiến trình 47 3. Quản lý luồng (thread) trên Windows 49 3.1. Các khái niệm cơ bản 49 3.2. Mô hình Boss/Worker và các mô hình khác 49 3.3. Bô ̣nhớ dành cho luồng 49 3.4. Độ ƣu tiên và các trạng thái của luồng 50 4. Môṭ số ví du ̣về tiến trình và luồng 50 4.1. Tìm kiếm song song với các tiến trình 50 4.2. Thuâṭ toán sắp xếp trôṇ bằng đa luồng 52 Bài tập: 55 CHƢƠNG 5: CÁC DỊCH VỤ CỦA WINDOWS 56 1. Tổng quan về dic̣ h vu ̣trên Windows 56 2. Các thành phần của một dịch vụ 56 2.1 Hàm main() 56 2.2 Hàm ServiceMain() 56 2.3 Kiểm soát dịch vụ qua các Handler 56 3. Ví du: dịch vụ đơn giản trên Windows 57 4. Quản lý các dịch vụ của Windows 60 4.1 Các phƣơng pháp kiểm soát các dịch vụ của Windows 60 4.2 Ví dụ : Điều khiển các dic̣ h vu ̣của Windows 60 Bài tập: 64 CHƢƠNG 6: LÂP̣ TRÌNH SOCKET 65 1. Khái niệm sockets trên Windows 65 2. Các hàm sockets phía server 65 3. Các hàm sockets phía client 66 4. Ứng dụng mang đơn giản 66 ii
  7. Bài giảng môn học: Lâp̣ triǹ h Windows 4.1 Phía server 66 4.2 Phía client 72 5. Windows Sockets 2.0 74 Bài tập: 74 CHƢƠNG 7: THƢ VIÊṆ LIÊN KẾ T ĐỘNG 75 7.1. Khái niệm và ứng dụng của thƣ viện liên kết động 75 7.2. Hệ thống thƣ viện liên kết động của Windows 75 7.3. Các bƣớc tạo một thƣ viện DLL 76 7.4. Chia sẻ bô ̣nhớ giƣ̃a các thƣ viêṇ liên kết đôṇ g 83 7.5. Các vấn đề khác về thƣ viện liên kết động 84 Bài tập: 85 TÀI LIỆU THAM KHẢO 86 ĐỀ THI THAM KHẢO 87 iii
  8. Bài giảng môn học: Lâp̣ triǹ h Windows LỜI NÓI ĐẦU Hệ điều hành Windows của Microsoft là hệ điều hành đƣợc cài đặt nhiều nhất trên các máy PC hiện nay. Sự phổ biến của Windows và nền tảng phần cứng của Intel dẫn tới sự cần thiết phải có những hiểu biết sâu về chúng, đặc biệt đối với những lập trình viên. Mục đích của học phần này là cung cấp cho học viên một cái nhìn tổng quan, từ cơ bản tới chi tiết về các khía cạnh của lập trình trên hệ điều hành Windows, từ các chi tiết trong cấu trúc của một chƣơng trình tới các khái niệm cấp cao về tiến trình, luồng, xử lý song song, thƣ viện DLL, lập trình Socket, can thiệp vào cơ sở dữ liệu Registry Tài liệu này dựa trên những kinh nghiệm và nghiên cứu mà tác giả đã đúc rút, thu thập trong quá trình giảng dạy môn học Lập trình C trên Windows, cùng với sự tham khảo của các tài liệu của các đồng nghiệp , các tác giả trong và ngoài nƣớc , tƣ̀ điển trƣc̣ tuyến Wikipedia . Với bẩy chƣơng đƣợc chia thành các chủ đề khác nhau từ các khái niêṃ cơ bản cho tới các thao tác với hệ thống file, thƣ mục, hệ thống CSDL Registry, quản lý tiến trình và luồng, lập trình quản lý dịch vụ, lập trình socket, thƣ viện liên kết động DLL hy vọng sẽ cung cấp cho các em sinh viên, các bạn độc giả một tài liệu bổ ích . Mặc dù đã rất cố gắng song vẫn không tránh khỏi một số thiếu sót , hy vọng sẽ đƣợc các bạn bè đồng nghiệp , các em sinh viên , các bạn độc giả góp ý chân thành để tôi có thể hoàn thiện hơn nữa tài liệu này. Xin gửi lời cảm ơn chân thành tới các bạn bè đồng nghiệp và Ban chủ nhiệm khoa Công nghệ Thông tin đã tạo điều kiện giúp đỡ để tài liệu này có thểhoàn thành. Hải phòng, tháng 06 năm 2010 Tác giả Nguyêñ Hƣ̃u Tuân 1
  9. Bài giảng môn học: Lâp̣ triǹ h Windows Chƣơng 1: Các khái niệm cơ bản Tài liệu này đƣợc biên soạn để cung cấp cho ngƣời hoc̣ những kiến thức cơ bản vế việc viết các chƣơng trình sử dụng giao diện lập trình API trên môi trƣờng Win32. Ngôn ngữ đƣợc sử dụng là ngôn ngữ C, hầu hết các trình biên dịch C++ hiện nay đều có thể dịch đƣợc các chƣơng trình mẫu trình bày trong tài liệu này. Hầu hết tất cả các thông tin đƣợc trình bày trong tài liệu này đều có thể ứng dụng cho bất cứ ngôn ngữ nào có thể truy cập các hàm API, chẳng hạn nhƣ Java, Assembly, và Visual Basic. Tài liệu này đƣợc biên soạn không phải để dạy các bạn độc giả lập trình bằng ngôn ngữ C, hoặc dạy chúng ta sử dụng bất cứ một trình biên dịch cụ thể nào (chẳng hạnnhƣ Borland C++, Visual C++, ) tuy nhiên trong phần phụ lục tôi sẽ dành một chút để cung cấp cho các bạn một số chú ý về một số trình biên dịch mà tôi đã sử dụng. 1. Giới thiệu về môi trƣờng lập trình Windows 1.1 Cơ sở về hệ điều hành Windows Hệ điều hành Windows là một hệ điều hành dành chongƣời dùng cuối (End User Operating System) với các tính năng cơ bản sau: đa nhiệm, giao diện đồ họa, plug and play và quan trọng nhất là Windows Interface Based - tức là giao diện các chƣơng trình chạy trên Windows đều có dạng các cửa sổ. 1.2 Các phiên bản của hệ điều hành Windows Do cách dùng tiếng Anh và việc hiểu tiếng Anh dẫn tới việc nhiều ngƣời hiểu về các phiên bản của hệ điều hành Windows chƣa chính xác. Ví dụ có bạn cho rằng Windows XP Professional Edition và Windows XP Home Edition là hai phiên bản khác nhau của hệ điều hành Windows. Thực ra nhƣ vậy vừa đúng lại vừa không đúng, đúng là Windows XP Professional Edition và Windows XP Home Edition là hai Edition khác nhau của cùng 1 Version Windows XP, có lẽ sai là vì hiểu từ Edition và Version sai. Version có nghĩa là một phiên bản, thƣờng đi kèm với các số hiệu của phiên bản (1.0, 1.2. 5.0) và thƣờng là một thay đổi lớn đối với bản thân phần mềm, ví dụ nhƣ đối với Windows thì có 3 thay đổi lớn: thay đổi về kiến trúc nền tảng của hệ điều hành (tức là phần kernel của hệ điều hành), hai là cập nhật các bản vá (patch) cho các lỗi của phiên bản trƣớc đó đối với tất cả các phần của hệ điều hành, ba là các phần mới của hệ điều hành (có thể là các ứng dụng đi kèm hoặc hỗ trợ thêm các công nghệ mới, ví dụ nhƣ đối với Windows là chuẩn Wi-Fi, DVD, dot NET framework hay các ứng dụng nhƣ Windows Media Player, IE ). Còn Edition là ấn bản khác nhau của cùng một phiên bản, các Edition thƣờng gắn với các yếu tố về địa lý, ngôn ngữ khác nhau (ví dụ nhƣ Compact Edition nghĩa là bản rút gọn, Standard Edition là bản chuẩn, Ultimate Edition là bản có các tính năng cao cấp nhất ). Đối với hệ điều hành Windows các Edition khác nhau thƣờng phân biệt bởi các tính năng của chúng, do nhắm tới việc phục vụ các đối tƣợng khác nhau nên Microsoft bỏ đi một số tính năng không cần thiết và tăng thêm các tính năng mà đối tƣợng ngƣời dùng hay dùng ví dụ nhƣ bản Home Edition nhắm tới ngƣời dùng gia đình nên các tính năng đồ họa, video, âm thanh phải tốt, còn bản Professional nhắm tới các ngƣời dùng chuyên nghiệp có trình độ cao nên các tính năng hệ thống sẽ cao hơn. Windows có các phiên bản sau đây: Windows 1.01 Windows 2.03 2
  10. Bài giảng môn học: Lâp̣ triǹ h Windows Windows 2.11 Windows 3.0 Windows 3.1x Windows For Workgroups 3.1 Windows NT 3.1 Windows For Workgroups 3.11 Windows 3.2 (released in Simplified Chinese only) Windows NT 3.5 Windows NT 3.51 Windows 95 Windows NT 4.0 Windows 98 Windows 98 SE Windows 2000 Windows Me Windows XP Windows XP 64-bit Edition 2003 Windows Server 2003 Windows XP Professional x64 Edition Windows Fundamentals for Legacy PCs Windows Vista Windows Home Server Windows Server 2008 Windows 7 Tất nhiên là mỗi Version trên lại có nhiều Edition khác nhau. Phần nhân (Kernel – Core) của hệ điều hành luôn là phần quan trọng nhất của một hệ điều hành. Đối với Windows nhân gồm 3 thành phần: các dịch vụ chạy ở mức nhân (kernel-mode service, để phân biệt với các dịch vụ chạy ở mức ứng dụng) gồm các thƣ viện chính của hệ điều hành, các thƣ viện thực hiện quản lý tiến trình, lập lịch, quản lý vào ra dữ liệu trên đĩa cứng, bộ nhớ. Phần 2 là các thƣ viện làm việc với các phần cứng ở mức chung, phần 3 là các Diver. 1.3 Vai trò của Windows trên thị trƣờng phần mềm Do sự thống trị của hãng Microsoft nói riêng và sự phổ biến gần nhƣ độc tôn của hệ điều hành Windows nói chung ở Việt Nam nên Windows đóng vai trò hết sức quan trọng trong việc phát triển phần mềm ở Việt Nam. Về bản chất các chƣơng trình đều phải thực hiện trên một nền tảng (platform) nhất định bao gồm các chi tiết từ phần cứng cho tới phần mềm, tuy nhiên đối với đa số ứng dụng, các lập trình viên cần quan tâm nhiều nhất tới hệ điều hành mà ứng dụng sẽ chạy. 2. Thƣ viện Win32 và Win64 2.1 Win32 API Win32 API hay thƣờng đƣợc viết tắt là Win32 là phiên bản 32 bit tƣơng ứng với hệ điều hành 32 bit của Windows. Win32 bao gồm các hàm đƣợc cài đặt của hệ thống, chẳng hạn nhƣ các hàm trong hệ thống Win16 bit, dƣới dạng các file thƣ viện DLL của hệ thống. Lõi (core) của Win32 là các file thƣ viện kernel32.dll, user32.dll và gdi32.dll. Win32 đầu tiên đƣợc đƣa ra cùng với hệ điều hành Windows NT. Phiên bản đầu tiên của Win32 đƣợc phát hành cùng với hệ điều hành Windows 95 (gọi là Win32c - compatible), và sau này chỉ còn là Win32. 3
  11. Bài giảng môn học: Lâp̣ triǹ h Windows 2.2 Win64 API Win64 là phiên bản 64 bit của thƣ viện Win32, là một biến thể dành cho các nền tảng 64 bit của hệ điều hành Windows (ví dụ hiện tại là AMD64 và IA-64). Cả hai phiên bản 32 và 64 bit của một ứng dụng đều có thể đƣợc biên dịch từ một mã chƣơng trình chung, mặc dùcó một số khác biệt trong hai phiên bản này. Sự khác nhau cơ bản của hai phiên bản là các con trỏ địa chỉ của Win32 là 32 bit, còn với Win64 là 64 bit. 3. Các bƣớc phát triển ứng dụng trên Windows 3.1 Chƣơng trình Win32 đơn giản nhất. Nếu bạn là một ngƣời mới bắt đầu lập trình trên môi trƣờng Win32 thì thao tác đầu tiên mà bạn cần phải làm đƣợc đó là dịch một chƣơng trình cơ bản trên môi trƣờng Windows. Hãy gõ đoạn chƣơng trình trên vào và nếu mọi thứ suôn sẻ bạn sẽ có một chƣơng trình đơn giản nhất (vô nghĩa) trong số các chƣơng trình mà bạn đã làm. Hãy nhớ là dịch chƣơng trình này theo kiểu dịch một chƣơng trình viết bằng ngôn ngữ C, không phải ngôn ngữ C++. Trong hầu hết các trƣờng hợp điều này đơn giản thực hiện bằng cách đổi phần tên mở rộng từ cpp thành c. Chẳng hạn bạn có thể đặt tên cho file chƣơng trình sau là test.c và bắt đầu làm việc. #include int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { MessageBox(NULL, "Goodbye, cruel world!", "Note", MB_OK); return 0; } Nếu nhƣ chƣơng trình không làm việc bƣớc đầu tiên mà bạn cần làm là đọc tất cả các lỗi (error) mà chƣơng trình báo lên và nếu nhƣ bạn không hiểu ý nghĩa của chúng hãy tra trong bất cứ một quyển sách dạy lập trình hoặc các sách hƣớng dẫn đi kèm với trình biên dịch mà bạn đang sử dụng. Hãy chắc chắn là bạn có đầy đủ các file mà trình biên dịch yêu cầu. Thật không may là tôi không thể giúp nhiều trong trƣờng hợp này vì các lỗi phụ thuộc vào trình biên dịch và ngƣời sử dụng (trong việc gây lỗi cũng nhƣ sửa lỗi). Bạn có thể nhận đƣợc một vài cảnh báo từ trình biên dịch về việc chƣơng trình không sử dụng các tham số đƣợc cung cấp cùng với hàm WinMain(). Nhƣng không sao điều quan trọng là bạn dịch chƣơng trình thành công và có một chƣơng trình Win32 thực sự. Chúng ta sẽ phân tích các đoạn mã chƣơng trình kỹ càng hơn. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) WinMain() là hàm chính của một chƣơng trình trên môi trƣờng Windows giống nhƣ hàm main trên môi trƣờng DOS và UNIX. Đó là nơi các đoạn mã chƣơng trình sẽ đƣợc thực thi. Các tham số của hàm main gồm có: HINSTANCE hInstance 4
  12. Bài giảng môn học: Lâp̣ triǹ h Windows Tham số này quản lý module chƣơng trình (file .exe trong bộ nhớ). Hệ điều hành sử dụng tham số này để quản lý chƣơng trình khi thực hiện. HINSTANCE hPrevInstance Luôn là NULL đối với các chƣơng trình Win32. LPSTR lpCmdLine Các tham số dòng lệnh của chƣơng trình đƣợc truyền dƣới dạng một xâu. Không bao gồm tên chƣơng trình. int nCmdShow Một số nguyên có thể truyền cho hàm ShowWindow(). Chúng ta sẽ bàn về tham số này sau. Tham số hInstance đƣợc dùng cho các công việc đại loại nhƣ nạp (load) các tài nguyên và bất cứ tác vụ nào khác đƣợc thực hiện theo kiểu từng module cơ bản. Một module là một file EXE hoặc file DLL đƣợc nạp vào chƣơng trình của bạn. Đối với hầu hết (không phải tất cả) các chƣơng trình đƣợc trình bày trong tài liệu này, chỉ có một module mà chúng ta cần quan tâm đó là file chƣơng trình (EXE). Các qui ƣớc gọi hàm WINAPI chỉ định qui ƣớc gọi hàm và đƣợc định nghĩa là _stdcall. Nếu nhƣ bạn không hiểu ý nghĩa của chúng thì cũng không cần bận tâm nhiều vì chúng không ảnh hƣởng tới tài liệu này chỉ cần nhớ là đây là một điều cần thiết (kiểu của hàm WinMain() là int, nhƣng kiểu gọi hàm là WINAPI). Các kiểu dữ liệu Win32. Các bạn sẽ thấy rằng rất nhiều từ khóa thông thƣờng hoặc các kiểu dữ liệu đều có các cách định nghĩa cụ thể trên môi trƣờng Windows, UINT là unsigned int, LPSTR là char *, vân vân. Việc chọn lựa sử dụng cách nào thực sự phụ thuộc vào bạn. Nếu bạn thích sử dụng char * hơn là LPSTR thì hãy sử dụng cách đó nhƣng cần phải kiểm soát đƣợc việc sử dụng của mình (cần nắm rõ các kiểu dữ liệu trƣớc khi thay chúng bằng một kiểu khác). Bạn chỉ cần nắm vững một số nguyên tắc và sẽ hiểu ý nghĩa của các tên này: LP là long pointer. Trên môi trƣờng Win32 phần long này không quan trọng và hoàn toàn có thể bỏ qua. Còn nếu bạn không hiểu con trỏ là gì thì có lẽ bạn nên học lại về C hoặc bỏ qua chúng vàtiếp tục. Tiếp đến là LPCSTR: long pointer const là một hằng xâu, tức là một xâu không thể thay đối, ngoài ra giữa hai kiểu LPSTR và LPCSTR còn có thể có thêm một chữ T nhƣng nếu bạn không có ý định sử dụng các font Unicode trong chƣơng trình thì nó cũng chẳng có ý nghĩa gì đặc biệt. 3.2 Chƣơng triǹ h cƣ̉ a sổ đơn giản Ví dụ: chƣơng trình gồm 1 cƣ̉ a sổ đơn giản trên Windows Đôi khi moị ngƣờ i hỏi nhau trong khi ho ̣chát vớ i nhau bằng phần mềm IRC là “Làm thế nào để taọ ra môṭ cƣ̉ a sổ ?”. Tôi e rằng điều này hoàn toàn không phải là môṭ điều đơn giản. Không khó khi baṇ biết mình đang làm gì nhƣng có khá ít nhƣ̃ng điều baṇ cần biết để tạo ra một cửa sổ , và những điều đó hoàn toàn có thể giải thích bằng một đoạn văn bản ngắn hoăc̣ khoảng môṭ vài phút chat bằng IRC. 5
  13. Bài giảng môn học: Lâp̣ triǹ h Windows Tôi luôn muốn làm trƣớ c và hoc̣ kỹ càng sa u vì thế ở đây tôi se ̃ trình bày đoaṇ ma ̃ chƣơng trình taọ ra môṭ cƣ̉ a sổ trƣớ c và giải thích sau: #include const char g_szClassName[] = "myWindowClass"; /* tên lớp cửa sổ */ // Step 4: the Window Procedure LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_CLOSE: DestroyWindow(hwnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: // để windows xử lý các thông điệp còn lại return DefWindowProc(hwnd, msg, wParam, lParam); } return 0; } int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wc; HWND hwnd; MSG Msg; //Step 1: Registering the Window Class wc.cbSize = sizeof(WNDCLASSEX); 6
  14. Bài giảng môn học: Lâp̣ triǹ h Windows wc.style = 0; wc.lpfnWndProc = WndProc; /* hàm xử lý thông điệp cửa sổ */ wc.cbClsExtra = 0;/* không cần thông tin thêm cho cửa sổ */ wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); /* màu nền */ /*wc.hbrBackground = GetStockObject(WHITE_BRUSH); màu nền trắng */ wc.lpszMenuName = NULL; /* không có hệ thống thực đơn */ wc.lpszClassName = g_szClassName; /* tên lớp cửa sổ */ wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); /* đăng ký lớp cửa sổ */ if(!RegisterClassEx(&wc)) { MessageBox(NULL, "Window Registration Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK); return 0; } // Step 2: Creating the Window // Tạo ra một thể nghiệm của lớp cửa sổ cho ứng dụng hwnd = CreateWindowEx( WS_EX_CLIENTEDGE, g_szClassName, "The title of my window", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL, hInstance, NULL); if(hwnd == NULL) { MessageBox(NULL, "Window Creation Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK); 7
  15. Bài giảng môn học: Lâp̣ triǹ h Windows return 0; } // Hiển thị cửa sổ ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); // Step 3: The Message Loop // Step 3: Tạo vòng lặp xử lý thông điệp while(GetMessage(&Msg, NULL, 0, 0) > 0) { TranslateMessage(&Msg); DispatchMessage(&Msg); } return Msg.wParam; } Hình 1 – Cƣ̉ a sổ của chƣơng trình khi chạy Đây có le ̃ là môṭ chƣơng trình windows đơn giản nhất mà baṇ có thể viết và chƣ́ c năng của chƣơng trình đơn giản là tạo ra một cửa sổ cũng rất đơn giản . Bạn nên gõ chƣơng trình và biên dic̣ h để chaỵ thƣ̉ và haỹ đảm bảo là không có lỗi gì xảy ra. Bƣớ c 1: Đăng ký (Registering) lớ p cƣ̉ a sổ (Window). Môṭ lớ p cƣ̉ a sổ (Window Class) chƣ́ a các thông tin về kiểu cƣ̉ a sổ , bao gồm thủ tuc̣ Window của nó (thủ tục này kiểm soát cƣ̉ a sổ khi nó đƣơc̣ taọ ra và đáp ƣ́ ng laị các sƣ ̣ kiêṇ ngƣờ i dùng tác đôṇ g lên cƣ̉ a sổ ), các icon lớn và nhỏ của cửa sổ, và màu nền của cửa sổ. Theo cách này bạn có thể đăng ký một lớp và sau đó tạo ra bao nhiêu cửa sổ tùy thích mà không cần chỉ rõ tất cả các thuôc̣ tính đó thêm môṭ lần nào nƣ̃a . Hầu hết các thuôc̣ tính mà baṇ thiết lâp̣ trong lớ p cƣ̉ a sổ (window) có thể thay đổi đƣợc theo một cách rất cơ bản (thổ môc̣ ) nếu nhƣ baṇ thích. Và cần chú ý là lớp cửa sổ này không liên quan gì tới khái niệm các lớp trong C++. const char g_szClassName[] = "myWindowClass"; Biến trên lƣu tên của lớ p cƣ̉ a sổ của chúng ta , chúng ta sẽ sử dụng nó để đăng ký lớp cƣ̉ a sổ của chúng ta vớ i hê ̣thống. 8
  16. Bài giảng môn học: Lâp̣ triǹ h Windows WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = 0; wc.lpfnWndProc = WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wc.lpszMenuName = NULL; wc.lpszClassName = g_szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); if(!RegisterClassEx(&wc)) { MessageBox(NULL, "Window Registration Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK); return 0; } Đó là đoaṇ ma ̃ chƣơng trình chúng ta sƣ̉ duṇ g trong hàm WinMain() để đăng ký lớ p cƣ̉ a sổ của chúng ta. Chúng ta sẽ điền đầy đủ các thành viên của cấu trúc WNDCLASSEX và gọi tớ i hàm RegisterClassEx(). Các thành viên của cấu trúc ảnh hƣởng đến lớp cửa sổ gồm có: cbSize: kích thƣớc của cấu trúc style: Class Style (CS_*), để không nhầm lẫn với Window Style (WS_*). Thông thƣờng thuôc̣ tính này đƣơc̣ gán bằng 0. lpfnWndProc: con trỏ trỏ tớ i thủ tuc̣ quản lý cƣ̉ a sổ cho lớ p cƣ̉ a sổ này. cbClsExtra: Lƣơṇ g dƣ̃ liêụ thêm đƣơc̣ cấp phát trong bô ̣nhớ cho mỗi cƣ̉ a sổ thuôc̣ loaị này. Thông thƣờ ng cũng đƣơc̣ gán bằng 0. hInstance: Quản lý một thể nghiệm (instance) của ứng dụng (tham số đầu tiên của hàm WinMain()). hIcon: Icon lớ n (thƣờ ng là 32x32) hiển thị khi chúng ta nhấn tổ hợp phím Alt+TAB. 9
  17. Bài giảng môn học: Lâp̣ triǹ h Windows hCursor: Con trỏ se ̃ xuất hiêṇ khi con trỏ chuôṭ di chuyển qua vùng cƣ̉ a sổ của chƣơng trình. hbrBackground: Tham số sƣ̉ duṇ g để thiết lâp̣ mầu nền của cƣ̉ a sổ . lpszMenuName: tên của tài nguyên menu đƣơc̣ sƣ̉ duṇ g cho các cƣ̉ a sổ của lớ p. lpszClassName: Tên để điṇ h danh lớ p cƣ̉ a sổ hIconSm: Icon nhỏ (16x16) hiển thi ̣trên thanh task bar và góc trên bên trái của cƣ̉ a sổ . Bạn không nên lo lắng nếu chƣa hiểu về các thuộc tính này, chúng sẽ đƣợc giải thích ở các phần tiếp theo của tài liệu này . Môṭ điều khác cần phải nhớ là không nên cố nhớ nhƣ̃ng thuôc̣ tính này. Tôi rất ít khi (không muốn nói là không bao giờ ) nhớ các thuôc̣ tính cũng nhƣ các tham số của các hàm , điều đó hoàn toàn là môṭ cố gắng lañ g phí thờ i gian và công sƣ́ c . Nếu nhƣ baṇ biết các hàm baṇ cần sƣ̉ duṇ g trong môṭ chƣơng trình nào đó , hãy tra trong các tài liệu help. Nếu baṇ không có cá c file help haỹ down chúng về , các file kiểu này trên mạng không hề thiếu. Cuối cùng thì baṇ se ̃ biết các tham số của các hàm mà baṇ thƣờ ng xuyên sƣ̉ dụng. Tiếp đến chúng ta goị tớ i hàm RegisterClassEx () và kiểm tra xem hàm này có thành công hay không , nếu nhƣ hàm trả về sai chúng ta se ̃ hiển thi ̣môṭ thông báo thất baị và dƣ̀ ng chƣơng trình. Bƣớ c 2: Tạo ra cửa sổ chƣơng trình Khi đa ̃ đăng ký lớ p cƣ̉ a sổ , chúng ta có thể tạo ra các cửa sổ trong chƣơng trình. Các bạn nên tra các tham số của hàm CreateWindowEx () (bạn luôn nên làm vậy khi làm việc với các hàm API mới), nhƣng tôi se ̃ giải thích môṭ chút ở đây: HWND hwnd; hwnd = CreateWindowEx( WS_EX_CLIENTEDGE, g_szClassName, "The title of my window", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL, hInstance, NULL); Tham số đầu tiên (WS_EX_CLIENTEDGE) là kiểu cửa sổ mở rộng (extended), trong trƣờ ng hơp̣ này tham số này đƣợc sử dụng để tạo thành một đƣờng viền chìm bên trong xung quanh cƣ̉ a sổ . Có thể đặt bằng 0 nếu chúng ta muốn khác . Cũng nên thay đổi các giá trị khác để xem chúng làm việc nhƣ thế nào. Tiếp theo chúng ta cần có tên lớ p (g_szClassName), tham số này báo cho hê ̣thống biết loại cửa sổ mà chúng ta muốn tạo ra . Vì chúng ta muốn tạo ra một cửa sổ từ lớp mà chúng ta vƣ̀ a đăng ký nên chúng ta sƣ̉ duṇ g tên của lớ p cƣ̉ a sổ đó . Sau đó chúng ta chỉ rõ tên của cƣ̉ a sổ hoăc̣ tiêu đề (xâu ký tƣ̣) sẽ xuất hiện trong vai trò là tham số Caption hoặc Title Bar của cƣ̉ a sổ của chúng ta. 10
  18. Bài giảng môn học: Lâp̣ triǹ h Windows Tham số chúng ta sƣ̉ duṇ g WS_OVERLAPPEDWINDOW trong vai trò tham số Window Style. Có khá ít các tham số kiểu này do đó bạn nên kiểm tra và thay đổi giá trị của chúng để xem kết quả họat động nhƣ thế nào. Chúng ta sẽ bàn thêm về các tham số này sau. Bốn tham số tiếp theo (CW_USEDEFAULT, CW_USEDEFAULT, 320, 240) là tọa độ X và Y của góc trên bên trái của cƣ̉ a sổ và các kích thƣớ c dài rôṇ g của nó . Tôi đa ̃ thiết lâp̣ giá trị của X và Y là CW _USEDEFAULT để windows tƣ ̣ choṇ vi ̣trí để đăṭ cƣ̉ a sổ chƣơng trình khi chaỵ . Hãy nhớ l à bên trái của màn hình là một giá trị 0 của X và nó tăng dần khi sang phải. Đỉnh của màn hình tƣơng ƣ́ ng vớ i giá tri ̣ 0 của Y tăng dần theo chiều đi xuống . Các đơn vị là các điểm ảnh, là đơn vị nhỏ nhất mà màn hình có thể hiển thi ̣đƣơc̣ taị đô ̣phân giải hiêṇ tại. Tiếp theo (NULL, NULL, g_hInst, NULL) chúng ta có một handle quản lý cửa sổ cha của cửa sổ đƣợc sinh ra , môṭ handle quản lý menu , môṭ handle cho thể nghiêṃ của ƣ́ ng duṇ g và một con trỏ trỏ tớ i dƣ̃ liêụ cƣ̉ a sổ . Trong windows các cƣ̉ a sổ trên màn hình đƣơc̣ sắp xếp theo môṭ cấu trúc phân cấp theo kiểu các cƣ̉ a sổ cha và con . Khi chúng ta nhìn thấy môṭ nút trên môṭ cƣ̉ a sổ , nút đó là con và nó đƣợc chứ a trong cƣ̉ a sổ là cha của nó . Trong ví du ̣này của chúng ta handle của cửa sổ cha là NULL vì chúng ta không có cửa sổ cha nào cả , đây là cƣ̉ a sổ chính hoăc̣ cƣ̉ a sổ ở cấp cao nhất của chúng ta . Menu cũng là NULL vì chúng t a chƣa có menu chƣơng trình nào . Handle dành cho thể nghiêṃ của chƣơng trình đƣơc̣ gán là giá tri ̣ đƣơc̣ truyền cho tham số đầu tiên của hàm WinMain (). Dữ liệu cửa sổ (thƣờ ng là chúng ta chẳng bao giờ sƣ̉ duṇ g chúng ) có thể đƣợ c sƣ̉ duṇ g để gƣ̉ i các dƣ̃ liêụ khác tớ i cƣ̉ a sổ đang đƣơc̣ taọ ra và cũng là NULL. Nếu baṇ đang băn khoăn về giá tri ̣NULL bí hiểm này thì haỹ yên tâm vì nó đơn giản chỉ là 0 (zero). Thƣc̣ sƣ ̣ trong C nó đƣơc̣ điṇ h nghiã là ((void*)0) vì nó đƣợc sử dụng với các con trỏ. Vì thế cho nên bạn có thể sẽ gặp các cảnh báo khi biên dịch chƣơng trình nếu sử dụng NULL cho các giá tri ̣kiểu nguyên , tùy thuộc vào trình biên dịch và cấp độ cảnh báo đƣợc thiết lâp̣ vớ i trình biên dic̣ h. Bạn có thể bỏ qua các cảnh báo hoặc đơn giản là sử dụng giá trị 0 để thay thế. Nguyên nhân số 1 mà mọi ngƣời không biết tại sao chƣơng trình của mình lại có lỗi là do ho ̣không kiểm tra cá c giá tri ̣trả về của lờ i goị hàm mà ho ̣thƣc̣ hiêṇ trong chƣơng trình của mình. CreateWindow() sẽ trả về sai trong một số trƣờng hợp ngay cả khi bạn là một lập trình viên có nhiều kinh nghiệm , chỉ đơn giản bởi vì có rất n hiều lỗi mà chúng ta rất dê ̃ taọ ra chúng. Cho tớ i khi baṇ hoc̣ đƣơc̣ cánh nhanh chóng xác điṇ h các lỗi kiểu nhƣ vâỵ , hãy tự cho mình một cơ hội để có thể tìm ra các lỗi trong chƣơng trình , hãy luôn kiểm tra các giá trị tr ả về khi goị tớ i các hàm. if(hwnd == NULL) { MessageBox(NULL, "Window Creation Failed!", "Error!", MB_ICONEXCLAMATION | MB_OK); return 0; } Sau khi chúng ta đa ̃ taọ ra cƣ̉ a sổ và kiểm tra để đảm bảo là chúng ta có một handle hợp lê,̣ chúng ta sẽ hiển thị cửa sổ lên màn hình của windows (hê ̣điều hành ) bằng cách sƣ̉ duṇ g 11
  19. Bài giảng môn học: Lâp̣ triǹ h Windows tham số cuối cùng của hàm WinMain () và sau đó cập nhật nó để đảm bảo là tự nó đƣợc vẽ lại môṭ cách hơp̣ lê ̣trên màn hình. ShowWindow(hwnd, nCmdShow); UpdateWindow(hwnd); Tham số nCmdShow là tùy choṇ , bạn có thể đơn giản gán nó là SW _SHOWNORMAL mỗi khi chúng ta làm viêc̣ vớ i nó.Tuy nhiên sƣ̉ duṇ g tham số đƣơc̣ truyền vào hàm WinMain() sẽ tạo cơ hội cho các ngƣời dùng sử dụng chƣơng trình của chúng ta có thể chỉ định họ muốn hoăc̣ không muốn của sổ của chúng ta bắt đầu vớ i kích thƣớ c cƣ̉ a sổ lớ n nhất hoăc̣ nhỏ nhất Baṇ se ̃ thấy các tùy chọn này trong các tham số của các shortcut của tới các cửa sổ. Bƣớ c 3: Vòng lặp xử lý thông điệp Đây chính là trái tim của toàn bô ̣chƣơng trình , hầu hết nhƣ̃ng thƣ́ mà chƣơng trình của chúng ta xử lý là nằm ở đây. while(GetMessage(&Msg, NULL, 0, 0) > 0) { TranslateMessage(&Msg); DispatchMessage(&Msg); } return Msg.wParam; Hàm GetMessage() lấy môṭ thông điêp̣ tƣ̀ hàng đơị thông điêp̣ của ƣ́ ng duṇ g của baṇ . Bất kỳ thờ i điểm nào ngƣờ i dùng di chuyển con chuôṭ , gõ bàn phím , click chuôṭ trên menu của cửa sổ chƣơng trình, hoăc̣ làm bất cƣ́ môṭ thao tác nào đaị loaị nhƣ vâỵ , các thông điệp sẽ đƣơc̣ sinh ra và thêm chúng vào hàng đơị thông điêp̣ của chƣơng trình. Bằng cách goị tớ i hàm GetMessage() bạn đang yêu cầu thông điệp tiếp theo loại bỏ khỏi hàng đợi và trả nó về cho bạn xử lý. Nếu nhƣ không có thông điêp̣ nào hàm GetMesage () sẽ chuyển sang blocks . Nếu bạn không quen với thuâṭ ngƣ̃, điều đó có nghiã là nó se ̃ đơị cho tớ i khi có môṭ thông điêp̣ tiếp theo để xƣ̉ lý (lấy về cho chƣơng trình). TranslateMessage() thƣc̣ hiêṇ thêm môṭ số xƣ̉ lý trên các sƣ ̣ kiêṇ bàn phím chẳng haṇ nhƣ sinh ra các thông điêp̣ WM _CHAR cùng vớ i các thông điêp̣ WM _KEYDOWN. Cuối cùng DispatchMessage() gƣ̉ i thông điêp̣ tớ i cƣ̉ a sổ mà thông điêp̣ cần đƣơc̣ gƣ̉ i tớ i (xƣ̉ lý theo kiểu hƣớ ng sƣ ̣ kiêṇ ). Đó có thể là cƣ̉ a sổ chính của chƣơng trình hoăc̣ có th ể là một cửa sổ khác, hoăc̣ là môṭ điều khiển , và trong một số trƣờng hợp là một cửa sổ đƣợc tạo ra ở hậu trƣờ ng bở i hê ̣thống hoăc̣ môṭ chƣơng trình khác. Đây không phải là điều mà các baṇ cần phải lo lắng vì tất cả nhƣ̃ng gì chúng ta bàn tớ i là lấy thông điêp̣ và gƣ̉ i nó đi , hê ̣thống se ̃ xƣ̉ lý để đảm bảo thông điêp̣ đó đƣơc̣ gƣ̉ i đến đúng nơi se ̃ nhâṇ nó . Bƣớ c 4: Thủ tục cửa sổ. Nếu nhƣ vòng lăp̣ thông điêp̣ là trái tim của chƣơng trình thì thủ tục cửa sổ là bộ não của chƣơng trình. Đây là nơi mà tất cả các thông điêp̣ đƣơc̣ gƣ̉ i tớ i cƣ̉ a sổ của chúng ta đƣơc̣ xƣ̉ lý. LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 12
  20. Bài giảng môn học: Lâp̣ triǹ h Windows { switch(msg) { case WM_CLOSE: DestroyWindow(hwnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, msg, wParam, lParam); } return 0; } Thủ tục cƣ̉ a sổ đƣơc̣ goị tớ i để xƣ̉ lý các thông điêp̣ , tham số HWND là để quản lý cƣ̉ a sổ của baṇ , cũng chính là đối tƣợng của thông điệp . Điều này là quan troṇ g bở i vì baṇ có thể có hai hoặc nhiều hơn nữa các cửa sổ cùng lớ p và chúng se ̃ sƣ̉ duṇ g cùng môṭ thủ tuc̣ cƣ̉ a sổ (WndProc()). Sƣ ̣ khác nhau ở đây là tham số hwnd se ̃ thay đổi tùy thuôc̣ vào cƣ̉ a sổ là cƣ̉ a sổ nào. Chẳng haṇ khi chúng ta lấy về môṭ thông điêp̣ WM _CLOSE chúng ta se ̃ hủy cƣ̉ a sổ. Và vì chúng ta sử dụng handle nhận đƣợc để quản lý cửa sổ qua tham số thứ nhất nên các cửa sổ khác sẽ không bị ảnh hƣởng, chỉ có cửa sổ nhận thông điệp là sẽ bị hủy. WM_CLOSE đƣơc̣ gƣ̉ i đến khi ngƣờ i dùng nhấn chu ột vào nút đóng cửa sổ hoặc nhấn tổ hơp̣ phím Alt +F4. Thao tác này se ̃ làm cho cƣ̉ a sổ bi ̣hủy theo măc̣ điṇ h , nhƣng tôi muốn xƣ̉ lý nó môṭ cách dƣ́ t khoát , vì điều này có thể liên quan tới một số thao tác khác chẳng hạn nhƣ đóng các file dƣ̃ liêụ đang mở , ngắt các kết nối cơ sở dƣ̃ liêụ , các kết nối mạng vân vân trƣớ c khi thoát khỏi chƣơng trình. Khi chúng ta goị tớ i hàm DestroyWindow () hê ̣thống se ̃ gƣ̉ i thông điêp̣ WM_DESTROY tớ i cƣ̉ a sổ se ̃ bi ̣ hủy bỏ, trong trƣờ ng hơp̣ này là cƣ̉ a sổ của chúng ta , và sau đó hủy tất cả các cƣ̉ a sổ con trƣớ c khi loaị bỏ cƣ̉ a sổ của chúng ta khỏi hê ̣thống . Vì chỉ có môṭ cƣ̉ a sổ trong chƣơng trình của chúng ta nên cũng không có nhiề u viêc̣ phải làm và chúng ta muốn thoát khỏi chƣơng trình, vì thế hàm PostQuitMessage() đƣơc̣ goị tớ i. Hàm này sẽ gửi thông điêp̣ WM _QUIT tớ i vòng lăp̣ thông điêp̣ . Chúng ta không bao nhận đƣợc thông điệp này vì nó làm cho hàm GetMessage() trả về FALSE, và nhƣ bạn sẽ thấy trong đoạn mã vòng lăp̣ xƣ̉ lý thông điêp̣ của chúng ta , khi điều đó xảy ra chúng ta dƣ̀ ng viêc̣ xƣ̉ lý các thông điêp̣ và trả về giá trị cuối cùng , tham số wParam của thông điêp̣ WM _QUIT là giá tri ̣đƣơc̣ truyền qua hàm PostQuitMessage (). Giá trị trả về chỉ thực sự có ích nếu chƣơng trình của chúng ta đƣơc̣ thiết kế để môṭ chƣơng trình khác goị và chúng ta muốn trả về môṭ giá tri ̣cu ̣thể . Bƣớ c 5: không có bƣớ c 5 Đến đây là hết , chúng ta không có bƣớc 5 và tôi hy vọng các bạn đã hiểu đƣợc ít nhiều cấu trúc cơ bản của môṭ chƣơng trình trên Windows. 13
  21. Bài giảng môn học: Lâp̣ triǹ h Windows 3.3 Quản lý các thông điệp Ví dụ: window_click. Vâỵ là chúng ta đa ̃ có môṭ cƣ̉ a sổ , nhƣng nó se ̃ không làm bất cƣ́ điều gì ngoaị trƣ̀ nhƣ̃ng gì mà hàm DefWindowProc () cho phép nó làm , chẳng haṇ nhƣ thay đổi kích thƣớc , phóng to, thu nhỏ, vân vân. Quả là không thú vị tí nào. Trong phần tiếp theo tô i se ̃ hƣớ ng dâñ các baṇ sƣ̉ a đổi nhƣ̃ng gì chúng ta đa ̃ viết để taọ ra môṭ cái gì đó mớ i mẻ. Trƣớ c tiên cần đảm bảo là baṇ đa ̃ biên dic̣ h và chaỵ chƣơng trình simple _window thành công, chúng ta sẽ copy nó sang ví dụ mới và sƣ̉ a đổi ma ̃ nguồn (code) của chƣơng trình. Chúng ta sẽ thêm cho chƣơng trình khả năng hiển thị tên của chƣơng trình khi ngƣời dùng kích chuột vào cửa sổ của chƣơng trình . Thƣc̣ chất của khả năng mớ i này là chúng ta se ̃ tƣ ̣ xƣ̉ lý thông điệp nhấn chuột của ngƣời dùng, tất nhiên là trong hàm WndProc(), tƣ̀ : LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_CLOSE: DestroyWindow(hwnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, msg, wParam, lParam); } return 0; } Khi chúng ta muốn xƣ̉ lý các thông điêp̣ nhấn chuôṭ chúng ta cần có t hêm các handle : WM_LBUTTONDOWN (hoăc̣ WM _RBUTTONDOWN, WM_MBUTTONDOWN cho các thao tác nhấn chuôṭ phải và giƣ̃a): LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_LBUTTONDOWN: // <- 14
  22. Bài giảng môn học: Lâp̣ triǹ h Windows // <- we just added this stuff break; // <- case WM_CLOSE: DestroyWindow(hwnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, msg, wParam, lParam); } return 0; } Thƣ́ tƣ ̣ xƣ̉ lý các thông điêp̣ là quan troṇ g và cần nhớ là đối vớ i các thông điêp̣ khác (ngoài WM_DESTROY và WM_QUIT) cần có thêm câu lêṇ h break sau khi xƣ̉ lý xong thông điêp̣ . Trƣớ c tiên tôi se ̃ trình bày đoaṇ ma ̃ lêṇ h mà chúng ta se ̃ thêm vào (hiển thi ̣tên của chƣơng trình của chúng ta ) và sau đó tôi sẽ tích hợp đoạn mã đó vào chƣơng trình của chúng ta. Trong các phần sau của chƣơng trình t ôi se ̃ chỉ cho các baṇ đoaṇ ma ̃ và để các baṇ tƣ ̣ tích hơp̣ đoaṇ ma ̃ đó vào các chƣơng trình . Điều này vƣ̀ a tốt cho tôi : tôi se ̃ không phải gõ đi gõ laị các đoạn mã lệnh và vừa tốt cho các bạn : các bạn có cơ hội thực hàn h nhƣ̃ng hiểu biết của mình để nâng cao kỹ năng thực hành . Còn nếu nhƣ bạn không chắc chắn hãy tra trong mã nguồn chƣơng trình đi kèm vớ i tài liêụ này. GetModuleFileName(hInstance, szFileName, MAX_PATH); MessageBox(hwnd, szFileName, "This program is:", MB_OK | MB_ICONINFORMATION); Hàm WndProc() của chúng ta bây giờ sẽ nhƣ sau: LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { case WM_LBUTTONDOWN: // BEGIN NEW CODE { char szFileName[MAX_PATH]; HINSTANCE hInstance = GetModuleHandle(NULL); 15
  23. Bài giảng môn học: Lâp̣ triǹ h Windows GetModuleFileName(hInstance, szFileName, MAX_PATH); MessageBox(hwnd, szFileName, "This program is:", MB_OK | MB_ICONINFORMATION); } // END NEW CODE break; case WM_CLOSE: DestroyWindow(hwnd); break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hwnd, msg, wParam, lParam); } return 0; } Hãy chú ý tới cặp dấu { và } mớ i. Các cặp dấu này là bắt buộc khi chúng ta khai báo các biến trong câu lêṇ h switch. Bƣớc tiếp theo tất nhiên là dịch chƣơng trình, chạy thử và xem kết quả của chƣơng trình. Chúng ta có thể chú ý là ở đây tôi đã sử dụng hai biến khi gọi hàm GetModuleFileName(), tham số thƣ́ nhất là môṭ handle tham chiếu tớ i mô ̣ t chƣơng trình đang chạy, nhƣng chúng ta có thể lấy handle đó ở đâu ra ? Ở đây một lân nữa chúng ta lại sử dụng môṭ hàm API khác GetModuleHandle(), thâṭ may mắn là đối vớ i hàm này nếu chúng ta truyền tham số là NULL vào thì kết quả trả về se ̃ là handle trỏ tớ i file đƣơc̣ sƣ̉ duṇ g để taọ ra tiến trình đã gọi hàm, đó chính xác là cái mà chúng ta cần. Và do đó chúng ta có câu lệnh: HINSTANCE hInstance = GetModuleHandle(NULL); Tham số thƣ́ hai khi go ̣i hàm GetModuleFileName() là một con trỏ xâu để chứa đƣờng dâñ (kết quả của hàm ) tớ i chƣơng trình có handle là tham số thƣ́ nhất , kiểu của nó là LPTRSTR (hoăc̣ LPSTR) và do LPSTR hoàn toàn tƣơng tự nhƣ char * nên chúng ta khao báo môṭ xâu nhƣ sau: char szFileName[MAX_PATH]; MAX_PATH là môṭ macro thuôc̣ windows .h điṇ h nghiã đô ̣dài tối đa của môṭ xâu để chƣ́ a đô ̣dài đƣờ ng dâñ tớ i môṭ file trên windows. 16
  24. Bài giảng môn học: Lâp̣ triǹ h Windows 3.4 Vòng lặp xử lý thông điệp Nhƣ̃ng kiến thƣ́ c về vòng lặp xử lý thông điệp và cấu trúc của việc gửi một thông điệp là cần thiết để viết bất cứ ứng dụng nào. Cho đến thờ i điểm hiêṇ taị chúng ta mớ i xem xét môṭ chút về quá trình xử lý thông điệp , trong phần này tôi sẽ trình bày với các bạn kỹ càng hơn về cả quá trình xử lý thông điệp. Thế nào là môṭ thông điêp̣ ? Môṭ thông điêp̣ (message) là một giá trị nguyên (số). Nếu baṇ tra trong các file header (đây là môṭ thói quen tốt khi làm việc với các hàm API) bạn sẽ thấy các dòng khai báo sau: #define WM_INITDIALOG 0x0110 #define WM_COMMAND 0x0111 #define WM_LBUTTONDOWN 0x0201 vân vân. Các thông điệp đƣợc sử dụng để tru yền thông hầu nhƣ moị thƣ́ trên hê ̣điều hành windows ít nhất là tại các cấp độ cơ bản . Nếu baṇ muốn môṭ cƣ̉ a sổ hoăc̣ môṭ điều khiển (là một dạng cửa sổ đặc biệt ) thƣc̣ hiêṇ môṭ công viêc̣ nào đó , bạn sẽ phải gửi cho nó môṭ thông điêp̣ . Nếu môṭ cƣ̉ a sổ khác muốn baṇ làm điều gì đó nó se ̃ gƣ̉ i tớ i cho baṇ môṭ thông điêp̣ . Nếu môṭ sƣ ̣ kiêṇ xảy ra chẳng haṇ nhƣ ngƣờ i dùng gõ bàn phím , di chuyển chuôṭ , nhấn chuôṭ lên môṭ button , thì các thôn g điêp̣ se ̃ đƣơc̣ hê ̣thống (hê ̣điều hành windows ) gƣ̉ i đến cho các cƣ̉ a sổ chiụ tác đôṇ g của sƣ ̣ kiêṇ đó . Nếu baṇ là môṭ trong các cƣ̉ a sổ nhƣ thế , bạn sẽ tiếp nhâṇ và xƣ̉ lý thông điêp̣ , có các hành vi thích hợp. Mỗi môṭ thông điêp̣ có thể có nhiều nhất là hai tham số , wParam và lParam . Nguyên bản wParam là 16 bit và lParam là 32 bit, nhƣng trên các hê ̣thống Win32 chúng đều là 32 bit. Không phải tất các thông điêp̣ đều sƣ̉ duṇ g hai tham số này , và mỗi thông điêp̣ sƣ̉ duṇ g chúng theo các cách khác nhau . Chẳng haṇ thông điêp̣ WM _CLOSE không sƣ̉ duṇ g cả hai tham số trên, và bạn có thể bỏ qua chúng . Thông điêp̣ WM_COMMAND sƣ̉ duṇ g cả hai tham số trên , wParam chƣ́ a hai giá tri ̣ , HIWORD(wParam) là thông điệp báo hiệu (nếu thích hơp̣ ) và LOWORD(wParam) là định danh điều khiển hoặc menu gửi thông điệp . lParam là HWND (window handle) của điều khiển gửi thông điệp hoặc NULL nếu nhƣ các thông điệp không phải đƣợc gƣ̉ i đi tƣ̀ môṭ điều khiển nào đó. HIWORD() và LOWORD là các macro đƣợc định nghĩa bởi windows lấy ra 2 byte cao (High Word) của một giá trị 4 byte = 32 bit (0xFFFF0000) và hai byte thấp (0x0000FFFF) tƣơng ƣ́ ng. Trên các hê ̣thống Win 32 môṭ WORD là môṭ giá tri ̣ 16 bit còn DWORD (Double WORD) là một giá trị 32 bit. Để gƣ̉ i môṭ thông điêp̣ baṇ có thể sƣ̉ duṇ g hàm PostMessage () hoăc̣ SendMessage (). PostMessage() đăṭ (put) thông điêp̣ vào hàng đơị thông điêp̣ và trả về ngay lâp̣ tƣ́ c. Điều đó có nghĩa là mỗi khi chúng ta gọi tới hàm PostMessage () nó sẽ gửi thông điệp đi ngay nhƣng việc thông điêp̣ đó có thƣc̣ hiêṇ ngay hay không hoăc̣ thâṃ chí có thƣc̣ hiêṇ hay không thì còn chƣa chắc chắn. Hàm SendMessage() gƣ̉ i thông điêp̣ trƣc̣ tiếp tớ i cho cƣ̉ a sổ nhâṇ thông điêp̣ và sẽ không kết thúc cho tới khi thông điệp đó đƣợc xử lý xong. Nếu chúng ta muốn đóng môṭ cƣ̉ a sổ chúng ta có thể gƣ̉ i tớ i cƣ̉ a sổ đó môṭ thông điêp̣ WM_CLOSE chẳng haṇ nhƣ PostMessage(hwnd, WM_CLOSE, 0, 0); điều này có tác đôṇ g tƣơng tƣ ̣ nhƣ viêc̣ chúng ta nhấn chuôṭ lên biểu tƣơṇ g trên góc trên bên phải của cƣ̉ a sổ . Chú ý rằng wParam và 17
  25. Bài giảng môn học: Lâp̣ triǹ h Windows lParam đều bằng 0 trong trƣờ ng hơp̣ này . Điều này là do nhƣ chúng ta đa ̃ nói trƣớ c chúng không có ý nghiã gì đối vớ i thông điêp̣ WM_CLOSE. Các hộp thoại (Dialog). Khi baṇ đa ̃ bắt đầu sƣ̉ duṇ g các hôp̣ thoaị , bạn sẽ cần gửi các thông điệp tới các điều khiển để có thể truyền thông vớ i chúng . Bạn có thể làm điều này bằng cách trƣớc hết lấy handle quản lý điều khiển bằng hàm GetDlgItem () và sau đó sử dụng hàm SendMessage () hoăc̣ có thể sƣ̉ duṇ g hàm SendDlgItemMessage () (hàm này kết h ợp công việc của cả hai hàm trên). Bạn sẽ cung cấp cho hàm một handle của một cửa sổ và một ID con và hàm sẽ lấy handle con của hôp̣ thoaị và gƣ̉ i thông điêp̣ tớ i cho nó . SendDlgItemMessage() và một vài hàm API tƣơng tự khác chẳng haṇ nhƣ GetDlgItemText () sẽ làm việc trên tất cả các cửa sổ chƣ́ không chỉ vớ i các hôp̣ thoaị . Thế nào là hàng đơị thông điêp̣ ? Giả sử bạn đang bận túi bụi với việc xử lý thông điệp WM _PAINT và đôṭ nhiên ngƣời dùng thƣc̣ hiêṇ hàng loaṭ thao tác trên bàn phím của máy tính . Điều gì se ̃ xảy ra ? Bạn sẽ bị ngăt viêc̣ đang ve ̃ để xƣ̉ lý các thao tác bàn phím của ngƣờ i dùng hoăc̣ các thao tác đó se ̃ bi ̣bỏ qua? Tất cả các cách giải quyết nhƣ vâỵ đều có vấn đề , giải pháp ở đây là sử dụng một hàng đơị để lƣu các thông điêp cần xƣ̉ lý , khi các thông điêp̣ đƣơc̣ gƣ̉ i đến chúng se ̃ đƣơc̣ thêm vào hàng đợi và khi các thông điệp đƣợc xử lý chúng sẽ đƣợc loại bỏ khỏ i hàng đơị . Và để đảm bảo các thông điệp không bị bỏ qua nếu nhƣ bạn đang bận xử lý một thông điệp nào đó , các thông điêp̣ khác se ̃ đƣơc̣ chờ trong hàng đơị cho tớ i khi tớ i lƣơṭ chúng đƣơc̣ xƣ̉ lý . Thế nào là môṭ vòng lăp̣ thông điêp̣ (vòng lặp xử lý thông điệp – Message Loop) while(GetMessage(&Msg, NULL, 0, 0) > 0) { TranslateMessage(&Msg); DispatchMessage(&Msg); } 1. Vòng lặp thông điệp gọi tới hàm GetMessage (), hàm này sẽ kiểm tra hàng đợi thông điêp̣ của baṇ . Nếu nhƣ hàng đơị thông điêp̣ là rỗng chƣơng trình của baṇ về cơ bản sẽ dừng và đợi cho tới khi có một thông điệp mới (trạng thái Block). 2. Khi môṭ sƣ ̣ kiêṇ xảy ra làm cho môṭ thông điêp̣ đƣơc̣ thêm vào hàng đơị (chẳng haṇ nhƣ hê ̣thống ghi nhâṇ môṭ sƣ ̣ kiêṇ nhấn chuôṭ ) hàm GetMessage() sẽ trả về môṭ giá tri ̣nguyên dƣơng chỉ ra rằng có môṭ thông điêp̣ cần xƣ̉ lý , và các thông tin về thông điêp̣ đó se ̃ đƣơc̣ điền vào cấu trúc MSG truyền cho hàm. Hàm sẽ trả về 0 nếu nhƣ thông điêp̣ là WM_QUIT và là môṭ giá tri ̣âm nếu nhƣ có lỗi xảy ra. 3. Chúng ta nhận đƣợc thông điệp (qua biến Msg ) và truyền cho hàm TranslateMessage(), hàm này thực hiện xử lý thêm một chút , dịch các thông tin của thông điêp̣ thành các thông điêp̣ ký tƣ̣ . Bƣớ c này thƣc̣ sƣ ̣ không bắt buôc̣ nhƣng môṭ số thông điêp̣ se ̃ không làm viêc̣ đƣơc̣ nếu không có bƣớ c này. 4. Sau đó chúng ta chuyển thông điêp̣ cho hàm DispatchMessage (). Hàm này sẽ nhâṇ thông điêp̣ kiểm tra cƣ̉ a sổ đích của thông điêp̣ và tìm hàm xƣ̉ lý thông điêp̣ 18
  26. Bài giảng môn học: Lâp̣ triǹ h Windows (Window Procedure) của cửa sổ đó. Sau đó nó se ̃ goị tớ i hàm xƣ̉ lý thông điêp̣ của cƣ̉ a sổ , gƣ̉ i các tham số: handle của cƣ̉ a sổ , thông điêp̣ và các tham số wParam, lParam. 5. Trong hàm xƣ̉ lý thông điêp̣ baṇ se ̃ kiểm tra thông điêp̣ và các tham số của nó và làm bất cứ điều gì mà bạn thích với chúng . Nếu baṇ không muốn xƣ̉ lý các thông điêp̣ môṭ cách chi tiết cụ thể, bạn hầu nhƣ chỉ việc gọi tới hàm DefWindowProc (), hàm này sẽ thƣc̣ hiêṇ các hành đôṇ g măc̣ điṇ h cho baṇ (điều này thƣờ ng có nghiã là chẳng làm gì cả ). 6. Sau khi baṇ đa ̃ kết thúc viêc̣ xƣ̉ lý thông điêp̣ , hàm xử lý thông điêp̣ của baṇ trả về, hàm DispatchMessage() trả về và chúng ta qua trở lại đầu vòng lặp. Đây là môṭ khái niêṃ cƣc̣ kỳ quan troṇ g của các chƣơng trình trên Windows . Thủ tục xƣ̉ lý thông điêp̣ của baṇ không đƣơc̣ goị môṭ c ách thần bí bởi hệ thống , mà chính bạn đã gọi tớ i chúng môṭ cách gián tiếp thông qua viêc̣ goị tớ i hàm DitpatchMessage (). Nếu nhƣ baṇ muốn, bạn có thể sử dụng hàm GetWindowLong () trên handle của cƣ̉ a sổ đích của thông điêp̣ để tìm ra thủ tục xử lý cửa sổ của nó và gọi tới hàm đó một cách trực tiếp: while(GetMessage(&Msg, NULL, 0, 0) > 0) { WNDPROC fWndProc = (WNDPROC)GetWindowLong(Msg.hwnd, GWL_WNDPROC); fWndProc(Msg.hwnd, Msg.message, Msg.wParam, Msg.lParam); } Tôi đa ̃ thƣ̉ cách đó vớ i đoaṇ ma ̃ chƣơng trình trƣớ c của chúng ta và nó hoaṭ đôṇ g tốt , tuy nhiên có rất nhiều vấn đề chẳng haṇ nhƣ các chuyển đổi Unicode /ANSI, các lời gọi tới các điều khiển thời gian vân vân m à hàm này không phù hợp , và khả năng rất cao là nó sẽ break vớ i hầu hết các chƣơng trình trƣ̀ các chƣơng trình đơn giản . Vì thế không nên thử dùng hàm này, trƣ̀ khi baṇ chỉ muốn thƣ̉ nó. Chú ý là chúng ta sử dụng hàm Get WindowLong() để lấy thủ tục xử lý cửa sổ của cửa sổ. Tại sao chúng ta không đơn giản là gọi tới hàm WndProc () môṭ cách trƣc̣ tiếp ? Vòng lặp các thông điệp của chúng ta chịu trách nhiệm đáp ứng cho tất cả các cửa sổ tron g chƣơng trình của chúng ta , điều này bao gồm cả các thƣ́ chẳng haṇ nhƣ các nút (button) và các hộp danh sách vớ i các hàm xƣ̉ lý thông điêp̣ của chúng , vì thế chúng ta cần đảm bảo là chúng ta gọi đến đúng hàm xử lý cửa sổ của các thành phần đó (đây thƣc̣ sƣ ̣ là môṭ ví du ̣của khái niêṃ đa thể trong lâp̣ trình hƣớ ng đối tƣơṇ g ). Vì các cửa sổ có thể sử dụng chung một hàm xử lý thông điêp̣ nên tham số đầu tiên (handle của cƣ̉ a sổ ) đƣơc̣ dùng để chỉ cho hàm xƣ̉ lý thông điêp̣ biết cƣ̉ a sổ nào là dành cho thông điêp̣ nào. Nhƣ baṇ có thể thấy ƣ́ ng duṇ g của baṇ dành phần lớ n thờ i gian của nó cho vòng lăp̣ xƣ̉ lý thông điệp , nơi mà baṇ có thể hân hoan gƣ̉ i các thông điêp̣ tớ i các cƣ̉ a sổ se ̃ xƣ̉ lý chúng . Nhƣng baṇ cần làm gì khi muốn thoát khỏi chƣơng trình ? Vì chúng ta sử dụng một vòng lặp while(), nếu nhƣ hàm GetMessage () trả về FALSE , vòng lặp sẽ thoát và chúng ta sẽ tới cuối hàm WinMain() và thoát khỏi chƣơng trình . Đó chính xác là nhƣ̃ng gì mà hàm PostQuitMessage() đa ̃ làm. Nó đặt một thông điệp WM _QUIT vào hàng đơị thônmg điêp̣ và thay vì trả về môṭ giá tri ̣dƣơng, hàm GetMesage() điền đầy đủ các thông tin cho cấu trúc Msg và trả về 0. Tại thời điểm này thành viên wParam của cấu trúc Msg chứa giá trị mà bạn đã truyền cho hàm PostQuitMessage () và bạn cũng có thể bỏ qua nó , hoăc̣ trả về qua hàm WinMain(), giá trị đó sẽ đƣợc dùng nhƣ là mã thoát chƣơng trình khi tiến trình kết thúc. 19
  27. Bài giảng môn học: Lâp̣ triǹ h Windows Chú ý: GetMessage() sẽ trả về -1 nếu nhƣ găp̣ môṭ lỗi . Bạn cần nhớ kỹ điều này nếu không có thể chƣơng trình se ̃ găp̣ truc̣ trăc̣ , măc̣ dù hàm GetMessage() có kiểu BOOL song nó vâñ có thể trả về môṭ giá tri ̣không phải là TRUE hay FALSE vì BOOL có nghiã là UINT (unsigned int). Các ví dụ sau có vẻ sẽ làm việc tốt: while(GetMessage(&Msg, NULL, 0, 0)) while(GetMessage(&Msg, NULL, 0, 0) != 0) while(GetMessage(&Msg, NULL, 0, 0) == TRUE) Thƣc̣ tế tất cả các trƣờ ng hơp̣ đó đều sai. Bạn có thể chú ý là tôi vẫn sử dụng trƣờng hợp 1 cho tất cả các ví du ̣cho tớ i thờ i điểm này , và nhƣ tôi đã đề cập nó vẫn làm việc tốt khi mà hàm GetMessage () không bao giờ trả về FALSE . Tuy nhiên không thể đảm bảo là hàm GetMessage() không găp̣ lỗi trong tất cả các trƣờ ng hơp̣ và baṇ nên sƣ̉ duṇ g cách viết nhƣ sau: while(GetMessage(&Msg, NULL, 0, 0) > 0) Bài tập: Bài tập 1: Viết chƣơng trình mô phỏng chƣơng trình máy tính với các chức năng tính toán đơn giản trên Windows. Bài tập 2: Viết chƣơng trình với hệ thống menu cho phép thay đổi màu nền 20
  28. Bài giảng môn học: Lâp̣ triǹ h Windows Chƣơng 2: Hê ̣thố ng file và thƣ mục 1. Truy câp̣ và sƣ̉ duṇ g hê ̣thố ng file trên môi trƣờng Windows Hầu hết các ƣ́ ng duṇ g ngày nay đều cho phép ngƣờ i dùng lƣạ choṇ ghi laị nhƣ̃ng gì chƣơng trình đa ̃ taọ ra . Nhƣ̃ng thƣ́ cần ghi laị có thể là các tài liêụ , dƣ̃ liêụ chƣơng trình hoăc̣ môṭ thiết lâp̣ nào đó đối vớ i chƣơng trình . Trong bài thƣc̣ hành này chúng ta se ̃ khám phá các hỗ trơ ̣ của Visual C ++ cho viêc̣ cài đăṭ các chƣ́ c năng này môṭ cách dê ̃ dàng . Cụ thể chúng ta sẽ học: Cách Visual C++ sƣ̉ dung các luồng C++ để ghi lại dữ liệu trong chƣơng trình Cách thức sử dụng file dạng nhị phân Cách làm cho các đối tƣợng chƣơng trình có khả năng serialize Cách thức lƣu các biến có kiểu khác nhau vào cùng một file 2. Các ví dụ về thao tác với file 2.1 Serialization Có hai phần của serialization . Khi dƣ̃ liêụ ƣ́ ng duṇ g đƣơc̣ lƣu trên điã hê ̣thống dƣới dạng một file thì đó gọi là serialization . Khi traṇ g thái của ƣ́ ng duṇ g đƣơc̣ phuc̣ hồi tƣ̀ file đó đƣơc̣ goị là deserialization . Sƣ ̣ kết hơp̣ của hai phần này làm nên cái goị là serialization của các đối tƣơng ứng dụng trong Visual C++. 2.1.1 Các lớp CArchive và CFile Serialization trong các ƣ́ ng duṇ g của Visual C++ đƣơc̣ thƣc̣ hiêṇ qua lớ p CArchive. Lớp CArchive đƣơc̣ thiết kế để làm viêc̣ vớ i các luồng vào ra cho môṭ đối tƣơṇ g CFile nhƣ minh họa trong hình vẽ sau: Lớ p này sƣ̉ duṇ g các luồng của C ++ để cho phép các luồng dữ liệu đƣợc nạp từ hoặc lƣu laị thành các file môṭ cách hiêụ quả . Lớ p CArchive không thể tồn taị nếu không có môṭ đối tƣơṇ g CFile gắn vớ i nó. Lớ p CArchive có thể chƣ́ a dƣ̃ liêụ trong rất nhiều kiểu file khác nhau , tất cả chúng đều là hâụ duê ̣của lớ p CFile . Theo măc̣ điṇ h App Wizard se ̃ bao gói tất cả các chƣ́ c năng để taọ và mở các đối tƣợng CFile thông thƣờng để sử dụng với CArchive . Nếu chúng ta muốn hoăc̣ 21
  29. Bài giảng môn học: Lâp̣ triǹ h Windows cần thiết phải làm viêc̣ vớ i môṭ trong các lo ại file này chúng ta cần phải viết thêm các đoạn mã chƣơng trình cần thiết để có thể làm việc với các loại file khác nhau. 2.1.2 Hàm Serialize Lớ p CArchive đƣơc̣ sƣ̉ duṇ g trong hàm Serialize trên các đối tƣơṇ g document và dƣ̃ liêụ trong các ƣ́ ng duṇ g Visual C ++. Khi môṭ ƣ́ ng duṇ g đoc̣ hoăc̣ ghi lên môṭ file hàm Serialize của đối tƣơṇ g document se ̃ đƣơc̣ goị đến , truyền đối tƣơṇ g CArchive đƣơc̣ sƣ̉ duṇ g để ghi hoặc đọc từ file. Trong hàm Serialize thƣ́ tƣ ̣ logic đƣơc̣ tuân theo để xác điṇ h xem đó là thao tác đoc̣ hay ghi bằng cách goị tớ i các hàm IsStoring hoăc̣ IsLoading . Giá trị trả về từ hai hàm trên sẽ xác định xem ứng dụng cần ghi hay đọc từ luồng vào ra của lớp CArchive . Môṭ hàm Serialize điển hình sẽ có cấu trúc nhƣ sau: void CAppDoc::Serialize(CArchive& ar) { // Is the archive being written to? if (ar.IsStoring()) { // Yes, write my variable ar > m_MyVar; } } Chúng ta có thể đặt một hàm Serialize trong bất cứ lớp nào mà chúng ta tạo ra để sau đó gọ đến hàm Serialize từ hàm Serialize của lớp document . Nếu chúng ta đăṭ các đối tƣơṇ g do chúng ta tạo ra vào một mảng các đối tƣơṇ g chẳng haṇ nhƣ CObArray nhƣ trong ƣ́ ng duṇ g trƣớ c đây 3 ngày chúng ta có thể gọi tới hàm Serialize của mảng từ hàm Serialize của lớp document. Đối tƣợng mảng sẽ , khi tớ i lƣơṭ nó , gọi tới hàm Serialize của các đối tƣợng đƣơc̣ chƣ́ a bên trong nó. 2.1.3 Các đối tƣợng có thể Serialize Khi chúng ta taọ ra lớ p CLine trong bài thƣc̣ hành số 10 chúng ta cần phải thêm 2 macro trƣớ c khi chúng ta có thể ghi laị hoăc̣ nap̣ dƣ̃ liêụ tƣ̀ các file . Hai macro này , DECLARE_SERIAL và IMPLEMENT_SERIAL se ̃ bao gói chƣ́ c năng cần thiết trong các lớp của chúng ta để hàm Serialize có thể hoạt động đúng đắn. Macro thƣ́ nhất DECLARE _SERIAL đƣơc̣ đăṭ ngay dòng lêṇ h đầu tiên trong khai báo lớ p (khái niêṃ này giống nhƣ khái niêṃ giao diêṇ của Java ), nó nhận một tham số là tên của lớ p, tác dụng của nó là sẽ thêm vào lớp của chúng ta các khai báo toán tử và hàm cần thiết để chƣ́ c năng serialization hoaṭ đôṇ g đúng đắn. 22
  30. Bài giảng môn học: Lâp̣ triǹ h Windows Ví dụ nhƣ sau: class CMyClass : public CObject { DECLARE_SERIAL (CMyClass) public: virtual void Serialize(CArchive &ar); CMyClass(); virtual ~CMyClass(); }; Macro thƣ́ hai IMPLEMENTATION _SERIAL đƣơc̣ đăṭ trong phần cài đăṭ của lớ p do chúng ta taọ ra . Macro này cần phải đăṭ bên ngoài bất cƣ́ các hàm thành viên nào của lớ p vì nó sẽ thêm mã của các hàm cần thiết cho lớp tƣơng ứng với khai báo của macro DECLARE_SERIAL. Macro này nhâṇ 3 tham số . Tham số thƣ́ nhất là tên lớp , giống nhƣ macro thƣ́ nhất . Tham số thƣ́ hai là tên của lớ p cơ sở (lớ p cha). Tham số thƣ́ ba là môṭ số version có thể đƣơc̣ sƣ̉ duṇ g để xác điṇ h môṭ file có là đúng version để thƣc̣ hiêṇ thao tác đoc̣ vớ i ƣ́ ng duṇ g của chúng ta hay không . Số version này phải là môṭ số dƣơng nên đƣơc̣ tăng lên mỗi lần phƣơng thƣ́ c serializeation của lớ p đƣơc̣ thay đổi thay bất cƣ́ cách thƣ́ c nào làm thay đổi dƣ̃ liêụ đƣơc̣ ghi hoăc̣ đoc̣ tƣ̀ file. Ví dụ về khai báo của macro thứ hai này nhƣ sau: // MyClass.cpp: implementation of the CMyClass class. #include "stdafx.h" #include "MyClass.h" #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[]=__FILE__; #define new DEBUG_NEW #endif IMPLEMENT_SERIAL (CMyClass, CObject, 1) ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// 23
  31. Bài giảng môn học: Lâp̣ triǹ h Windows CMyClass::CMyClass() { } CMyClass::~CMyClass() { } 2.1.4 Cài đăṭ hàm Serialize Cùng với hai macro chúng ta cần cài đặt hàm Serialize trong lớp của chúng ta . Hàm này nên đƣơc̣ khai báo là hàm kiểu void vớ i môṭ tham số (CArchive &ar), kiểu public và là hàm virtual. Khi cài đăṭ hàm này ch úng ta thƣờng sử dụng cách tiếp cận giống nhƣ cách tƣơng tự đƣơc̣ dùng trong lớ p document ở trên, có nghĩa là cần phải kiểm tra đó là thao tác ghi hay đọc trƣớ c khi thƣc̣ hiêṇ thao tác. 2.2 Cài đặt một lớp Serializable Khi chú ng ta bắt đầu thiết kế môṭ ƣ́ ng duṇ g mớ i , môṭ trong nhƣ̃ng điều đầu tiên mà chúng ta cần thiết kế là cách thức chứa dữ liệu trong lớp document , dƣ̃ liêụ mà chƣơng trình sẽ tạo ra và thao tác với . Nếu chúng ta taọ ra môṭ ƣ́ ng duṇ g daṇ g data -oriented chƣ́ a môṭ tâp̣ dƣ̃ liêụ tƣ̀ ngƣờ i dùng chẳng haṇ nhƣ môṭ ƣ́ ng duṇ g cở sở dƣ̃ liêụ chẳng haṇ làm thế nào để chƣ́ a các dƣ̃ liêụ đó trong bô ̣nhớ của chƣơng trình . Cách tổ chức dữ liệu nhƣ thế nào nế u chúng ta làm việc với một ứng dụng soạn thảo văn bản hoặc ứng dụng xử lý bảng tính? Khi chúng ta đa ̃ xác điṇ h đƣơc̣ cách thiết kế các cấu trúc dƣ̃ liêụ mà chƣơng trình se ̃ dùng chúng ta có thể quyết định đƣợc cách tố t nhất để thƣc̣ hiêṇ chƣ́ c năng serialize cho chƣơng trình và các lớ p trong chƣơng trình . Nếu chúng ta quyết điṇ h lƣu trƣc̣ tiếp tất cả dƣ̃ liêụ của chƣơng trình trong lớ p document thì tất cả nhƣ̃ng gì chúng ta cần là ghi dƣ̃ liê ̣ u và đoc̣ dƣ̃ liêụ vớ i đối tƣơṇ g CArchive trong hàm Serialize của lớ p document . Nếu chúng ta taọ ra lớ p riêng để lƣu các dƣ̃ liêụ của chƣơng trình (nhƣ chúng ta đa ̃ làm ở bài thƣc̣ hành số 10) chúng ta cần phải thêm chức năng se rializable cho các lớ p này để chúng có thể tƣ ̣ ghi và đoc̣ tƣ̀ file. Trong ƣ́ ng duṇ g mà chúng ta se ̃ xây dƣṇ g ở bài thƣc̣ hành này chúng ta se ̃ viết môṭ chƣơng trình cơ sở dƣ̃ liêụ daṇ g đơn giản , flat-file để minh hoạ cách thƣ́ c kết hơp̣ các kiểu dƣ̃ liêụ khác nhau trong môṭ luồng dƣ̃ liêụ trong ƣ́ ng duṇ g serialization . Ứng dụng của chúng ta sẽ hiển thị một số các trƣờng dữ liệu có kiểu khác nhau , ghi và đoc̣ dƣ̃ liêụ tƣ̀ môṭ luồng dƣ̃ liêụ duy nhất vớ i đối tƣơṇ g CArchive. 2.2.1 Thiết kế giao diêṇ chƣơng triǹ h Chúng ta có thể tạo ra các lớp của riêng mình , các lớp này có thể serialized , có thể sử dụng với các ứng dụng SDI hoặc MDI. Nói ngắn gọn bất cứ ứng dụng nào làm việc với bất cứ kiểu dƣ̃ liêụ nào, dù đó là một cơ sở dữ liệu hoặc một tài liệu đều có thể serialized. Chúng ta tiến hành các bƣớc sau: 1. Tạo một project dạng SDI có tên là Serialize 2. Trong phần Document Template String gõ phần File Extension là fdb 24
  32. Bài giảng môn học: Lâp̣ triǹ h Windows 3. Phần Advanced features bỏ dấu check ActiveX control 4. Phần Generated Classes choṇ lớ p cơ sở của lớ p CSerialzeView là CFormView 5. Nhấn Finish , chúng ta sẽ nhận đƣợc thông báo là chƣơng trình sẽ không có chƣ́ c năng in ấn, nhấn Yes để tiếp tuc̣ Sau khi taọ môṭ ƣ́ ng duṇ g SDI hoăc̣ MDI trong đó lớ p view kế thƣ̀ a tƣ̀ lớ p CFormView chúng ta cần thiết kế form view của chƣơng trình trình , quá trình này cũng giống nhƣ thiết kế các hộp thoại trong các ƣ́ ng duṇ g daṇ g Dialog based nhƣng chúng ta không cần có các nút để thoát khỏi chƣơng trình hoặc hủy bỏ quá trình thực hiện nhƣ trên các hộp thoại thông thƣờng . Vớ i môṭ ƣ́ ng duṇ g SDI hoăc̣ MDI chƣ́ c năng ghi và thoát cƣ̉ a sổ đƣơc̣ đăṭ trên các hê ̣thống menu chƣơng trình hoăc̣ trên các thanh công cu.̣ Chú ý : Nếu chúng ta làm viêc̣ vớ i các ƣ́ ng duṇ g daṇ g Dialog based App Wizard se ̃ không cung cấp các đoaṇ ma ̃ serialization cho ƣ́ ng duṇ g chúng ta cần p hải tự thực hiện điều này nếu muốn. Thiết kế form của chƣơng trình gồm các điều khiển nhƣ bảng sau: Object Property Setting Static Text ID IDC_STATIC Caption &Name: Edit Box ID IDC_ENAME Static Text ID IDC_STATIC Caption &Age Edit Box ID IDC_EAGE Static Text ID IDC_STATIC Caption Marital Status: Radio Button ID IDC_RSINGLE Caption &Single Group Checked Radio Button ID IDC_RMARRIED Caption &Married Radio Button ID IDC_RDIVORCED Caption &Divorced Radio Button ID IDC_RWIDOW Caption &Widowed Check Box ID IDC_CBEMPLOYED Caption &Employed Button ID IDC_BFIRST Caption &First 25
  33. Bài giảng môn học: Lâp̣ triǹ h Windows Button ID IDC_BPREV Caption &Previous Button ID IDC_BNEXT Caption Nex&t Button ID IDC_BLAST Caption &Last Static Text ID IDC_SPOSITION Caption Record 0 of 0 Giao diêṇ chƣơng trình sau khi thiết kế trong nhƣ sau: Khi chúng ta phát triển các ƣ́ ng duṇ g hoăc̣ cƣ̉ a sổ kiểu dialog -based chúng ta sẽ gắn các biến cho các điều khiển trên cƣ̉ a sổ của lớ p hôp̣ thoaị . Tuy nhiên đối vớ i môṭ ƣ́ ng duṇ g daṇ g SDI hoăc̣ MDI chúng ta se ̃ gắn cho lớ p nào ? Vì hàm UpdateData là một hàm của lớp CWnd và lớp view là một hậu duệ của lớ p CWnd, măc̣ dù lớ p document thì không phải , nên lớp view se ̃ là nơi logic nhất để đăṭ các biến gắn vớ i các điều khiển trên cƣ̉ a sổ chƣơng trình. Để gán các biến cho các điều khiển của chƣơng trình chúng ta mở Class Wizard v à gắn các biến cho các điều khiển và chỉ định lớp chứa chúng là lớp view cụ thể ở ứng dụng này là CSerializeView. Các biến đƣợc gán cho các điểu khiển nhƣ sau: Object Name Category Type IDC_CBEMPLOYED m_bEmployed Value BOOL IDC_EAGE m_iAge Value int IDC_ENAME m_sName Value CString IDC_RSINGLE m_iMaritalStatus Value int 26
  34. Bài giảng môn học: Lâp̣ triǹ h Windows IDC_SPOSITION m_sPosition Value CString Nếu nhƣ chúng ta kiểm tra ma ̃ chƣơng trình của lớ p view chúng ta se ̃ thấy rằng không có hàm OnDraw. Nếu chúng ta sƣ̉ duṇ g lớ p tổ tiên CFormView cho ƣ́ ng duṇ g SDI hoăc̣ MDI chún g ta không cần lo lắng về hàm OnDraw . Thay vào đó chúng ta se ̃ đối xƣ̉ vớ i lớ p view rất giống vớ i lớ p hôp̣ thoaị nhƣ trong môṭ cƣ̉ a s ổ hộp thoại hoặc một ứng dụng dạng dialog - based. Sƣ ̣ khác nhau chủ yếu ở đây là dƣ̃ liêụ chƣơng trình mà chúng ta cần sƣ̉ duṇ g để gán cho các điều khiển của cƣ̉ a sổ chƣơng trình không phải là của lớ p view mà là của lớ p document. Do đó chúng ta cần xâu dƣṇ g các tƣơng tác giƣ̃a các lớ p này để truyền các dƣ̃ liêụ giƣ̃a chúng. 2.2.2 Tạo lớp Serializable Khi chúng ta taọ ra môṭ ƣ́ ng duṇ g daṇ g form-based chúng ta se ̃ giả sƣ̉ rằng chƣơng trình sẽ chứa nhiều bản ghi trong form và ngƣờ i dùng có thể duyêṭ qua các bản ghi này để thƣc̣ hiêṇ các thay đổi và ghi laị . Ngƣờ i dùng cũng có thể thêm vào các bản ghi mớ i và loaị bỏ bản ghi nào đó . Thách thức đối với chúng ta trong viêc̣ xây dƣṇ g ƣ́ ng duṇ g là làm thế nào để biểu diêñ các bản ghi đó để có thể hỗ trơ ̣ tất cả các chƣ́ c năng của chƣơng trình. Môṭ cách tiếp câṇ là taọ ra môṭ lớ p bao gói tất cả các bản ghi và sau đó chƣ́ a tất cả các bản ghi trong một mảng giống nhƣ cách mà chúng ta đã thực hiện với các ứng dụng trong ba bài thực hành trƣớc đây . Lớ p này se ̃ cần kế thƣ̀ a tƣ̀ lớ p CObject và cần chƣ́ a các biến cho tất cả các biến tƣơng ứng với các điều k hiển đƣơc̣ thêm vào lớ p view cùng vớ i phƣơng thƣ́ c đoc̣ và ghi tất cả các biến này. Cùng với việc thêm vào các phƣơng thức đọc và ghi các biến chúng ta cần phải làm cho lớ p này là môṭ lớ p serialiable bằng các thêm vào hàm Serialize cho lớp cùng với hai macro đã mô tả ở trên. Tạo một lớp cơ bản Nhƣ các baṇ có thể nhớ tƣ̀ bài thƣc̣ hành số 10 khi chúng ta muốn taọ ra môṭ lớ p mới chúng ta cần chọn tab Class View và nhấn chuột phải chọn Add | Add Class. Sau đó trong hôp̣ thoại tiếp theo chọn đó là một Generic class , chúng ta sẽ học nhiều hơn về các lớp này trong bài thực hành số 16. Tiếp theo chúng ta gõ tên lớ p là CPerson , lớ p cơ sở của lớ p là lớp CObject. Sau khi ta ọ xong lớ p chúng ta tiếp tuc̣ thêm các biến thành viên cho lớ p , các biến thành viên này cần phải khớp với các biến đƣợc gán cho các điều khiển trên cửa sổ chƣơng trình trong lớp view nhƣ trong bảng sau: Name Type m_bEmployed BOOL m_iAge int m_sName CString m_iMaritalStatus int Cài đặt phƣơng thức đọc và ghi cho lớp CPerson Sau khi đa ̃ taọ ra lớ p CPerson chúng ta se ̃ cung cấp môṭ phƣơng tiêṇ để đoc̣ và ghi các biến cho lớ p. Cách dễ nhất để cung cấp các chức năng này là thêm các hàm inline trong định nghĩa của lớp, chúng ta có thể thêm một tập cá c hàm inline để thiết lâp̣ và lấy giá tri ̣của các 27
  35. Bài giảng môn học: Lâp̣ triǹ h Windows biến (riêng biêṭ ). Ví dụ chúng ta có thể mở file header (Person.h) và sửa lại khai báo của lớp CPerson nhƣ sau: class CPerson : public CObject { public: // Functions for setting the variables void SetEmployed(BOOL bEmployed) { m_bEmployed = bEmployed;} void SetMaritalStat(int iStat) { m_iMaritalStatus = iStat;} void SetAge(int iAge) { m_iAge = iAge;} void SetName(CString sName) { m_sName = sName;} // Functions for getting the current settings of the variables BOOL GetEmployed() { return m_bEmployed;} int GetMaritalStatus() { return m_iMaritalStatus;} int GetAge() {return m_iAge;} CString GetName() {return m_sName;} CPerson(); virtual ~CPerson(); private: BOOL m_bEmployed; int m_iMaritalStatus; int m_iAge; CString m_sName; }; Hàm cấu tử mặc định đƣợc Visual C++ tƣ ̣ đôṇ g cung cấp và chúng ta không cần sƣ̉ a đổi (vớ i Visual 6.0 thì vẫn cần thêm hàm này vào). 2.2.3 Cài đặt hàm Serialize Tiếp đến chúng ta se ̃ thêm hàm Serialize cho lớ p bắt đầu bằng viêc̣ thêm 2 macro vào đúng vi ̣trí của chúng trong các file khai báo và cài đăṭ của lớ p CPerson , sau đó thêm hàm virtual, public Serialize(CArchive &ar) cho lớ p CPerson nhƣ sau: void CPerson::Serialize(CArchive &ar) { // Call the ancestor function CObject::Serialize(ar); // Are we writing? 28
  36. Bài giảng môn học: Lâp̣ triǹ h Windows if (ar.IsStoring()) // Write all of the variables, in order ar > m_sName >> m_iAge >> m_iMaritalStatus >> m_bEmployed; } Về bản chất hàm này cũng khá đơn giản , ban đầu nó goị tớ i phƣơng thƣ́ c Serialize của lớ p cha để xác điṇ h xem đó là thao tác đoc̣ hay ghi dƣ̃ liêụ , sau đó nó goị tớ i hàm IsStoring để xác định nếu là ghi dữ liệu thì thực hiện ghi các biến thành viên của lớp còn ngƣợc lại thì thực hiêṇ thao tác đoc̣ (chú ý thứ tự của các biến là quan trọng). Chú ý: chúng ta biết là một ứng dụng không phải khi nào cũng có thể đọc hoặc ghi dữ liêụ thành công nên trên thƣc̣ tế khi chúng ta tiến hành thƣc̣ hiêṇ hàm trên se ̃ có môṭ Exception đƣơc̣ throw để kiểm soát lỗi nhƣng ở đây chúng ta tạm thời bỏ qua vấn đề này (xem phu ̣luc̣ A cuốn Teach yourself Visual C++ 6.0 in 21 days để biết thêm chi tiết). Cài đặt các chức năng cho lớp document Khi chúng ta xây dƣṇ g môṭ ƣ́ ng duṇ g daṇ g form-based trong đó form nằm trên cƣ̉ a sổ là không gian chính để ngƣờ i dùng có thể tƣơng tác vớ i ƣ́ ng duṇ g có môṭ giả sƣ̉ không đƣơc̣ đề câp̣ rõ ràng là ƣ́ ng duṇ g của chúng ta se ̃ cho phép ngƣờ i dùng làm viêc̣ vớ i nhiều bản ghi . Điều này có nghiã là chúng ta cần hỗ trợ các tính năng lƣu và duyệt qua các bản ghi này . Viêc̣ lƣu các bản ghi có thể thƣc̣ hiêṇ dê ̃ dàng bằng cách sƣ̉ duṇ g môṭ mảng nhƣ chúng ta đa ̃ tƣ̀ ng làm trong bài thực hành số 10. Cách làm này cho phép chúng ta có thể thêm vào môṭ số lƣơṇ g bản ghi không hạn chế (không biết có đúng không nƣ̃a). Viêc̣ duyêṭ qua các bản ghi đƣơc̣ thƣc̣ hiêṇ qua bốn thao tác là First (duyêṭ bản ghi đầu tiên), Last (bản ghi cuối cùng), Previous (bản ghi trƣớ c) và Next (bản ghi tiếp theo). Chúng ta cần một chức năng thông báo để xác định bản ghi nào đang đƣơc̣ hiển thi.̣ Để lƣu trƣ̃ và hỗ trơ ̣ các tính năng này lớ p document cần hai biến : môṭ mảng và môṭ chỉ số bản ghi hiêṇ taị nhƣ bảng sau: Name Typ e m_iCurP int osition m_oaPeo CO ple bArray Môṭ viêc̣ khác chúng ta cần làm là include file header của lớ p CPerson vào file cài đăṭ của lớp document (vị trí trƣớc các file header của lớp document và view) nhƣ sau: #include "stdafx.h" #include "Serialize.h" #include "Person.h" 29
  37. Bài giảng môn học: Lâp̣ triǹ h Windows #include "SerializeDoc.h" #include "SerializeView.h" Thêm môṭ bản ghi mớ i Trƣớ c khi chúng ta có thể duyêṭ qua các bản ghi trong chƣơng trình chúng ta cần xây dƣṇ g chƣ́ c năng thêm môṭ bản ghi mớ i cho mảng các đối tƣơṇ g . Cách tiếp cận tƣơng tự nhƣ bài thực hành sẽ đƣợc sử dụng , và vì các bản ghi mặc định đều có các trƣờng dữ liệu là rỗng nên chúng ta chỉ cần sƣ̉ duṇ g hàm cấu tƣ̉ măc̣ điṇ h do Visual C ++ cung cấp là đủ , đồng thời mỗi khi thêm vào môṭ bản ghi mớ i chúng ta se ̃ gán bản ghi hiêṇ taị là bản ghi mớ i đó (hàm này là private). CPerson* CSerializeDoc::AddNewRecord(void) { // Create a new CPerson object CPerson *pPerson = new CPerson(); try { // Add the new person to the object array m_oaPeople.Add(pPerson); // Mark the document as dirty SetModifiedFlag(); // Set the new position mark m_iCurPosition = (m_oaPeople.GetSize() - 1); } // Did we run into a memory exception? catch (CMemoryException* perr) { // Display a message for the user, giving them the // bad news AfxMessageBox("Out of memory", MB_ICONSTOP | MB_OK); // Did we create a line object? if (pPerson) { // Delete it delete pPerson; pPerson = NULL; } 30
  38. Bài giảng môn học: Lâp̣ triǹ h Windows // Delete the exception object perr->Delete(); } return pPerson; } Tƣơng tƣ ̣ nhƣ bài thƣc̣ hành số 10 chúng ta cần các hàm lấy tổng số bản ghi , số thƣ́ tƣ ̣ bà đối tƣợng tƣơng ứng với bản ghi hiện tại nhƣ sau (các hàm này là public): int CSerializeDoc::GetTotalRecords(void) { return m_oaPeople.GetCount(); } int CSerializeDoc::GetCurRecordNbr(void) { return m_iCurPosition + 1; } CPerson* CSerializeDoc::GetCurRecord(void) { // Are we editing a valid record number? if (m_iCurPosition >= 0) // Yes, return the current record return (CPerson*)m_oaPeople[m_iCurPosition]; else // No, return NULL return NULL; } Các chức năng tiếp theo cần đƣợc cài đặt là các hàm cho phép thực hiện các thao tác lấy các bản ghi của mảng một cách tƣơng đối (đầu, cuối, trƣớ c, sau): CPerson* CSerializeDoc::GetFirstRecord(void) { // Are there any records in the array? if (m_oaPeople.GetSize() > 0) { 31
  39. Bài giảng môn học: Lâp̣ triǹ h Windows // Yes, move to position 0 m_iCurPosition = 0; // Return the record in position 0 return (CPerson*)m_oaPeople[0]; }else // No records, return NULL return NULL; } CPerson* CSerializeDoc::GetNextRecord(void) { // After incrementing the position marker, are we // past the end of the array? if (++m_iCurPosition 0) { // Once we decrement the current position, // are we below position 0? if ( m_iCurPosition < 0) // If so, set the record to position 0 m_iCurPosition = 0; // Return the record at the new current position return (CPerson*)m_oaPeople[m_iCurPosition]; }else // No records, return NULL 32
  40. Bài giảng môn học: Lâp̣ triǹ h Windows return NULL; } CPerson* CSerializeDoc::GetLastRecord(void) { // Are there any records in the array? if (m_oaPeople.GetSize() > 0) { // Move to the last position in the array m_iCurPosition = (m_oaPeople.GetSize() - 1); // Return the record in this position return (CPerson*)m_oaPeople[m_iCurPosition]; }else // No records, return NULL return NULL; } Tiếp đến là hàm Serialize cho mảng các đối tƣơṇ g của lớ p document (CSerializeDoc): void CSerializeDoc::Serialize(CArchive& ar) { // Pass the serialization on to the object array m_oaPeople.Serialize(ar); } Hàm làm công tác môi trƣờng, dọn dẹp tất cả mọi thứ trƣớc khi bắt đầu một tài liệu mới (hàm này đƣợc gọi tới khi chƣơng trình kết thúc hoăc̣ trƣớ c khi môṭ tài liêụ mớ i đƣơc̣ mở ): void CSerializeDoc::DeleteContents() { // TODO: Add your specialized code here and/or call the base class // Get the number of lines in the object array int liCount = m_oaPeople.GetSize(); int liPos; // Are there any objects in the array? if (liCount) { 33
  41. Bài giảng môn học: Lâp̣ triǹ h Windows // Loop through the array, deleting each object for (liPos = 0; liPos NewDataSet(); return TRUE; } Khi môṭ tài liêụ mớ i bắt đầu chƣơng trình se ̃ đƣa ra môṭ form rỗng sẵn sàng để nhâp̣ thông tin mớ i , và để bản ghi này có thể sẵn sàng nhận thông tin chúng ta thêm vào môṭ bản ghi trong mảng các đối tƣơṇ g và khi môṭ bản ghi mớ i đƣơc̣ thêm vào mảng chúng ta cần thay đổi viêc̣ hiển thi ̣để chỉ ra rằng bản ghi mớ i đó tồn taị ngƣơc̣ laị các hiển thi ̣se ̃ tiếp tuc̣ với bả n 34
  42. Bài giảng môn học: Lâp̣ triǹ h Windows ghi cuối cùng tƣ̀ tâp̣ bản ghi trƣớ c (và ngƣời dùng có thể băn khoăn tại sao ứng dụng của chúng ta không bắt đầu với một tập bản ghi mới). Khi mở môṭ tâp̣ dƣ̃ liêụ sẵn có chúng ta không cần thêm vào bất cƣ́ bản ghi mớ i nà o nhƣng vâñ cần phải cho đối tƣơṇ g view biết rằng nó cần phải làm tƣơi bản ghi đƣơc̣ hiển thi ̣ cho ngƣờ i dùng . Do đó chúng ta có thể thêm đoaṇ ma ̃ tƣơng tƣ ̣ cho hàm OnOpenDocument nhƣ sau (bỏ phần đầu có chức năng thêm vào một bản ghi mới) nhƣ sau: BOOL CSerializeDoc::OnOpenDocument(LPCTSTR lpszPathName) { if (!CDocument::OnOpenDocument(lpszPathName)) return FALSE; // TODO: Add your specialized creation code here // Get a pointer to the view POSITION pos = GetFirstViewPosition(); CSerializeView* pView = (CSerializeView*)GetNextView(pos); // Tell the view that it's got a new data set if (pView) pView->NewDataSet(); return TRUE; } Đó là tất cả các công viêc̣ chuẩn bi ,̣ tổ chƣ́ c và xƣ̉ lý dƣ̃ li ệu của lớp document, tiếp đến chúng ta sẽ làm việc với lớp view để tƣơng tác với ngƣời dùng. Điều đầu tiên cần chú ý là các include trong các file ma ̃ nguồn cần theo đúng thƣ́ tƣ ̣ (giống bài thƣc̣ hành số 10): lớ p CPerson trƣớ c, sau đó tớ i lớ p document và cuối cùng là lớp view và các chỉ thi ̣include này chỉ thƣc̣ hiêṇ trong các file cài đăṭ lớ p (khác với C/C++ thông thƣờ ng) #include "stdafx.h" #include "Serialize.h" #include "Person.h" #include "SerializeDoc.h" #include "SerializeView.h" Vì số lƣợng các thao tác đối với các bản ghi là khá nhiều nên cũng giống nhƣ bài thực hành 10 (sƣ̉ duṇ g 1 biến lƣu điểm hiêṇ taị của con trỏ chuôṭ ) trong bài thƣc̣ hành này để cho tiêṇ chúng ta thêm môṭ biến thành viên kiểu CPerson * có tên là m_pCurPerson cho lớ p View. 35
  43. Bài giảng môn học: Lâp̣ triǹ h Windows Hàm đầu tiên mà chúng ta sẽ thực hiện là hàm hiển thị dữ liệu , nhƣng chƣ́ c năng này đƣơc̣ sƣ̉ duṇ g trong hầu hết các tƣơng tác nên chúng ta se ̃ l àm một hàm riêng để sau đó gọi đến hàm này (giống hàm Draw của lớ p CLine trong bài thƣc̣ hành 10 về chƣ́ c năng ) (hàm private): void CSerializeView::PopulateView(void) { // Get a pointer to the current document CSerializeDoc* pDoc = GetDocument(); if (pDoc) { // Display the current record position in the set m_sPosition.Format("Record %d of %d", pDoc->GetCurRecordNbr(), pDoc->GetTotalRecords()); } // Do we have a valid record object? if (m_pCurPerson) { // Yes, get all of the record values m_bEmployed = m_pCurPerson->GetEmployed(); m_iAge = m_pCurPerson->GetAge(); m_sName = m_pCurPerson->GetName(); m_iMaritalStatus = m_pCurPerson->GetMaritalStatus(); } // Update the display UpdateData(FALSE); } Tiếp đến là các hàm duyệt qua các bản ghi , đồng thờ i cũng là các hàm xƣ̉ lý các sƣ ̣ kiêṇ tƣơng ƣ́ ng vớ i các nút lêṇ h: void CSerializeView::OnBnClickedBfirst() { // TODO: Add your control notification handler code here // Get a pointer to the current document CSerializeDoc * pDoc = GetDocument(); if (pDoc) { 36
  44. Bài giảng môn học: Lâp̣ triǹ h Windows // Get the first record from the document m_pCurPerson = pDoc->GetFirstRecord(); if (m_pCurPerson) { // Display the current record PopulateView(); } } } void CSerializeView::OnBnClickedBlast() { // TODO: Add your control notification handler code here // Get a pointer to the current document CSerializeDoc * pDoc = GetDocument(); if (pDoc) { // Get the last record from the document m_pCurPerson = pDoc->GetLastRecord(); if (m_pCurPerson) { // Display the current record PopulateView(); } } } void CSerializeView::OnBnClickedBprev() { // TODO: Add your control notification handler code here // Get a pointer to the current document CSerializeDoc * pDoc = GetDocument(); if (pDoc) { 37
  45. Bài giảng môn học: Lâp̣ triǹ h Windows // Get the last record from the document m_pCurPerson = pDoc->GetPrevRecord(); if (m_pCurPerson) { // Display the current record PopulateView(); } } } void CSerializeView::OnBnClickedBnext() { // TODO: Add your control notification handler code here // Get a pointer to the current document CSerializeDoc * pDoc = GetDocument(); if (pDoc) { // Get the last record from the document m_pCurPerson = pDoc->GetNextRecord(); if (m_pCurPerson) { // Display the current record PopulateView(); } } } Tiếp đến chúng ta cần môṭ hàm reset laị lớ p view mỗi khi môṭ bản ghi mớ i đƣơc̣ bắt đầu hoăc̣ đƣơc̣ mở để ngƣờ i không tiếp tuc̣ nhìn thấy tâp̣ bản ghi cũ . Chúng ta có thể gọi tới hàm xƣ̉ lý sƣ ̣ kiêṇ của nút First để buôc̣ lớ p view đƣa ra bản ghi đầu tiên trong tâp̣ bản ghi . Để làm điều này chúng ta thêm môṭ hàm void (pubic) tên là NewDataSet nhƣ sau: void CSerializeView::NewDataSet(void) { OnBnClickedBfirst(); } 38
  46. Bài giảng môn học: Lâp̣ triǹ h Windows Đến đây chúng ta có thể dic̣ h và chaỵ chƣơng trình nhƣng các baṇ se ̃ thấy chỉ các các nút duyệt qua các bản ghi là có tác dụng còn các điều khiển khác của form là không có tác dụng gì. Điều nà y là do chúng ta chƣa có các hàm xƣ̉ lý các điều khiển trên form . Cần thêm các hàm xử lý các sự kiện với các điều khiển trên form chƣơng trình nhƣ sau: Hàm xử lý dấu check Employed: void CSerializeView::OnBnClickedCbemployed() { // TODO: Add your control notification handler code here UpdateData(TRUE); // If we have a valid person object, pass the data changes to it if (m_pCurPerson) m_pCurPerson->SetEmployed(m_bEmployed); } hàm xử lý các sự kiện cho các nút Radio: void CSerializeView::OnBnClickedMaritalstatus() { // TODO: Add your control notification handler code here UpdateData(TRUE); // If we have a valid person object, pass the data changes to it if (m_pCurPerson) m_pCurPerson->SetMaritalStat(m_iMaritalStatus); } Đối với các trƣờng tên và tuổi chúng ta cần xử lý sự kiện EN _CHANGE và goị tới các hàm SetName, SetAge tƣơng ƣ́ ng của lớ p CPerson nhƣ sau: void CSerializeView::OnEnChangeEname() { // TODO: Add your control notification handler code here UpdateData(TRUE); // If we have a valid person object, pass the data changes to it if (m_pCurPerson) m_pCurPerson->SetName(m_sName); } void CSerializeView::OnEnChangeEage() { 39
  47. Bài giảng môn học: Lâp̣ triǹ h Windows // TODO: Add your control notification handler code here UpdateData(TRUE); // If we have a valid person object, pass the data changes to it if (m_pCurPerson) m_pCurPerson->SetAge(m_iAge); } Hãy dịch chạy thử và nhập dữ liệu vào để thấy các chức năng của chƣơng trình đã chạy đúng (đây quả là môṭ bài thƣc̣ hành không dê ̃ dàng). 3. Quản lý file và thƣ mục nâng cao Bài tập: Bài tập 1: Viết chƣơng trình mô phỏng tìm kiếm file. Bài tập 2: Viết chƣơng trình liệt kê tất cả các thông tin về các file và thƣ mục trong một thƣ mục. 40
  48. Bài giảng môn học: Lâp̣ triǹ h Windows Chƣơng 3: Hê ̣thố ng CSDL Registry 1. Khái niệm và vai trò của CSDL Registry 1.1 Các khóa, các hive Registry là nơi lƣu trữ tất cả các các loại cấu trúc dữ liệu. Cấu hình hệ thống Windows, cấu hình phần cứng máy tính, cấu hình thông tin về các chƣơng trình ứng dụng dựa trên Win32, và các thiết lập ngƣời dùng khác đều đƣợc lƣu trong Registry. Ví dụ, bất cứ một phần cứng máy tính nào thay đổi đều làm chức năng Plug and Play (Cắm và chạy) khởi tạo ngay và làm thay đổi luôn cấu hình trong Registry. Registry lƣu trữ tất cả các thiết lập về cấu trúc bộ nhớ, phần cứng, thiết bị ngoại vi, và các thành phần liên quan đến mạng. Bạn sẽ tìm thấy ở đó nhiều hơn những thiết lập cần thiết trong các tệp khởi tạo ban đầu Từ Win98 về sau, Windows có sử dụng Registry Checker để tự quét Registry, nếu không thấy gì, nó tự lƣu backup một lần trong ngày, nếu tìm thấy lỗi sẽ sửa có thể sửa bằng cách thay thế bản Registry đã backup gần nhất còn tốt. Registry Checker tối ƣu hoá và nén file backup thành công mỗi lần khởi động máy. Nó còn làm một loạt các việc linh tinh nhƣ loại bỏ những khoảng trống không dùng trong Registry, tối ƣu hoá 41
  49. Bài giảng môn học: Lâp̣ triǹ h Windows Các tệp Registry của Windows. Registry hiện tại bao gồm 3 tệp chính: 1. Tệp USER.DAT Dùng để lƣu trữ những xác lập ngƣời sử dụng đối với các phần mềm. 2. Tệp SYSTEM.DAT Dùng để lƣu trữ những xác lập liên quan tới máy tính và phần cứng. 3. Tệp Policy.pol System policies đƣợc thiết kế để chuẩn bị cho việc ghi đè bất cứ thiết lập đã đƣợc chứa trong 2 thành phần registry khác nhau. System policies có thể chứa dữ liệu bổ sung đặc trƣng tới mạng hay môi trƣờng tổ hợp nhƣ đã đƣợc cài đặt bởi network administrator. Bản thân System policies cũng đã đƣợc chứa trong tệp Policy.pol. Không nhƣ SYSTEM.DAT và USER.DAT, Policy.pol không phải là thành phần bắt buộc của phần cài đặt Windows. Các khóa chính trong một CSDL Registry: + HKEY_LOCAL_MACHINE chứa các thông tin về cấu hình vật lý của hệ thống cùng với các phần mềm đã đƣợc cài đặt trên hệ thống. + HKEY_USERS: chứa các thông tin cấu hình của tài khoản ngƣời dùng + HKEY_CURRENT_CONFIG: chứa các thông tin thiết lập của hệ thống hiện tại chẳng hạn nhƣ độ phân giải màn hình hay font chữ. + HKEY_CLASS_ROOT: chứa các thông tin ánh xạ từ các kiểu file sang các ứng dụng mở chúng. HKEY_CURRENT_USER: chứa các thông tin về các tài khoản trên hệ thống, chẳng hạn nhƣ các biến môi trƣờng, các máy in và các tùy chọn ứng dụng khác. 1.2 Các kiểu dữ liệu Lời khuyên của Microsoft về những công cụ xử lý registry 42
  50. Bài giảng môn học: Lâp̣ triǹ h Windows Phƣơng pháp Thiết lập Phần lớn thiết lập hệ thống SYSTEM. Ví dụ bạn sử dụng Display Control Panel Properties để sửa các thành phần của mục appearance System Policy Editor Thiết lập ngƣời dùng, vài thiết lập hệ thống. Các chƣơng trình tiện ích thứ 3 Thiết lập chi tiết ứng dụng Bạn có thể đã sử dụng Registry Editor để thay đổi Registry bằng tay. Tôi thƣờng dùng Norton Registry Editor vì nó còn có thêm chức năng khác, ví dụ nhƣ tìm và thay thế đối với các thành phần của Registry. Từ các phần mềm Registry Editor trên, ta nhận thấy registry đƣợc bố trí thành các nhánh lớn. Tại mỗi nhánh có các khoá SUBKEY. Tại các SUBKEY dữ liệu đƣợc lƣu ở các dạng: 1. String (Dạng chuỗi) 2. Numeric (Dạng số) 3. Binary (Dạng nhị phân) 4. Expanded String (Dạng chuỗi mở rộng) 5. MultiString (Dạng chuỗi tổng hợp) (Nếu bạn dùng Registry Editor - REGEDIT. EXE thì sẽ gọi tên khác là DWORD) 2. Quản lý CSDL Registry Khi lập trình đối với Registry, bạn phải thực hiện hết sức thận trọng, sao lƣu các tệp này thƣờng xuyên để tránh lỗi đáng tiếc, phải mất công cài lại thì cũng rất mất thời gian.Sử dụng các hàm API đối với Registry cũng xin hết sức thận trọng. 2.1 Thay đổi khó a Để thay đổi giá trị của một khóa trong CSDL Registry chúng ta sử dụng hàm LONG RegSetValueEx(HKEY hKey, LPCTSTR lpValueName, DWORD reserved, DWORD dwType, CONST BYTE * lpData, CONST cbData). 2.2 Thêm mớ i khó a Để làm việc với các khóa trong CSDL Registry đầu tiên chúng ta sẽ quan tâm tới các hàm thêm khóa mới: LONG RegOpenKeyEx(HKEY hKey, LPCTSTR lpSubKey, DWORD ulOptions, REGSAM samDesired, PHKEY phkResult). Trong đó tham số thứ nhất là handle trỏ tới khóa đang mở, tham số phkResult trỏ tới một biến có kiểu HKEY cho khóa có thểmớ mới, lpSubKey là tên của subkey mà chúng ta muốn mở, thông thƣờng có thể là một đƣờng dẫn chăng hạn nhƣ “Microsoft\WindowsNT\CurrentVersion”. Giá trị NULL cho biến này có nghĩa là một khóa bằng giá trị hKey sẽ đƣợc sinh ra. Biến ulOptions là biến dự trữ và có giá trị bằng 0. Biến samDesired là mặt nạ truy cập mô tả giá trị bảo mật cho khóa mới, có thể là kết hợp của các giá trị hằng số KEY_ALL_ACCESS, KEY_WRITE, KEY_QUERY_VALUE, KEY_ENUMERATE_SUBKEYS. Giá trị trả về của hàm thƣờng là ERROR_SUCCESS. Cũng có thể dùng hàm LONG RegCreateKeyEx( HKEY hKey, LPCTSTR lpSubKey, DWORD Reserved, LPTSTR lpClass, DWORD dwOptions, REGSAM samDesired, PSECURITY_ATTRIBUTES lpSecurityAttributes, PHKEY phkResult, LPDWORD lpdwDisposition) để tạo khóa mới. 43
  51. Bài giảng môn học: Lâp̣ triǹ h Windows 2.3 Liêṭ kê cá c khó a Hàm liệt kê các khóa trong CSDL Registry là hàm LONG RegEnumKeyEx(HKEY hKey, DWORD dwIndex, LPTSTR lpName, LPDWORD lpcbName, LPDWORD lpReserved, LPTSTR lpClass, LPDWORD lpcbClass, PFILETIME lpftLastWriteTime). 3. Can thiêp̣ Windows qua Registry Hầu nhƣ tất cả các thay đổi, thiết lập của Windows đều có thể đƣợc thực hiện thông qua việc thiết lập các giá trị trong CSDL Registry, vấn đề cốt lõi là chúng ta cần nắm đƣợc khóa và giá trị cần thay đổi tƣơng ứng. 3.1 Thay đổi giao diêṇ Để ngăn cấm không cho ngƣời dùng thay đổi Wallpaper chúng ta có thể thay đổi giá trị khóa:“HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/policies/ActiveDeskto p” với giá trị DWORD là 1. Để chỉ định file Wallpaper của Windows ta có thể sửa cáckhóa “HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Policies\ System” với các khóa là Wallpaper, WallpaperStyle và giá trị kiểu REG_SZ (String) là đƣờng dẫn tới file ảnh. 3.2 Thay đổi cá c thiết lâp̣ đố i vớ i cá c ổ điã Để thay đổi thiết lập với các ổ đĩa ta thực hiện với cáckhóa HKEY_LOCAL_MACHINE\SYSTEM\MountedDevices có tên là MountedDevices nhƣ hình sau: 3.3 Thay đổi cá c thiết lâp̣ vớ i ngƣời dùng Các biến môi trƣờng của một ngƣời dùng nằm trong phần “HKEY_CURRENT_USER\Environment”, còn cho cả hệ thống nằm trong phần “HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Environment”. Bài tập: Bài tập 1: Viết chƣơng trình thay đổi giao diện Windows bằng cách sử dụng can thiệp qua Registry. Bài tập 2: Viết chƣơng trình thay đổi các biến môi trƣờng của Windows bằng cách sử dụng can thiệp qua Registry. 44
  52. Bài giảng môn học: Lâp̣ triǹ h Windows Chƣơng 4: Quản lý các tiến triǹ h và luồng 1. Các tiến trình và luồng trên Windows Tất cả các tiến trình (process) của Windows đều có một hoặc nhiều luồng (thread) và luồng chính là đơn vị thực thi cơ sở nhất của Windows. Các luồng đƣợc lập lịch dựa trên các nhân tố: sự sẵn sàng của các tài nguyên nhƣ CPU và bộ nhớ vật lý, độ ƣu tiên. Windows hỗ trợ kiến trúc đa xử lý đối xứng SMP (Symmetric MultiProcessing) bắt đầu từ phiên bản NT4, do đó các luồng có thể chạy trên cac CPU riêng rẽ trong cùng một hệ thống. Trên quan điểm của lập trình viên mỗi tiến trình sẽ bao gồm các tài nguyên: + Một hoặc nhiều luồng + Một không gian bộ nhớ ảo riêng. + Một hoặc nhiều đoạn mã, bao gồm của các đoạn mã trong các file DLL. + Một hoặc nhiều phân đoạn dữ liệu chứa các dữ liệu toàn cục. + Các giá trị biến môi trƣờng + Vùng nhớ Heap + Các tài nguyên khác chẳng hạn nhƣ các handle và các heap đã mở sẵn. Mỗi luồng trong một tiến trình sẽ chia sẻ mã chƣơng trình, các biến toàn cục, các biến môi trƣờng, các tài nguyên. Mỗi luồng sẽ đƣợc lập lịch một cách riêng rẽ và có các thành phần sau: + Một ngăn xếp các lời gọi tới các thủ tục, các ngắt, các quản lý biệt lệ, và bộ nhớ + Một mảng TLS (Threa Local Storage) các con trỏ để có thể cấp phát bộ nhớ lƣu trữ dữ liệu cho luồng. + Một tham số trên stack, từ lúc bắt đầu tạo ra luồng, đƣợc sử dụng riêng chomỗi luồng. + Một cấu trúc ngữ cảnh, đƣợc quản lý bởi nhân. 45
  53. Bài giảng môn học: Lâp̣ triǹ h Windows 2. Các thao tác với tiến trình 2.1. Tạo tiến trình Hàm cơ bản để quản lý các tiến trình của Windows là hàm CreateProcess(), hàm này tạo ra một tiến trình với một luồng đơn. Tham số mà hàm cần là tên file chƣơng trình sẽ thực hiện. Chúng ta có thể thấy có nhiều đề cập tới khái niệm tiến trình cha, tiến trình con nhƣng thực sự thì quan hệ này không đƣợc quản lý bởi Windows. Windows đơn thuần chỉ tham chiếu tới các tiến trình tại ra một tiến trình con mà nó là tiến trình cha. Hàm CreateProcess() có 10 tham số: BOOL CreateProcess ( LPCTSTR lpApplicationName, LPTSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpsaProcess, LPSECURITY_ATTRIBUTES lpsaThread, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, 46
  54. Bài giảng môn học: Lâp̣ triǹ h Windows LPCTSTR lpCurDir, LPSTARTUPINFO lpStartupInfo, LPPROCESS_INFORMATION lpProcInfo) 2.2. Kết thú c và thoá t khỏi môṭ tiến triǹ h Sau khi tiến trình kết thúc, tiến trình, hay chính xác hơn là một luồng chạy trong tiến trình sẽ gọi tới hàm ExitProcess() để kết thúc tiến trình. Hàm này không trả về giá trị mà thay vào đó sẽ kết thúc tất cả các luồng của tiến trình. Hàm có thể gọi tới để lấy mã kết thúc một tiến trình là GetExitCodeProcess(). 2.3. Các thao tác với biến môi trƣờng của Windows Để thao tác với các biến môi trƣờng của Windows ta dùng hai hàm sau: DWORD GetEnvironmentVariable ( LPCTSTR lpName, LPTSTR lpValue, DWORD cchValue) BOOL SetEnvironmentVariable ( LPCTSTR lpName, LPCTSTR lpValue) 2.4. Ví dụ: Ghi nhâṭ ký thời gian thƣc̣ hiêṇ củ a cá c tiến triǹ h #include "EvryThng.h" int _tmain (int argc, LPTSTR argv []) { STARTUPINFO StartUp; PROCESS_INFORMATION ProcInfo; union { /* Structure required for file time arithmetic. */ LONGLONG li; FILETIME ft; } CreateTime, ExitTime, ElapsedTime; FILETIME KernelTime, UserTime; SYSTEMTIME ElTiSys, KeTiSys, UsTiSys, StartTimeSys, ExitTimeSys; LPTSTR targv = SkipArg (GetCommandLine ()); OSVERSIONINFO OSVer; BOOL IsNT; HANDLE hProc; OSVer.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); 47
  55. Bài giảng môn học: Lâp̣ triǹ h Windows GetVersionEx (&OSVer); IsNT = (OSVer.dwPlatformId == VER_PLATFORM_WIN32_NT); /* NT (all versions) returns VER_PLATFORM_WIN32_NT. */ GetStartupInfo (&StartUp); GetSystemTime (&StartTimeSys); /* Execute the command line; wait for process to complete. */ CreateProcess (NULL, targv, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, &StartUp, &ProcInfo); /* Assure that we have all REQUIRED access to the process. */ DuplicateHandle (GetCurrentProcess (), ProcInfo.hProcess, GetCurrentProcess (), &hProc, PROCESS_QUERY_INFORMATION | SYNCHRONIZE, FALSE, 0); WaitForSingleObject (hProc, INFINITE); GetSystemTime (&ExitTimeSys); if (IsNT) { /* W NT. Elapsed, Kernel, & User times. */ GetProcessTimes (hProc, &CreateTime.ft, &ExitTime.ft, &KernelTime, &UserTime); ElapsedTime.li = ExitTime.li - CreateTime.li; FileTimeToSystemTime (&ElapsedTime.ft, &ElTiSys); FileTimeToSystemTime (&KernelTime, &KeTiSys); FileTimeToSystemTime (&UserTime, &UsTiSys); _tprintf (_T ("Real Time: %02d:%02d:%02d:%03d\n"), ElTiSys.wHour, ElTiSys.wMinute, ElTiSys.wSecond, ElTiSys.wMilliseconds); _tprintf (_T ("User Time: %02d:%02d:%02d:%03d\n"), UsTiSys.wHour, UsTiSys.wMinute, UsTiSys.wSecond, UsTiSys.wMilliseconds); _tprintf (_T ("Sys Time: %02d:%02d:%02d:%03d\n"), KeTiSys.wHour, KeTiSys.wMinute, KeTiSys.wSecond, KeTiSys.wMilliseconds); } else { 48
  56. Bài giảng môn học: Lâp̣ triǹ h Windows /* Windows 9x and CE. Elapsed time only. */ } CloseHandle (ProcInfo.hThread); CloseHandle (ProcInfo.hProcess); CloseHandle (hProc); return 0; } 3. Quản lý luồng (thread) trên Windows 3.1. Các khái niệm cơ bản Trong phần trƣớc chúng ta đã xem xét cách thức một luồng thực hiện kết thúc một tiến trình. Các luồng trong một tiến trình chia sẻ chung dữ liệu và mã lệnh, vì thế về bản chất các luồng đó cũng có vùng nhớ riêng của chúng. Windows đáp ứng điều này bằng một số cách sau: + Mỗi luồng có một stack của riêng nó cho các lời gọi hàm và các xử lý khác + Lời gọi tiến trình có thể truyền một biến, con trò, tới thời gian tạo ra luồng. + Mỗi luồng có thể cấp phát TLS của riêng nó. 3.2. Mô hiǹ h Boss/Worker và cá c mô hiǹ h khá c Lệnh grepMT minh họa cho mô hình Boss/Worker. Luồng boss (luồng chính) sẽ gán các tác vụ cho luồng worker để thực hiện. Mỗi luồng worker sẽ đƣợc cho một file để tìm kiếm và worker sẽ trả về giá trị của nó cho boss thread. Xem thêm ví dụ sắp xếp trộn để hiểu rõmô hình này. 3.3. Bô ̣nhớ dành cho luồng Các luồng có thể cần phải cấp phát và quản lý bộ nhớ của riêng nó và bảo vệ các vùng nhớ đó khỏi các luồng khác. Điều này đƣợc thực hiện qua các TLS, có thể minh họa bằng hình vẽ sau: 49
  57. Bài giảng môn học: Lâp̣ triǹ h Windows 3.4. Độ ƣu tiên và các trạng thái của luồng Các luồng thuộc về nhân của Windows luôn có độ ƣu tiên cao nhất khi sẵn sàng để thực hiện. Một luồng đƣợc coi là không sẵn sàng để thực hiện nếu nó ở trạng thái chờ, treo, hay bị block bởi một lý do nào đó. 4. Môṭ số ví du ̣về tiến triǹ h và luồng 4.1. Tìm kiếm song song với các tiến trình #include "EvryThng.h" int _tmain (DWORD argc, LPTSTR argv []) /* Create a separate process to search each file on the command line. Each process is given a temporary file, in the current directory, to receive the results. */ { HANDLE hTempFile; SECURITY_ATTRIBUTES StdOutSA = /* SA for inheritable handle. */ {sizeof (SECURITY_ATTRIBUTES), NULL, TRUE}; TCHAR CommandLine [MAX_PATH + 100]; STARTUPINFO StartUpSearch, StartUp; PROCESS_INFORMATION ProcessInfo; DWORD iProc, ExCode; HANDLE *hProc; /* Pointer to an array of proc handles. */ typedef struct {TCHAR TempFile [MAX_PATH];} PROCFILE; PROCFILE *ProcFile; /* Pointer to array of temp file names. */ GetStartupInfo (&StartUpSearch); GetStartupInfo (&StartUp); ProcFile = malloc ((argc - 2) * sizeof (PROCFILE)); hProc = malloc ((argc - 2) * sizeof (HANDLE)); /* Create a separate "grep" process for each file. */ for (iProc = 0; iProc < argc - 2; iProc++) { _stprintf (CommandLine, _T ("%s%s %s"), _T ("grep "), argv [1], argv [iProc + 2]); GetTempFileName (_T ("."), _T ("gtm"), 0, ProcFile [iProc].TempFile); /* For search results. */ hTempFile = /* This handle is inheritable */ 50