Tìm hiểu về cửa sổ ứng dụng

Chia sẻ: Buihuu Tan | Ngày: | Loại File: PDF | Số trang:20

0
104
lượt xem
22
download

Tìm hiểu về cửa sổ ứng dụng

Mô tả tài liệu
  Download Vui lòng tải xuống để xem tài liệu đầy đủ

Trong bài viết này, chúng ta sẽ xây dựng một chương trình với đầy đủ chức năng của một cửa sổ ứng dụng trên desktop. Tìm hiểu về cửa sổ, lớp cửa sổ: Trong một ứng dụng đồ họa 32-bit, cửa sổ (window) là một vùng hình chữ nhật trên màn hình, nơi mà ứng dụng có thể hiện thị thông tin và nhận thông tin vào từ người dùng (users). Do vậy, nhiệm vụ đầu tiên của một ứng dụng đồ họa 32-bit là tạo một cửa sổ....

Chủ đề:
Lưu

Nội dung Text: Tìm hiểu về cửa sổ ứng dụng

  1. Iczelion’s Tutorial Win32 ASM Tutorial 3 : A Simple Window Tìm hiểu về cửa sổ ứng dụng Trong bài viết này, chúng ta sẽ xây dựng một chương trình với đầy đủ chức năng của một cửa sổ ứng dụng trên desktop. Tìm hiểu về cửa sổ, lớp cửa sổ Trong một ứng dụng đồ họa 32-bit, cửa sổ (window) là một vùng hình chữ nhật trên màn hình, nơi mà ứng dụng có thể hiện thị thông tin và nhận thông tin vào từ người dùng (users). Do vậy, nhiệm vụ đầu tiên của một ứng dụng đồ họa 32-bit là tạo một cửa sổ. Một cửa sổ sẽ chia sẻ màn hình với các cửa sổ khác trong cùng một ứng dụng hay với các ứng dụng khác. Chỉ một cửa sổ trong một thời điểm nhận được thông tin nhập từ người dùng thông qua bàn phím, thiết bị chuột hay các thiết bị nhập liệu khác để tương tác với cửa sổ và ứng dụng. Tất cả các cửa sổ đều đựơc tạo từ một cấu trúc được cung cấp sẵn gọi là lớp cửa sổ (window class). Cấu trúc này là một tập hợp các mô tả các thuộc tính mà hệ thống dùng như khuôn mẫu để tạo nên các cửa sổ. Mỗi một cửa sổ phải là thành viên của một lớp cửa sổ. Tất cả các lớp cửa sổ này đều được xử lý riêng biệt. Người dịch: Benina (REA TEAM) Trang 1 Tổng hợp và hiệu chỉnh: NhatPhuongLe (VNCERT TEAM)
  2. Iczelion’s Tutorial Win32 ASM Tutorial 3 : A Simple Window Các chương trình trên Windows thường sử dụng các API để thiết kế giao diện (GUI) cho chúng. Bằng cách này, nó có lợi cho cả người sử dụng và cả lập trình viên. Đối với người sử dụng, họ không cần phải tìm hiểu làm thế nào để sử dụng giao diện đồ họa cho mỗi chương trình mới, giao diện của các chương trình là tương tự nhau. Đối với các lập trình viên, mã lệnh để tạo nên giao diện chương trình đã sẵn, được test và sẵn sàng cho việc sử dụng. Nhưng mặt khác, việc giao diện được thiết kế sẵn theo khuôn mẫu của HĐH sẽ làm tăng thêm sự tạp trong việc các lập trình muốn cài đặt hay thao tác trên các đối tượng bất kỳ của giao diện như là hộp thoại , menus, icons, … vì họ phải tuân theo một nguyên tắc chính xác, nhưng điều này có thể khắc phục bằng cách lập trình theo đơn thể hoặc theo hướng đối tượng (OOP). Tôi sẽ phát thảo những bước cần thiết để xây dựng cửa sổ ứng dụng trên desktop: 1. Lấy handle của thể hiện – chính là bản thân ứng dụng  Handle là một số nguyên không dấu 32bit do HĐH tạo ra để làm định danh cho mỗi đối tượng.  Còn lý do tại sao phải xin HĐH cấp một handle và vì sao gọi là instance handle (handle của thể hiện). Vì Windows là HĐH đa nhiệm, có thể có nhiều bản của cùng một chương trình cùng chạy vào cùng một thời điểm nên phải cần phải có 1 handle của chương trình ngay lúc chúng đang được active (nghĩa là đang được thao tác). Ngoài ra, để quản lý chặt chẽ hơn, HĐH còn có khái niệm hPrevinst là chỉ số của bản đã được khởi động trước đó và chúng luôn có giá trị NULL 2. Lấy địa chỉ của xâu ký tự các đối số dòng lệnh (command line), không cần thiết trừ khi chương trình thực hiện bằng dòng lệnh) 3. Định nghĩa lớp cửa sổ và đăng ký nó với HĐH (trừ khi bạn dùng loại cửa sổ đã được HĐH định nghĩa trước như MessageBox hay Dialogbox). 4. Tạo lập cửa sổ làm việc cho ứng dụng. 5. Hiển thị cửa sổ trên desktop (trừ khi bạn không muốn hiển thị cửa sổ ngay) 6. Luôn luôn vẽ (paint) và vẽ lại (repaint) vùng client của cửa sổ Hiểu như thế nào là vẽ và vẽ lại? Có nghĩa là người dùng có thể di chuyển cửaa sổ của một hay nhiều chương trình khác trên màn hình và sự di chuyển sẽ che một phần cửa sổ của ứng dụng. HĐH sẽ không lưu phần cửa sổ mà bị chương trình khác che. Khi cửa sổ bị/được di chuyển, HĐH sẽ gửi yêu cầu vẽ lại vùng client của cửa sổ. Người dịch: Benina (REA TEAM) Trang 2 Tổng hợp và hiệu chỉnh: NhatPhuongLe (VNCERT TEAM)
  3. Iczelion’s Tutorial Win32 ASM Tutorial 3 : A Simple Window 7. Nhận và xử lý thông điệp thông qua vòng lặp thông điệp. Chúng ta xét qua lược đồ quản lý sự kiện và thông điệp của HĐH : Trình tự xử lý thông điệp được mô tả như sau:  Nhận một thông điệp thông qua hàm API GetMessage() từ hàng đợi thông điệp của ứng dụng. Ngoại trừ thông điệp WM_QUIT, mỗi thông điệp sẽ trả về cho hàm GetMessage() một thông số khác 0 (TRUE). Khi nhận thông điệp WM_QUIT thì chương trình sẽ thoát khỏi vòng lặp và chấm dứt hoạt động chương trình. Hàm GetMessage() này nhận vào 4 tham số đầu vào trong đó tham số thứ 1 quan trọng là con trỏ trỏ về một cấu trúc kiểu MSG.  Sau đó, giải mã thông điệp này thông qua hàm API TranslateMessage(). Hàm này sẽ gọi trình điều khiển bàn phím (Keyboard driver) để chuyển đổi những thông điệp từ bàn phím do người dùng gõ (ví dụ WM_KEYDOWN, WM_KEYUP, …) thành những ký tự để đưa các trị này vào hàng đợi thông điệp của ứng dụng dưới dạng thông điệp WM_CHAR. Nghĩa là chương trình của Người dịch: Benina (REA TEAM) Trang 3 Tổng hợp và hiệu chỉnh: NhatPhuongLe (VNCERT TEAM)
  4. Iczelion’s Tutorial Win32 ASM Tutorial 3 : A Simple Window bạn có đủ khả năng phân biệt chữ A (a hoa) và a (a thường) mà không cần biết có phím SHIFT đang hoạt động.  Rồi sau đó, phân công giải quyết bởi một hàm thích hợp thông qua hàm API DispatchMessage() để xem dữ kiện chứa trong cấu trúc MSG để gọi cho đúng hàm giải quyết thông điệp. Hàm này sẽ push thông điệp vào Window Procedure để bắt đầu xử lý thông điệp. Vòng lặp While tiếp tục chạy cho đến khi nào GetMessage() trả về giá trị 0 cho biết là không còn cái thông điệp nào trong hàng đợi thông điệp và lúc này thông điệp chấm dứt. Hệ điều hành Windows sẽ phân công giải quyết các thông điệp thông qua một thủ tục gọi là Windows Procedure () và thường được lập trình viên đặt tên là WinProc(). Đoạn code trong thủ tục WndProc() này là phần mà bạn phải nhọc công lập trình. Các thông số bên trong các lớp Window (Window Class - WNDCLASS), các hàm API như GetMessage(), ShowWindow(), … bạn có thể tự tìm hiểu thông qua MSDN hoặc ebook Microsoft® Win32® Programmer's Reference. 8. Nếu thông điệp được gởi tới cửa sổ, chúng sẽ được xử lý bởi một hàm đặc biệt (được gọi là Window Procedure) của cửa sổ. Trong mỗi chương trình trên nền Windows, bộ phận trung tâm là 1 hoặc nhiều hàm nhận và xử lý các thông điệp. Các hàm này được gọi là Window Procedure() viết tắt là WndProc(). Đứng về mặt lập trình, thì cấu trúc của WndProc thường là lệnh switch() với những trường hợp cho những thông điệp khác nhau. Trong trường hợp của chúng ta, chỉ có hai case là xử lý thông điệp WM_PAINT và thông điệp WM_DESTROY. Trong thực tế các ứng dụng thường chứa một switch khổng lồ với số lượng trường hợp nhiều kinh khủng. Giá trị trả về của WndProc() tùy thuộc vào loại thông điệp. Mỗi WndProc() đều cần 4 tham số đầu vào :  hwnd : handle của thông điệp msg và 2 thông số khác là wparam và lparam. Tham số hwnd này rất quan trọng vì một WndProc() có thể hỗ trợ nhiều cửa sổ khác nhau trên màn hình cùng một lúc. Do đó, tham số hwnd sẽ nhận diện cửa sổ nào đã gọi WndProc() của chúng ta.  msg : cho số thứ tự của thông điệp. Nếu hwnd cho biết ai gọi thì msg cho biết nó muốn gì. Có thể. Có thể nó muốn nói là cửa sổ mới tạo xong (WM_CREATE) hoặc di chuyển đến vị trí mới (WM_MOVE) hoặc thay đổi kích thước (WM_SIZE) hoặc vẽ lại ứng dụng (WM_PAINT) hoặc khi người dùng nhấn nút Close đóng cửa sổ ứng dụng lại (WM_DESTROY). 9. Thoát chương trình nếu người dùng đóng cửa sổ Như bạn thấy, cấu trúc của một chương trình chạy trên nền Windows khá phức tạp so với chương trình chạy trên nền Dos. Nhưng trong thế giới Windows khác hoàn toàn so với thế giới Dos. Những chương trình Winodws có thể “chung sống” êm thắm với nhau. Chúng phải tuân thủ theo các nguyên tắc. Bạn - một lập trình viên - càng phải nghiêm ngặt hơn trong thói quen và phong cách lập trình của mình. Người dịch: Benina (REA TEAM) Trang 4 Tổng hợp và hiệu chỉnh: NhatPhuongLe (VNCERT TEAM)
  5. Iczelion’s Tutorial Win32 ASM Tutorial 3 : A Simple Window Xây dựng một ứng dụng Win32 ASM đơn giản Dưới đây là source code xây dựng một cửa sổ ứng dụng đơn giản. Trước khi đi vào các chi tiết phức tạp trong việc lập trình Win32 ASM , tôi sẽ điểm qua một số điểm quan trọng để cho bạn lập trình được dễ dàng.  Bạn nên sẽ đặt tất cả các hằng số, cấu trúc và các khai báo hàm (prototypes) trong một file include và include nó ngay lúc bắt đầu code của file .asm của bạn. Nó sẽ giúp bạn tránh được lỗi do gõ sai. Hiện thời, file include hòan chỉnh nhất cho MASM là windows.inc. Bạn cũng có thể định nghĩa những hằng số cho riêng bạn và những định nghĩa cấu trúc ,nhưng bạn sẽ đặt chúng vào trong một file include riêng biệt với windows.inc.  Dùng chỉ thị includelib để chỉ rõ thư viện nhập nào được sử dụng trong chương trình của bạn. Ví dụ, nếu chương trình của bạn gọi MessageBox, bạn sẽ gọi chỉ thị includelib với cú pháp như sau: includelib user32.lib tại vị trí bắt đầu trong file .asm của bạn. Chỉ thị này nói cho MASM biết rằng chương trình của bạn có sử dụng những hàm trong thư viện nhập có tên là user32.lib. Nếu chương trình gọi những hàm mà những hàm này nằm trong nhiều hơn một thư viện nhập, chỉ cần thêm một includelib cho mỗi thư viện bạn dùng. Bằng cách sử dụng chỉ thị includelib, bạn sẽ không phải lo lắng về các thư viện nhập này trong lúc liên kết chương trình để tạo ra file thực thi (file .EXE). Bạn có thể dùng /LIBPATH để nói cho trình liên kết biết tất cả các thư viên đó ở đâu.  Khi khai báo prototypes các hàm API, cấu trúc hay hằng số trong file include của bạn, hãy cố bám sát các tên gốc được dùng trong file include của HĐH. Điều này giúp bạn đỡ phải tra cứu một vài mục Win32 API reference.  Sử dụng makefile để trình biên dịch tự động dịch chương trình của bạn. Điều này sẽ giúp bạn tiết kiệm thời gian thay vì phải gõ lệnh biên dịch. .386 .model flat,stdcall option casemap:none include windows.inc include user32.inc includelib user32.lib ; Goi cac ham trong user32.lib ; va kernel32.lib include kernel32.inc includelib kernel32.lib ; Khai bao prototype ham WinMain WinMain proto :DWORD,:DWORD,:DWORD,:DWORD .DATA ; Nhung du lieu duoc khoi tao truoc ClassName db "SimpleWinClass",0 ; Ten cua lop cua so AppName db "Our First Window",0 ; Ten cua cua so Người dịch: Benina (REA TEAM) Trang 5 Tổng hợp và hiệu chỉnh: NhatPhuongLe (VNCERT TEAM)
  6. Iczelion’s Tutorial Win32 ASM Tutorial 3 : A Simple Window .DATA? ; Nhung du lieu khong duoc khoi tao hInstance HINSTANCE ? ; handle cua the hien cua ung dung CommandLine LPSTR ? .CODE ; Here begins our code start: invoke GetModuleHandle, NULL ; Lay handle cua the hien cua ung dung hInstance,eax mov hInstance,eax invoke GetCommandLine ; Lay dia chi cua xau ky tu chua ; cac doi so dong lenh. ; Ban khong can goi ham nay neu nhu ; chuong trinh cua ban khong thuc hien ; bang dong lenh mov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT ; Goi ham ; WinMain invoke ExitProcess, eax ; Thoat khoi ung dung. ; Gia tri tra ve cua ExitProcess duoc ; luu trong eax cua WinMain. WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE, CmdLine:LPSTR,CmdShow:DWORD LOCAL wc:WNDCLASSEX ; Tao cac bien cuc bo trong ngan xep LOCAL msg:MSG LOCAL hwnd:HWND mov wc.cbSize,SIZEOF WNDCLASSEX ; Dien day du gia tri cho ; cac bien cua lop cua so mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInstance pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,NULL mov wc.lpszClassName,OFFSET ClassName invoke LoadIcon,NULL,IDI_APPLICATION mov wc.hIcon,eax mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx, addr wc ; Dang ky lop cua so voi HDH invoke CreateWindowEx,NULL,\ ADDR ClassName,\ ADDR AppName,\ WS_OVERLAPPEDWINDOW,\ CW_USEDEFAULT,\ CW_USEDEFAULT,\ CW_USEDEFAULT,\ CW_USEDEFAULT,\ NULL,\ NULL,\ hInst,\ NULL mov hwnd,eax invoke ShowWindow, hwnd,CmdShow ; Hien thi cua so ra desktop invoke UpdateWindow, hwnd ; Ve va ve lai vung client cua cua so Người dịch: Benina (REA TEAM) Trang 6 Tổng hợp và hiệu chỉnh: NhatPhuongLe (VNCERT TEAM)
  7. Iczelion’s Tutorial Win32 ASM Tutorial 3 : A Simple Window .WHILE TRUE ; Vong lap thong diep invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDW mov eax,msg.wParam ; Tra gia tri trong thanh ghi eax ret WinMain endp WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM .IF uMsg==WM_DESTROY ; Neu nguoi dung dong cua so dong cua so invoke PostQuitMessage,NULL ; thi thoat ung dung .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ; Nguoc lai thi xu ly thong diep theo mac dinh cua HDH ret .ENDIF xor eax,eax ret WndProc endp end start Phân tích mã lệnh Bạn đã thấy một chương trình tạo một cửa sổ ứng dụng đơn giản cần phải code rất nhiều mã lệnh như trên. Nhưng phần lớn những đoạn code đó chỉ là một template code (mã nguồn mẫu) mà bạn có thể copy từ mã lệnh của file này đến mã lệnh của một file khác. Hay nếu bạn thích, bạn có thể dịch một vài đoạn mã nguồn đó vào trong một thư viện để dùng như một đọan mở đầu hay đọan code cuối. Bạn có thể chỉ viết code trong hàm WinMain. Thật vậy, đây là những gì mà trình biên dịch C đã làm sẵn. Chúng cho phép bạn viết code hàm WinMain không cần phải lo làm những công việc “vặt vảnh” khác. Khác với trình biên dịch C là bạn phải có một hàm có tên là WinMain, nếu không sẽ không thể kết hợp code của bạn với đoạn đầu hay đọan cuối của code. Bạn không bị hạn chế vấn đề này trong ngôn ngữ ASM. Bạn có thể dùng bất kỳ tên hàm nào không nhất thiết là WinMain hoặc không có hàm nào cũng không sao. Tiếp theo là những giải thích về đoạn code đã viết ở trên. Nó rất dài, chúng ta hãy cùng nhau phân tích chương trình này để làm rõ vấn đề xây dựng một ứng dụng Win32 ASM không đến nổi quá phức tạp như bạn nghĩ. .386 .model flat,stdcall option casemap:none WinMain proto :DWORD,:DWORD,:DWORD,:DWORD include windows.inc include user32.inc include kernel32.inc includelib user32.lib includelib kernel32.lib Người dịch: Benina (REA TEAM) Trang 7 Tổng hợp và hiệu chỉnh: NhatPhuongLe (VNCERT TEAM)
  8. Iczelion’s Tutorial Win32 ASM Tutorial 3 : A Simple Window Ba dòng trước tiên rất cần thiết vì: .386: nói cho MASM biết rằng chúng ta có ý định sử dụng cấu trúc bộ lệnh 80386 trong chương trình. .model flat,stdcall: nói cho MASM rằng kiểu bộ nhớ đang sử dụng cho chương trình là kiểu flat memory. Cũng như bạn sẽ dùng tham số stdcall để quy ướ cách truyền tham số cho hàm như thế nào. option casemap:none nói cho MASM biết rằng tên các nhãn trong chương trình phân biệt chữ hoa, chữ thường, vì vậy ExitProcess sẽ khác với exitprocess. Kế đến là khai báo prototype của hàm WinMain. Sau này, khi chúng ta gọi hàm WinMain, chúng ta ta phải định nghĩa lại prototype của hàm để có thể sử dụng được hàm invoke. Chúng ta phải include file windows.inc phần bắt đầu trong mã nguồn. Nó chứa các cấu trúc quan trọng, các hằng số được dùng trong chương trình của chúng ta. File include windows.inc chỉ là một file text. Bạn có thể mở nó với bất kỳ trình sọan thảo vănbản nào. Xin chú ý rằng file windows.inc không chứa tất cả các cấu trúc và các hằng số. Bạn có thể thêm các đối tượng mới (cấu trúc, hằng số, …) mới nếu không có trong file. Chương trình ứng dụng gọi các hàm API chứa trong user32.dll (CreatWindowEx, RegisterWindowClassEx và gọi hàm API chứa trong kernal32.dll (ExitProcess), vì thế chúng ta phải liên kết chương trình với các thư viện nhập này. Câu hỏi kế tiếp là: “Làm thế nào tôi biết được thư viện nhập nào được liên kết với chương trình của chúng ta?” Bạn phải biết API nằm ở đâu để có thể gọi các hàm API. Ví dụ: nếu bạn gọi một hàm API trong gdi32.dll , bạn phải liên kết với với gdi32.lib. Đây là cách mà MASM giải quyết vấn đề. Còn đối với TASM, cách liên kết với các thư viện nhập thì đơn giản hơn, chỉ cần liên kết đến một và chỉ một file mà thôi: import32.lib. .DATA ClassName db "SimpleWinClass",0 AppName db "Our First Window",0 .DATA? hInstance HINSTANCE ? CommandLine LPSTR ? Kế đến là section “DATA” Trong section .DATA, chúng ta khai báo những chuỗi kết thúc bằng zero.  Classname là tên của lớp cửa sổ (Window class)  Appname là tên cửa sổ ứng dụng của chúng ta. Người dịch: Benina (REA TEAM) Trang 8 Tổng hợp và hiệu chỉnh: NhatPhuongLe (VNCERT TEAM)
  9. Iczelion’s Tutorial Win32 ASM Tutorial 3 : A Simple Window Chú ý rằng: hai biến này được khởi tạo giá trị từ ban đầu. Trong section .DATA?, hai biến được khai báo:  hInstance: viết tắt của 2 từ instance handle – là định danh của thể hiện của chương trình được HĐH cấp phát.  CommandLine : địa chỉ của xâu ký tự dòng lệnh của chương trình. Bạn để ý biến hInstance chúng ta báo kiểu dữ liệu là HINSTANCE và biến CommandLine khai báo kiểu LPSTR. Đây là 2 kiểu dữ liệu mới, nhưng thật ra chính là một tên khác cho kiểu DWORD, là số nguyên 32-bit không dấu. Bạn có thể nhìn thấy chúng trong windows.inc. HINSTANCE typedef DWORD LPSTR typedef DWORD Chú ý rằng tất cả các biến trong section .DATA? không được gán giá trị khởi tạo ban đầu, đó là do chúng không lưu giữ một giá trị nào khi chương trình được load lên bộ nhớ, nhưng chúng ta muốn dành một vùng nhớ để sau này sử dụng. .CODE start: invoke GetModuleHandle, NULL mov hInstance,eax invoke GetCommandLine mov CommandLine,eax invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT invoke ExitProcess,eax ..... end start Section .CODE chứa tất cả các chỉ thị lệnh. Các mã lệnh của bạn phải nằm giữa nhãn : và end . Tên của nhãn không quan trọng. Bạn có thể đặt tên cho nó bất kỳ và miễn sao không trùng với các từ khóa, toán tử của MASM. Chỉ thị đầu tiên của chúng ta là gọi hàm GetModuleHandle để lấy instance handle của cửa sổ ứng dụng. Dưới Win32, handle của thể hiện của chương trình và handle của đơn thể là một và giống nhau (tức instance handle = module handle). Bạn có thể tưởng tượng rằng instance handle như là một ID của cửa sổ chương trình. Nó được sử dụng như một tham số cho các hàm API khác mà chương trình bạn cần gọi đến, vì vậy thông thường một ý tưởng tốt là copy ID này và lưu nó (hay lưu giữ nó) với một tham số khác có tên là module handle ngay lúc chương trình bắt đầu thực thi. Chú ý: thực tế dưới platform Win32, instance handle là một địa chỉ tuyến tính (linear address) của chương trình trong bộ nhớ. Người dịch: Benina (REA TEAM) Trang 9 Tổng hợp và hiệu chỉnh: NhatPhuongLe (VNCERT TEAM)
  10. Iczelion’s Tutorial Win32 ASM Tutorial 3 : A Simple Window Vậy địa chỉ tuyến tính là gì ? Lập trình Win32 dựa trên cơ sở flat memory model, mỗi chương trình hoạt động một cách độc lập trên 1 đoạn địa chỉ cơ sở được gọi là linear address space (chứa đựng code, data, và đoạn mã con trong stack - lưu ý: mỗi địa địa chỉ linear address space chứa tối đa khoảng 2^32-1 ô nhớ, bất kỳ địa chỉ nào trong linear address space gọi là linear address) Trở lại đoạn code trên, ta có thể tìm giá trị trả về của hàm trong Win32 trong thanh ghi EAX. Tất cả những giá trị khác được trả về qua những biến được chuyển vào danh sách tham số của hàm mà bạn đã định nghĩa cho lời gọi hàm. Một hàm Win32 mà bạn gọi, chúng sẽ luôn cất giữ giá trị của các thanh ghi đoạn và các thanh ghi EBX,EDI,ESI và EBP. Trái lại, ECX và EDX được xem xét là thanh ghi dùng để xóa giá trị luôn luôn không được dùng để lưu trữ giá trị trả về của một hàm. Chú ý: Không có gì chắc rằng giá trị của các thanh ghi EAX, ECX và EDX được cất giữ thông qua lời gọi hàm API. Dòng tiếp theo (mov hInstance,eax) có nghĩa là: khi gọi một hàm API, luôn bảo đảm rằng giá trị trả về của hàm được chứa trong thanh ghi EAX. Nếu bất cứ hàm nào của bạn được gọi bởi HĐH, bạn phải làm đúng theo nguyên tắc sau: cất giữ và phục hồi giá trị của các thanh ghi đoạn và EBX, EDI, ESI, EBP trước khi hàm trả về giá trị. Nếu không, chương trình của bạn sẽ bị lỗi ngay lập tức, nguyên tắc này áp dụng bao gồm cả Windows Procedure (WndProc) và hàm CALLBACK. Lời gọi hàm GetCommandLine không cần thiết nếu chương trình của bạn không thực thi bằng dòng lệnh. Trong ví dụ này, tôi sẽ chỉ cho bạn gọi nó như thế nào trong trường hợp bạn cần nó trong chương trình. Kế đến là lời gọi hàm WinMain. Trong các chương trình C/C++ viết trên môi trường DOS, thì thường hàm main() được xem là điểm vào chính thức của chương trình (tức là khi bạn nhấn CTRL + F5 để thực thi chương trình thì hệ điều hành sẽ gọi hàm WinMain() này thực hiện trước, và hệ điều hành sẽ load ứng dụng của bạn lên trên bộ nhớ RAM. Do đó, WinMain còn được gọi với tên là Program Entry Point hoặc là Orginal Entry Point. int WINAPI WinMain( HINSTANCE hInstance, // handle to current instance HINSTANCE hPrevInstance, // handle to previous instance LPSTR lpCmdLine, // pointer to command line int nCmdShow // show state of window ); Người dịch: Benina (REA TEAM) Trang 10 Tổng hợp và hiệu chỉnh: NhatPhuongLe (VNCERT TEAM)
  11. Iczelion’s Tutorial Win32 ASM Tutorial 3 : A Simple Window Đôi khi, lập trình cũng có thể thể định nghĩa lại WinMain theo Win32 ASM như sau : int WINAPI WinMain( HINSTANCE hInstance, // handle to current instance HINSTANCE hPrevInstance, // handle to previous instance LPSTR lpCmdLine, // pointer to command line DWORD nCmdShow // show state of window ); Ta thấy hàm này có 4 tham số: - hInst, hPrevinst: Chỉ số chương trình khi chúng đang chạy. Vì Windows là hệ điều hành đa nhiệm, có thể có nhiều bản của cùng một chương trình cùng chạy vào cùng một thời điểm nên phải quản lý chặt chẽ chúng. hInst là chỉ số bản chương trình vừa khởi động, hPrevinst là chỉ số của bản đã được khởi động trước đó và chúng luôn có giá trị NULL. Chú ý:  Dưới Win32 không có previous instance. Mỗi chương trình tồn tại một mình trong khoảng không gian địa chỉ bộ nhớ của nó, vì vậy giá trị của hPrevInst luôn luôn là NULL. Đây là những khiếm khuyết về mặt xử lý có từ những ngày đầu khi hệ thống 16- bit, khi tất cả các thể hiện của một chương trình chạy trong khoảng không gian địa chỉ giống nhau và một thể hiện được nhận biết nếu nó là thể hiện đầu tiên. Dưới Win16, nếu hPrevInst là NULL, thì thể hiện này là cái đầu tiên.  Bạn không phải khai báo tên hàm như là WinMain. Trong thực tế, bạn sẽ tự do trong việc đặt tên hàm này không nhất thiết phải đặt là WinMain. Bạn không phải dùng đến một WinMain trong tất cả chương trình, mà có thể paste mã lệnh bên trong hàm WinMain kế đến sau GetCommandLine thì chương trình của bạn chạy tốt. Trước khi chứa giá trị trả về của hàm WinMain, thì EAX chứa giá trị mã code của hàm ExitProcess. Chúng ta chuyển giá trị này như là tham số đầu vào cho hàm ExitProcess để nó kết thúc ứng dụng. - lpszCmdLine: chứa địa chỉ đầu của xâu ký tự các đối số dòng lệnh. - nCmdShow : Cho biết cách thức hiển thị cửa sổ khi chương trình khởi động. Windows có thể gán giá trị SW_SHOWNORMAL hay SW_SHOWMINNOACTIVE. Các tham số trên do hệ điều hành truyền vào. WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE, CmdLine:LPSTR,CmdShow:DWORD Dòng trên là khai báo hàm đầy đủ của WinMain so với khai báo Prototype của hàm này ở đầu chương trình. Các tham số hợp thành cặp ( : ) theo từng loại tham số theo sau chỉ thị PROC. Những tham số có trong lời gọi hàm WinMain (invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT). Người dịch: Benina (REA TEAM) Trang 11 Tổng hợp và hiệu chỉnh: NhatPhuongLe (VNCERT TEAM)
  12. Iczelion’s Tutorial Win32 ASM Tutorial 3 : A Simple Window Bạn có thể tham chiếu những tham số này bằng tên của nó thay vì bằng các thao tác thủ công trên ngăn xếp. Thêm vào đó, MASM sẽ sinh ra đoạn mã mở đầu và kết thúc cho hàm. Vì vậy, chúng ta sẽ không cần quan tâm đến khung ngăn xếp cho hàm khi vào và ra khỏi hàm. Hiểu đoạn mã mở đầu và kết thúc cho hàm như thế nào ? Bạn dùng một chương trình debug, ở đây tôi dùng OllyDBG. Mở OllyDBG, load file SimpleWindow.exe vào nó, bạn sẽ được như sau: Vị trị vệt sáng mà OllyDBG đang dừng, đó chính là Orginal Entry Point (OEP) của chương trình? (đã giải thích ở phần tìm hiểu hàm WinMain). Ở đây, để làm rõ hơn, tôi chỉ bạn cách nhận biết, bạn vào menu View  Source files, sẽ thấy OllyDBG chỉ tới file .asm (SimpleWindow.asm) Người dịch: Benina (REA TEAM) Trang 12 Tổng hợp và hiệu chỉnh: NhatPhuongLe (VNCERT TEAM)
  13. Iczelion’s Tutorial Win32 ASM Tutorial 3 : A Simple Window Bạn double-click vào dòng này, OllyDBG sẽ hiện ra toàn bộ source code: Và nếu bạn chú ý, bạn thấy trong cửa sổ này, vệt sáng đen đang dừng lại tại thủ tục WinMain. Theo giải thích ở trên, khi thực thi bất kỳ ứng dụng nào trên nền Win32, thì HĐH sẽ tìm hàm WinMain thực thi đầu tiên, do đó mới gọi WinMain chính là điểm vào của chương trình, hay gọi theo thuật ngữ IT là OEP. Nếu bạn double vào dòng WinMain này, thì OllyDBG sẽ nhảy sang cửa sổ Disassembly: Vậy làm thế nào để nhận biết đâu là đoạn mã mở đầu và đoạn mã kết thúc. Để làm được điều này, ta xem lại chi tiết hàm WinMain ở tiếp sau đây: Người dịch: Benina (REA TEAM) Trang 13 Tổng hợp và hiệu chỉnh: NhatPhuongLe (VNCERT TEAM)
  14. Iczelion’s Tutorial Win32 ASM Tutorial 3 : A Simple Window LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND Chỉ thị LOCAL cấp vùng nhớ từ vùng nhớ trong ngăn xếp cho các biến cục bộ được sử dụng trong hàm WinMain. Cụm chỉ thị LOCAL phải đứng ngay dưới chỉ thị PROC. Sau chỉ thị LOCAL là : . Vì vậy, khai báo biến cục bộ wc có kiểu là WNDCLASSEX ta có cú pháp như sau: LOCAL wc:WNDCLASSEX, báo cho MASM biết cấp vùng nhớ cho biến cục bộ này, biến cục bộ có tên là wc - là một cấu trúc WNDCLASSEX có rất nhiều thuộc tính: typedef struct _WNDCLASSEX { // wc UINT cbSize; UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HANDLE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground; LPCTSTR lpszMenuName; LPCTSTR lpszClassName; HICON hIconSm; } WNDCLASSEX; Thuộc tính Ý nghĩa Ghi chú cbSize Kích thước của cấu trúc WNDCLASSEX style Kiểu lớp lpfnWndProc Con trỏ đến Window Procedure cbClsExtra Số byte được cấp phát thêm sau cấu Mặc định trúc lớp cửa sổ cbWndExtra Số byte được cấp phát thêm sau một Mặc định thể hiện của cửa sổ hInstance Định danh chứa thủ tục cửa sổ của lớp cửa sổ hIcon Định danh của icon Dùng hàm LoadIcon hCursor Định danh của con trỏ chuột Dùng hàm LoadCursor hbrBackground Định danh của nền Dùng hàm GetStockObject lpszMenuName Tên thực đơn Tên thực đơn gắn với cửa sổ, thực đơn này được khai báo trong tập tin resource lpszClassName Tên lớp Người dịch: Benina (REA TEAM) Trang 14 Tổng hợp và hiệu chỉnh: NhatPhuongLe (VNCERT TEAM)
  15. Iczelion’s Tutorial Win32 ASM Tutorial 3 : A Simple Window Chúng ta có thể tham chiếu wc trong mã nguồn mà không gặp phải trở ngại nào liên quan với thao tác trên ngăn xếp. Mặt trái của việc sử dụng biến cục bộ là không được sử dụng bên ngoài hàm mà chúng được cài đặt và sẽ tự động mất đi khi thoát khỏi hàm. Hay nói một cách khác bạn không thể khởi tạo giá trị ban đầu cho biến cục bộ một cách tự động bởi vì chúng nằm trên bộ nhớ trong ngăn xếp, và sự cấp phát vùng nhớ này là cấp phát động (khi nào cần thì cấp phát, khi thoát khỏi hàm thì giải phóng). Bạn phải gán giá trị cho các thuộc tính của biến cấu trúc này một cách thủ công sau chỉ thị LOCAL: mov wc.cbSize,SIZEOF WNDCLASSEX mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc mov wc.cbClsExtra,NULL mov wc.cbWndExtra,NULL push hInstance pop wc.hInstance mov wc.hbrBackground,COLOR_WINDOW+1 mov wc.lpszMenuName,NULL mov wc.lpszClassName,OFFSET ClassName invoke LoadIcon,NULL,IDI_APPLICATION mov wc.hIcon,eax mov wc.hIconSm,eax invoke LoadCursor,NULL,IDC_ARROW mov wc.hCursor,eax invoke RegisterClassEx, addr wc Những thuộc tính trên rất dễ hiểu. Thực chất đó là các thuộc tính của một lớp cửa sổ. Một lớp cửa sổ không có gì hơn là một tập hợp khuôn mẫu hay là sự mô tả rõ ràng của một cửa sổ. Nó định nghĩa những thuộc tính quan trọng của một cửa sổ như: icon, cursor của nó, các hàm thực hiện việc load icon, cursor của cửa sổ lên màn hình desktop, như hình dáng, màu sắc của icon thì như thế nào… Bạn tạo một cửa sổ từ một lớp cửa sổ. Đây là một loại khái niệm hướng đối tượng. Nếu bạn muốn tạo nhiều một cửa sổ với cùng những thuộc tính đó, Việc gom chung các thuộc tính của cửa sổ thành một lớp sẽ dễ dàng hơn khi ta cần tạo nhiều cửa sổ bằng cách tham chiếu tới chúng. Điều này sẽ giúp hệ thống tiết kiệm bộ nhớ hơn - tránh khai báo những thuộc tính giống nhau. Hãy nhớ rằng, trước kia HĐH được thiết kế khi chip nhớ bị giới hạn và hầu hết các máy tính có 1 MB bộ nhớ. HĐH phải có khả năng quản lý tốt bộ nhớ trong việc cấp phát cho các chương trình sử dụng khi tài nguyên bộ nhớ rất ít ỏi. Điểm đáng chú ý ở đây là nếu bạn chỉ định nghĩa riêng cửa sổ ứng dụng, bạn phải điền đầy đủ thông tin của các thuộc tính của cửa sổ trong cấu trúc WNDCLASS hay WNDCLASSEX và gọi hàm RegisterClass hay RegisterClassEx để đăng ký với HĐH biết là ứng dụng của bạn có sử dụng cửa sổ, trước khi bạn có thể tạo cửa sổ cho ứng dụng. Tuy nhiên, bạn chỉ đăng ký một lớp cửa sổ cho mỗi loại cửa sổ mà bạn muốn tạo một cửa sổ từ lớp đó. HĐH có một vài lớp cửa sổ được định nghĩa sẵn như: nút (button) và editbox. Đối với mỗi điều khiển (nút, editbox, …), bạn không cần phải đăng ký một lớp cửa sổ, mà chỉ cần gọi hàm API CreateWindowEx với tên lớp được định nghĩa trước. Người dịch: Benina (REA TEAM) Trang 15 Tổng hợp và hiệu chỉnh: NhatPhuongLe (VNCERT TEAM)
  16. Iczelion’s Tutorial Win32 ASM Tutorial 3 : A Simple Window Thành phần quan trọng nhất của lớp WNDCLASSEX, là lpfnWndProc. lpfn đó là một con đến hàm. Dưới nền Win32, không có con trỏ “near” hay “far”, chỉ duy nhất một loại pointer bởi vì kiểu của bộ nhớ là FLAT. Đây cũng là những khiếm khuyết từ thời Win16. Mỗi lớp cửa sổ phải luôn luôn có một hàm được gọi từ thủ tục window (Window Procedure, viết tắt là WndProc). WndProc chịu trách nhiệm xử lý thông điệp của tất cả các cửa sổ đã được tạo từ lớp cửa sổ. HĐH sẽ gởi thông điệp đến WndProc để khai báo cho nó những sự kiện quan trọng liên quan đến các cửa sổ mà nó chịu trách nhiệm, như người dùng sử dụng bàn phím hay chuột tương tác với cửa sổ. Nó sẽ báo cho WndProc để phản hồi lại một cách thông minh đối với mỗi thông điệp mà nó nhận được. Bạn sẽ tốn rất nhiều thời gian để viết các điều khiển sự kiện trong WndProc. M tả mỗi thành phần của WNDCLASSEX như dưới đây: WNDCLASSEX STRUCT DWORD cbSize DWORD ? style DWORD ? lpfnWndProc DWORD ? cbClsExtra DWORD ? cbWndExtra DWORD ? hInstance DWORD ? hIcon DWORD ? hCursor DWORD ? hbrBackground DWORD ? lpszMenuName DWORD ? lpszClassName DWORD ? hIconSm DWORD ? WNDCLASSEX ENDS  cbSize: Kích thước của cấu trúc WNDCLASSEX có trị là bytes. Chúng ta có thể dùng sizeof() để lấy giá trị.  style: Kiểu của các windows được cài đặt từ class này. Bạn có thể kết hợp các kiểu khác nhau bằng cách dùng toán tử OR  lpfnWndProc: Địa chỉ của WndProc chịu trách nhiệm cho các các cửa sổ được tạo từ lớp cửa sổ này.  cbClsExtra: Chỉ định số lượng byte mở rộng được cấp theo cấu trúc lớp cửa sổ này. HĐH tạo trị đầu là zero cho các bytes này. Bạn có thể cài đặt các dữ liệu đặc biệt cho lớp ở đây.  cbWndExtra: Chỉ định số lượng byte mở rộng được cấp phát theo thể hiện của cửa sổ. HĐH tạo trị đầu là zero cho các bytes. Nếu một ứng dụng sử dụng cấu trúc WNDCLASS để đăng ký một dialog box bằng cách sử dụng chỉ thị CLASS trong file tài nguyên, HĐH phải set thành phần này bằng DLGWINDOWEXTRA.  hInstance: handle của chương trình  hIcon : Handle của icon. Icon được hiển thị trên ứng dụng bằng cáchsử dụng hàm API LoadIcon.  hCursor : Handle của cursor. Con trỏ chuột được hiển thị trên ứng dụng bằng cáchsử dụng hàm API LoadCursor.  hbrBackground : màu nền của cửa sổ được cài đặt từ lớp cửa sổ.  lpszMenuName : menu mặc định cho cửa sổ được cài đặt từ lớp cửa sổ.  lpszClassName : Tên của lớp cửa sổ. Người dịch: Benina (REA TEAM) Trang 16 Tổng hợp và hiệu chỉnh: NhatPhuongLe (VNCERT TEAM)
  17. Iczelion’s Tutorial Win32 ASM Tutorial 3 : A Simple Window  hIconSm : Handle của icon nhỏ mà nó được liên kết với lớp cửa sổ. Nếu thành phần này là NULL, hệ thống sẽ tìm icon được chỉ định bởi thành phần hIcon cho một icon có kích thước thích hợp để sử dụng như một icon nhỏ (small icon). invoke CreateWindowEx, NULL,\ ADDR ClassName,\ ADDR AppName,\ WS_OVERLAPPEDWINDOW,\ CW_USEDEFAULT,\ CW_USEDEFAULT,\ CW_USEDEFAULT,\ CW_USEDEFAULT,\ NULL,\ NULL,\ hInst,\ NULL Sau khi đăng ký với lớp cửa sổ biết là chúng ta cần tạo một cửa sổ ứng dụng, bạn gọi hàm API CreateWindowEx để tạo cửa sổ thuộc lớp cửa sổ vừa đăng ký. Hàm này có 12 tham số: CreateWindowExA proto dwExStyle:DWORD,\ lpClassName:DWORD,\ lpWindowName:DWORD,\ dwStyle:DWORD,\ X:DWORD,\ Y:DWORD,\ nWidth:DWORD,\ nHeight:DWORD,\ hWndParent:DWORD ,\ hMenu:DWORD,\ hInstance:DWORD,\ lpParam:DWORD Chúng ta sẽ xem xét các mô tả chi tiết cho mỗi tham số: dwExStyle: Kiểu mở rộng cửa sổ. Đây là một tham số mới được thêm vào (so với hàm API CreatWindow). Bạn có thể sử dụng những kiểu cửa sổ mới của Wins 95 và WinNT. Bạn có thể chỉ định kiểu cửa sổ thông thường bởi tham số dwStyle nhưng nếu bạn muốn kiểu cửa sổ bắt mắt hơn chút xíu như cửa sổ ứng dụng bạn luôn ở trên cùng, hay trong suốt hơn so các cửa sổ ứng dụng khác, bạn phải chỉ rõ chúng tại đây. Bạn cũng có thể gán giá trị NULL cho thuộc tính này nếu bạn không muốn kiểu cửa sổ mở rộng. lpClassName: Địa chỉ của chuỗi string ASCIIZ, chứa tên của lớp cửa sổ mà bạn muốn dùng như một template cho cửa sổ này. Lớp cửa sổ này có thể là do bạn đã đăng ký trước đó hay là lớp do HĐH đã định nghĩa trước. Như đã nói ở trên, mọi cửa sổ bạn tạo radựa trên nền tảng của lớp cửa sổ. Người dịch: Benina (REA TEAM) Trang 17 Tổng hợp và hiệu chỉnh: NhatPhuongLe (VNCERT TEAM)
  18. Iczelion’s Tutorial Win32 ASM Tutorial 3 : A Simple Window lpWindowName: Địa chỉ của string ASCIIZ chứa tên của cửa sổ. Chuỗi này sẽ hiện ra trên thanh tiêu đề của của cửa sổ. Nếu thông số này có giá trị là NULL, thanh tiêu đề của cửa sổ là chuỗi trắng. dwStyle: Kiểu của cửa sổ. Bạn có thể chọn giao diện của cửa sổ ở đây. Giá trị của thuộc tính này nếu là NULL thì vẫn cửa sổ vẫn tồn tại nhưng nó sẽ không có thanh menu, không có các button thu nhỏ, phóng to, và không có button đóng cửa sổ. Kiểu cửa sổ này không ít sử dụng. Bạn cần nhấn phím Alt-F4 để đóng nó. Kiểu cửa sổ thông thường sử dụng nhiều nhất là WS_OVERLAPPEDWINDOW. Một kiểu cửa sổ chỉ là một bit cờ. Vì vậy bạn có thể kết hợp nhiều kiểu khác nhau cùng một lúc bằng toán tử OR để có được một giao diện cửa sổ ưng ý nhất. Cũng bằng cách này, thì kiểu WS_OVERAPPEDWINDOW thực tế là một kết hợp của nhiều kiểu thường dùng nhất. X,Y: Tọa độ của góc trên bên trái của cửa sổ. Thông thường giá trị này sẽ là CW_USEDEFAULT, đó là bạn muốn HĐH lựa chọn cho bạn nơi để hiển thị cửa sổ ra màn hình desktop. nWidth, nHeight: chiều rộng và chiều cao của cửa sổ tính bằng pixels. Bạn cũng có thể sử dụng CW_USEDEFAULT để HĐH chọn lựa chiều rộng và cao thích hợp cho cửa sổ ứng dụng của bạn. hWndParent: handle của cửa sổ cha (nếu có tồn tại cửa sổ cha). Tham số này nói cho HĐH rằng cửa sổ này là con (cửa sổ cấp thấp hơn) của cửa sổ nào khác. Nếu vậy thì sẽ có một cửa sổ nào đó đóng vai trò là cửa sổ cha. Chú ý rằng đây không phải là quan hệ cha- con như Multiple Document interface - MDI. Cửa sổ con không nhất định phải thuộc vùng client của cửa sổ cha. Khái niệm về mối quan hệ này đặc biệt chỉ sử dụng bên trong HĐH Windows. Nếu cửa sổ cha mất hiệu lực, tất cả các cửa sổ con cũng mất hiệu lực một cách tự động. Điều này rất đơn giản. Nếu chỉ có một cửa sổ, chúng ta sẽ gán tham số này trị là NULL hMenu: handle của của menu cửa sổ. NULL nếu lớp menu đã được sử dụng. Hãy nhìn lại một thành phần của WNDCLASSEX là lpszMenuName. lpszMenuName chỉ định menu “mặc định” cho lớp cửa sổ. Mọi cửa sổ được tạo từ lớp cửa sổ này sẽ có menu giống nhau. Trừ khi bạn chỉ định một kiểu menu mới và sẽ “chồng lên” kiểu menu mặc định cho một cửa sổ qua tham số hMenu. hMenu thực tế là một tham số mục-đích-kép. Trong trường bạn muốn tạo cửa sổ mà cửa sổ này thuộc loại cửa sổ đã được HĐH định nghĩa sẵn (như các điều khiển: Dialog, Editbox), hMenu được sử dụng như một ID của điều khiển. HĐH có thể quyết định hMenu là một handle menu hay là ID của điều khiển bằng cách “nhìn vào” tham số lpClassName. Nếu nó là tên của một lớp cửa sổ được HĐH định nghĩa trước (như các lớp button, edit ...), hMenu sẽ là ID cho chính điều khiển đó. Nếu không, nó là handle cho menu của cửa sổ. hInstance: Là handle của bản thân chương trình. lpParam: Đây là con trỏ trỏ đến dữ liệu được truyền tới cửa sổ. Loại cửa sổ MDI sử dụng tham số này để truyền dữ liệu CLIENTCREATESTRUCT. Thông thường giá trị này Người dịch: Benina (REA TEAM) Trang 18 Tổng hợp và hiệu chỉnh: NhatPhuongLe (VNCERT TEAM)
  19. Iczelion’s Tutorial Win32 ASM Tutorial 3 : A Simple Window được set là NULL, có nghĩa là không có dữ liệu được truyền cho CreateWindow(). Cửa sổ có thể khôi phục lại giá trị của tham số này bới gọi hàm GetWindowLong. mov hwnd,eax invoke ShowWindow, hwnd,CmdShow invoke UpdateWindow, hwnd Khi hàm CreateWindowEx trả về giá trị khác NULL, tức là việc tạo cửa sổ ứng dụng thành công; handle của cửa sổ cất trong thanh ghi eax. Chúng ta phải nhớ giá trị này để sử dụng trong tương lai. Cửa sổ mà chúng ta đã tạo không tự động hiển thị. Chúng ta phải gọi hàm ShowWindow() với handle là handle của cửa sổ để yêu cầu cửa sổ ứng dụng được hiển thị trên màn hình. Kế đến bạn gọi hàm UpdateWindow để cửa sổ của bạn vẽ lại vùng client của nó. Hàm này thường dùng khi bạn muốn cập nhật nội dung của vùng client . Bạn sẽ hay quên gọi hàm này để vẽ lại cửa sổ. .WHILE TRUE invoke GetMessage, ADDR msg,NULL,0,0 .BREAK .IF (!eax) invoke TranslateMessage, ADDR msg invoke DispatchMessage, ADDR msg .ENDW Tại thời điểm này, cửa sổ ứng dụng đã có trên màn hình. Nhưng nó không thể tương tác với người dùng. Vì vậy, nói nôm na là bạn phải làm cho nó nhận biết và xử lý các sự kiện xảy ra khi có tương tác giữa cửa sổ và người dùng. Chúng ta hoàn toàn thực hiện được điều này với một vòng lặp thông điệp. Chỉ có một vòng lặp thông điệp cho mỗi module. Vòng lặp thông điệp này sẽ kiểm tra liên tục các thông điệp tới cửa sổ bằng cách sử dụng hàm GetMessage. GetMessage sẽ chuyển một con trỏ đến cấu trúc MSG của HĐH . Cấu trúc MSG này với đầy đủ các thông tin về thông điệp mà HĐH muốn gởi đến một cửa sổ trong module. Hàm GetMessage chỉ trả về giá trị khi không còn thông điệp nào gởi tới cửa sổ trong module. Suốt trong thời gian đó, HĐH có thể cho điều khiển một chương trình khác ứng dụng. Đây là cách xử lý tác vụ đa nhiệm dưới nền Win16. GetMessage trả về FALSE nếu thông điệp WM_QUIT được gửi tới cửa sổ, khi đó vòng lặp thông điệp sẽ kết thúc và thoát chương trình. TranslateMessage là hàm bắt thông điệp từ bàn phím khởi tạo thông điệp WM_CHAR trong hàng đợi thông điệp. Thông điệp WM_CHAR chứa giá trị ASCII của phím được nhấn, mã ASCII này có mối quan hệ với mã quét “thô” của của phím được trình điều khiển của bàn phím dịch ra. Bạn có thể bỏ qua hàm này nếu chương trình của bạn không tiến hành thao tác ấn phím (keystrokes). DispatchMessage gởi thông điệp đến thủ tục WndProc đảm trách việc xác định cửa sổ mà thông điệp này muốn gởi đến nó. mov eax,msg.wParam ret WinMain endp Người dịch: Benina (REA TEAM) Trang 19 Tổng hợp và hiệu chỉnh: NhatPhuongLe (VNCERT TEAM)
  20. Iczelion’s Tutorial Win32 ASM Tutorial 3 : A Simple Window Nếu vòng lặp thông điệp kết thúc, mã exit được lưu vào tham số wParam của cấu trúc MSG. Bạn có thể lưu mã exit này vào thanh ghi eax để hàm trả về giá trị, trao quyền điều khiển cho HĐH. Tại thời điểm này, HĐH sẽ không dùng giá trị trả về, nhưng nó sẽ an toàn hơn và thực hiện đúng nguyên tắc. WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM Đây là một thủ tục window. Bạn không nhất thiết phải đặt tên nó là WndProc. Tham số đầu tiên là hWnd, là handle của cửa sổ ứng dụng mà thông điệp gửi nó. uMsg là thông điệp. Chú ý rằng uMsg không là một cấu trúc MSG. Thực ra, nó chỉ là một số. HĐH định nghĩa hàng trăm thông điệp, nhưng phần lớn chúng, thì chương trình của bạn không sử dụng. HĐH sẽ gởi một thông điệp thích hợp đến một cửa sổ trong trường hợp có liên quan đến cửa sổ đó mà thông điệp đó xảy đến. Thủ tục window nhận thông điệp và “trả lời” lại một cách thông minh. wParam và IParam là hai tham số mở rộng được dùng bởi một vài thông điệp. Bên cạnh thông điệp được gởi tới cửa sổ, thêm vào đó dữ liệu kèm theo thông điệp gởi tới cửa sổ. Những dữ liệu này được gởi tới thủ tục window bởi IParam và wParam. .IF uMsg==WM_DESTROY invoke PostQuitMessage,NULL .ELSE invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .ENDIF xor eax,eax ret WndProc endp Ở đây đã đến phần cốt yếu. Đây là nơi chứa đoạn code thông minh nhất của chương trình. Đoạn code này đáp ứng trả lời cho mỗi thông điệp gởi tới cửa sổ trong thủ tục window. Trước hết, chương trình phải kiểm tra thông điệp gởi tới cửa sổ, để biết nó có phải là một thông điệp hay không và liên quan đến tác vụ nào. Nếu nó là thông điệp, bạn muốn làm bất cứ điều gì để tương phản hồi lại và trả về giá trị Zero trong eax. Nếu nó không phải, bạn phải gọi hàm DefWindowProc, chuyển tất cả các tham số mà bạn nhận được vào trong nó cho quá trình xử lý thông điệp mặc định của HĐH. Hàm DefWindowProc là một hàm API, xử lý thông điệp mà chương trình của bạn không quan tâm. Chỉ có thông điệp mà bạn phải phản hồi lại là WM_DESTROY. Thông điệp này được gởi đến thủ tục window khi nào cửa sổ ứng dụng bị đóng lại. Trong thời gian cửa sổ nhận được thông điệp này, nó sẽ biến mất khỏi màn hình. Đây chỉ là sự thông báo cửa sổ của bạn mất hiệu lực, do chính bạn muốn trở về màn hình desktop. Nếu bạn muốn có cách để ngăn không cho người dùng đóng cửa sổ ứng dụng, bạn sẽ thi hành thông điệp WM_CLOSE. Bây giờ trở lại với thông điệp WM_DESTROY, bạn phải gọi hàm API PostQuitMessage nó sẽ gởi thông điệp WM_QUIT tới module. Thông điệp WM_QUIT sẽ nhờ hàm API GetMessage trả về giá trị Zero trong eax, đồng thời sẽ kết thúc vòng lặp thông điệp và thoát ứng dụng. Bạn có thể gởi thông điệp WM_DESTROY đến thủ tục window bằng cách gọi hàm DestroyWindow. Người dịch: Benina (REA TEAM) Trang 20 Tổng hợp và hiệu chỉnh: NhatPhuongLe (VNCERT TEAM)
Đồng bộ tài khoản