Trao đổi với tôi

http://www.buidao.com

6/24/09

[Iczelion's Tuts] TUTORIAL 3: A SIMPLE WINDOW

TUTORIAL 3: A SIMPLE WINDOW

Trong tut này, chúng ta sẽ xây dựng một chương trình windows hiển thị một cửa sổ window với đầy đủ các chức năng trên desktop.

Download file ví dụ:

LÝ THUYẾT

Chương trình Windows dựa trên các hàm API cho GUI (Giao diện người dùng đồ họa) của chúng ta. Đây là cách mà trước tiên nó giúp ích cho cả người sử dụng và lập trình viên. Đối với người sử dụng, ko phải học điều khiển GUI cho mỗi chương trình mới, GUI của chương trình Windows là tương tự nhau. Đối với các lập trình viên, GUI codes có sẳn đã được test và sẳn sàng cho sử dụng. Mặt trái việc này là các lập trình viên bị gia tăng sự phức tạp trong việc code. Để cài đặt hay thao tác trên bất kỳ các đối tượng GUI như là các windows, menu hay icons, lập trình viên phải theo một rule chính xác. Nhưng có thể khắc phục bằng cách modular programming hay OPP paradigm

Tôi sẽ phát thảo những bước cần thiết để cài đặt một window trên desktop như dưới đây:

1/. Lấy instance handle chương trình của bạn (cần thiết)

2/. Lấy command line (ko cần thiết trừ khi chương trình muốn tiến hành 1 command line)

3/. Register (đăng ký) window class (cần thiết, trừ khi bạn dùng lọai window được định nghĩa trước. VD: Messagebox hay 1 dialog box)

4/. Cài đặt window (cần thiết)

5/. Show (hiển thị) window trên desktop ( cần thiết trừ khi bạn ko muốn show window ngay)

6/. Refesh (làm tươi) vùng client của window

7/. Enter (nhập vào) 1 vòng lặp vô hạn để kiểm tra những messages (thông điệp) từ window

8/. Nếu message xảy ra, chúng sẽ được thực thi bởi hàm chuyên trách chịu trách nhiệm của window

9/. Thóat chương trình nếu người dùng close window

Như bạn thấy đó, cấu trúc của một chương trình window thật phức tạp so với chương trình Dos. Nhưng trong thế giới Winodws khác hòan tòan so với thế giới Dos. Những chương trình winodws có thể sống êm thắm với nhau. Chúng phải tuân thủ theo các rules (quy tắc) . Bạn là một lập trình viên cũng phải nghiêm ngặt hơn trong thói quen và lối lập trình của mình

NỘI DUNG:

Dưới đây là nguồn code của một chương trình window đơn giản của chúng ta. Trước khi đi vào các chi tiết “đẫm máu” 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 sẽ put (đặt) tất cả các hằng số windows, cấu trúc và các prototypes hàm trong một file include và include (gắn) nó ngay lúc bắt đầu code của file .asm của bạn. Nó sẽ giúp bạn nhiều typo (lỗi ấn lót) và effort (sự cố gắng). Hiện thời, file include hòan chỉnh nhất cho MASM là hutch’s windows.inc , bạn có thể download từ site của anh ấy hoặc của tôi (tác giả của tut này). 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ẽ put (đặt) chúng vào trong một file include tách riêng.

-Dùng chỉ thị includelib để chỉ rõ thư viện import được sử dụng trong chương trình của bạn. Cho ví dụ, nếu chương trình của bạn gọi MessageBox, bạn sẽ put dòng này:

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 rằng chương trình của bạn có sử dụng những hàm trong thư viện import. Nếu chương trình gọi hàm trong nhiều thư viện , chỉ add một includelib cho mỗi thư viện bạn dùng. Sử dụng chỉ thị IncludeLib, bạn sẽ ko bị sai về các thư viện import lúc link. Bạn có thể dùng /LIBPATH để nói cho Link biết tất cả các libs ở đâ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 origin được dùng trong file include windows. Điều này giúp bạn đỡ nhức đầu khi phải xem vào các items trong Win32 API reference.

-Sử dụng makefile để tự động tiến trình assembling của bạn.

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib ; calls to functions in user32.lib and
kernel32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

.DATA ; initialized data
ClassName db "SimpleWinClass",0 ; the name of our window class
AppName db "Our First Window",0 ; the name of our window

.DATA? ; Uninitialized data
hInstance HINSTANCE ? ; Instance handle of our program
CommandLine LPSTR ?
.CODE ; Here begins our code
start:
invoke GetModuleHandle, NULL ; get the instance handle of our program.
; Under Win32, hmodule==hinstance mov hInstance,eax
mov hInstance,eax
invoke GetCommandLine ; get the command line. You don't have to call this function IF
; your program doesn't process the command line.
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
;call the main function
invoke ExitProcess, eax ; quit our program. The exit code is returned in eax from WinMain.

WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX ; create local variables on stack
LOCAL msg:MSG
LOCAL hwnd:HWND

mov wc.cbSize,SIZEOF WNDCLASSEX ; fill values in members of wc
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 ; register our window class
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 ; display our window on desktop
invoke UpdateWindow, hwnd ; refresh the client area

.WHILE TRUE ; Enter message loop
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam ; return exit code in eax
ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY ; if the user closes our window
invoke PostQuitMessage,NULL ; quit our application
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam ; Default message processing
ret
.ENDIF
xor eax,eax
ret
WndProc endp

end start

PHÂN TÍCH:

Bạn có thể đã thấy được một chương trình Windows đơn giản cần phải code rất nhiều (như đọan code trên). Nhưng phần lớn code của chúng giống như là 1 template(khuôn mẫu) code mà bạn có thể copy từ một nguồn file code đến một file khác. Hay nếu bạn tham chiếu, bạn có thể assemble vài nguồn code của chúng vào trong một thư viện để được 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. Trong thực tế , đây là những gì C compilers làm ra. Chúng cho phép bạn viết code hàm WinMain mà ko quấy rầy bạn làm những công việc “vặt vảnh quản gia” khác. Khác với C compilers là bạn phải có một hàm có tên là WinMain, sẽ ko thể kết hợp code của bạn với đọan đầu và đọ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 họặc không có hàm nào cũng ko sao.

Bạn hảy chuẩn bị sẳn sàng. Sắp tới đây là một tut rất dài. Chúng ta hảy phân tích chương trình này để đánh tan bí ẩn của nó.

.386
.model flat,stdcall
option casemap:none

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

Ba dòng trước tiên rất cần thiết.

.386 nói cho MASM chúng ta dự định sử dụng cấu trúc 80386 để set trong chương trình.

.model flat,stdcall nói cho MASM rằng chương trình của chúng ta sử dụng model định địa chỉ theo flat memory . Cũng như bạn sẽ dùng tham số stdcall để chuyển convention (quy ước gọi hàm) như một mặc định trong chương trình chúng ta.

(option casemap:none theo tut02 : báo cho trình biên dịch tuân thủ trường hợp viết hoa do người dùng chỉ định.)

Kế đến là prototype hàm cho WinMain. Khi chúng ta gọi hàm WinMain sau đó, chúng ta chúng ta phải định nghĩa prototype hàm của nó trước tiên, do đó chúng ta mới có thể invoke nó được.

Chúng ta phải include (gắn) file windows.inc lúc bắt đầu trong nguồn code. Nó chứa cấu trúc quan trọng và 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ỳ chương trình sọan thảo text 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ố. hutch và tôi (tác giả tut) đang chỉnh sửa cập nhật. Bạn có thể add (thêm) các items mới nếu nó không có trong file.

Chúng ta gọi hàm API chứa trong user32.dll. (Ví dụ : CreatWindowEx, RegisterWindowClassEx ) và kernal32.dll (ExitProcess), vì vậy chúng ta phải link (liên kết) chương trình của chúng ta với hai thư viện import. Câu hỏi kế tiếp là: Làm thế nào tôi biết được thư viện import gì được liên kết với chương trình của chúng ta?. Trả lời: bạn phải biết các hàm API cư trú ở đâu mà chương trình bạn gọi các hàm đó. Ví dụ: nếu bạn gọi một hàm API trong gdi32.dll , bạn phải link với gdi32.lib.

Đây là sự tiếp cận mở đầu của MASM. Cách TASM linking thư viện import thì đơn giản hơn: chỉ link đế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 .DATA chúng ta khai báo những chuổi kết thúc bằng zero (ASCIIZ strings)(ngdịch: tôi sẽ dịch là “chuổi kết zero”): Classname là tên của window class chúng ta và Appname là tên window của chúng ta. Chú ý rằng có hai biến được khởi tạo từ đầu.

Trong .DATA? , hai biến được khai báo: hInstance ( instance handle (nó có nghĩa gần như là: “chỉ số thông hành hiện tạm thờicủa chương trình chúng ta trên Windows) hay còn gọi là “thẻ quản chương trình”)CommandLine ( command line của chương trình chúng ta ). Lọai dữ liệu lạ HINSTANCE và LPSTR , thật sự là những cái tên mới cho DWORD. Bạn có thể nhìn thấy chúng trong windows.inc . Chú ý rằng tất cả các biến trong .DATA? section ko khởi tạo giá trị ngay lúc đầu, đó là do chúng không lưu giữ một giá trị nào trong lúc startup (khởi động chương trình), nhưng chúng ta muốn dành một khỏang trống bộ 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

.CODE chứa tất cả các chỉ thị lệnh. Các codes của bạn phải được cư trú giữa : và end . Tên của nhãn label không quan trọng. Bạn có thể đặt tên cho nó bất cứ gì mà bạn thích và miễn sao không phạm đến các naming convention ( qui ước đặt tên) 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 chương trình bạn. Dưới Win32, instance handle và module handle là một và giống nhau. Bạn có thể tưởng tượng rằng instance handle như là một ID (chỉ mục) của chương trình bạn. 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à “nhận lại” (hay lưu giữ nó) nó ngay lúc bắt đầu chương trình của chúng ta .

Chú ý: thực tế dưới Win32, instance handle là một linear address (địa chỉ tuyến tính) của chương trình bạn trong memory.

Trở lại code trên từ một hàm Win32, giá trị trở về của hàm, nếu không có gì khác lạ, có thể tìm thấy trong EAX. Tất cả những giá trị khác được trả về qua những biến được chuyển vào list tham số của hàm mà bạn đã định nghĩa cho lời gọi hàm call.

Một hàm Win32 mà bạn gọi , chúng sẽ luôn “bảo tòan” các thanh ghi segment registers và các thanh ghi EBX,EDI,ESI và EBP. Trái lại, ECX và EDX được xem xét là scratch (xóa) registers và luôn luôn không được định nghĩa vào lúc return (trả về) từ một hàm Win32.

Chú ý: Đừng mong chờ những giá trị của các thanh ghi EAX,ECX và EDX được “duy trì bảo tòan giá trị” xuyên qua lời gọi hàm API.

Dòng dưới tiếp theo là: khi gọi một hàm API, ta “mong đợi” giá trị trả về trong EAX. Nếu bất cứ hàm nào của bạn được gọi bởi Windows, bạn phải làm đúng theo rule sau: duy trì và phục hồi giá trị của các thanh ghi segment và EBX,EDI,ESI và EBP trước khi hàm trả về (return) , nếu khác, chương trình của bạn sẽ bị crash (phá vở) ngay lập tức, điều này include vào trong procedure window (thủ tục window) của bạn và Windows sẽ gọi lại hàm.

Hàm Call GetCommandLine không cần thiết nếu chương trình của bạn không tiến trình một dòng lệnh (command line). 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. Ở đây nó chứa 4 tham số: instance handle của chương trình , instance handle của previous instance của chương trình, command line và window state tại lúc xuất hiện đầu tiên. Dưới Win32 ko có previous instance. Mỗi chương trình tồn tại một mình trong khỏang không gian địa chỉ bộ nhớ của nó , vì vậy giá trị của hPrevInst luôn luôn là 0. Đây là những cặn bả từ những ngày có win16 khi tất cả các instance của một chương trình chạy trong khỏang không addr giống nhau và một instance được nhận biết nếu nó là instance đầu tiên. Dưới Win16, nếu hPrevInst là NULL, thì instance này là cái đầu tiên.

Chú ý: Bạn ko 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. Bạn ko phải dùng một hàm tương đương nào như WinMain cho tất cả chương trình. Bạn có thể paste codes bên trong hàm WinMain kế đến hàm GetCommandLine và chương trình của bạn như thế vẫn có chức năng hòan hảo.

Trước khi trả về (return) từ hàm WinMain , EAX được filled (lắp giá trị) exit code. Chúng ta chuyển exit code như là một tham số cho ExitProcess mà nó kết thúc ứng dụng của chúng ta.

WinMain proc Inst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD

Dòng trên là khai báo hàm của WinMain. Chú ý đến các tham số: hợp thành từng lọai theo sau chỉ thị PROC. Chúng là những tham số mà WinMain chứa trong lời gọi hàm. Bạn có thể tham chiếu những tham số này bởi name (tên) của nó thay vì bằng stack manipulation (thao tác thủ công trên stack). Thêm vào đó , MASM sẽ sinh ra đọan mở đầu và kết thúc codes cho hàm. Vì vậy, chúng ta sẽ ko phải tự quan tâm đến stack frame cho hàm khi enter (vào) và exit (ra) khỏi hàm.

LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND

Chỉ thị LOCAL chỉ định memory từ stack cho biến cục bộ được sử dụng trong hàm. Cụm chỉ thị LOCAL phải đứng ngay dưới chỉ thị PROC. Sau chỉ thị LOCAL là : . Vì vậy LOCAL wc:WNDCLASSEX báo cho MASM biết chỉ định memory trong stack với kích thước là cấu trúc WNDCLASSEX cho tên biến là wc. Chúng ta có thể tham chiếu wc trong codes của chúng ta mà ko gặp một rắc rối khó khăn nào như trong việc dùng stack manipulation. Đó thật sự là một “tín hiệu của Chúa”, tôi nghĩ thế (tác giả hài hước ). Mặt trái của nó là biến cục bộ ko được sử dụng bên ngòai hàm mà chúng được cài đặt và sẽ tự động mất đi khi hàm trả về (return). Hay nói một cách khác bạn ko thể initialize (khởi tạo giá trị đầu tiên: tạo trị đầu) cho biến cục bộ một cách tự động bởi vì chúng là stack memory (bộ nhớ stack) được định vị động khi hàm được enter. Bạn phải ấn định thủ công chúng với những giá trị theo yêu cầu 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 dòng trên có nội dung rất đơn giản. Nó chỉ là lời giới thiệu của những dòng lệnh riêng biệt để thực thi. Concept (nội dung) phía sau tất cả các dòng này là window class. Một window class ko có gì hơn là một bản sơ đồ hay là một bản chi tiết kỹ thuật của một window. Nó định nghĩa những đặc trưng quan trọng riêng biệt của một window như là icon, cursor của nó, hàm đảm nhiệm cho nó, màu color của nó….ect (vân vân). Bạn cài đặt một window từ một window class. Đây là vài thành phần của object oriented(xây dựng) concept (nội dung). Nếu bạn muốn cài đặt nhiều hơn 1 window với chi tiết kỹ thuật giống như vậy, thật là hợp lý khi store (cất giữ) tất cả các chi tiết kỹ thuật của chúng trong chỉ một nơi và tham chiếu đến chúng khi cần thiết. Lược đồ này giảm bớt nhiều bộ nhớ bởi tránh việc sao lại những thông tin giống nhau. Hảy nhớ rằng, Windows được thiết kế trong quá khứ khi chip bộ nhớ bị giới hạn và computer chỉ có 1 MB memory. Windows phải rất hiệu quả trong việc sử dụng nguồn bộ nhớ khan hiếm. Điểm cần biết ở đây là: nếu bạn định nghĩa window của riêng bạn , bạn phải lắp đầy chi tiết kỹ thuật đã tồn tại sẳn của window của bạn trong một cấu trúc WNDCLASS hay WNDCLASSEX và gọi hàm RegisterClass hay RegisterClassEx trước khi bạn có thể create (cài đặt) window của bạn. Bạn chỉ phải register (đăng ký) một window class cho mỗi lọai window mà bạn muốn create một window từ đó.

Windows có thể định nghĩa trước nhiều lớp window classes khác nhau, như button và edit box. Đối với những window này (hay còn gọi là các điều khiển controls) , bạn ko phải register (đăng ký) một window class, chỉ gọi CreateWindowEx với tên class được định nghĩa trước.

Thành phần quan trọng nhất của WNDCLASSEX , là lpfnWndProc . lpfn đặt một con trỏ long-pointer đến hàm. Dưới Win32, ko có con trỏ pointer “near” hay “far”. , chỉ duy nhất một lọai pointer bởi vì model của bộ nhớ là FLAT memory. Nhưng đây cũng là những cặn bả từ thời Win16. Mỗi window class phải được liên kết với một hàm được gọi từ thủ tục window (window procedure). Thủ tục window chịu trách nhiệm xử lý thông điệp (message , cũng có thể dịch là “tín hiệu” ) của tất cả các windows đã được cài đặt từ window class được liên kết. Windows (HĐH) sẽ gởi thông điệp đến thủ tục window để khai báo cho nó những biến cố quan trọng liên quan đến các cửa sổ windows mà nó chịu trách nhiệm, như bàn phím người dùng hay con chuột input nhập vào. Nó sẽ báo cho thủ tục window để phản ứng đáp lại một cách thông minh đối với mỗi thông điệp window mà nó nhận. Bạn sẽ tốn rất nhiều thời gian để viết các điều khiển biến cố trong thủ tục window.

Tôi 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 với nhau bằng cách dùng “or”

lpfnWndProc : Địa chỉ của thủ tục window chịu trách nhiệm cho các windows được cài đặt từ class này

cbClsExtra : Chỉ định số lượng extra bytes (những byte mở rộng) để cấp theo cấu trúc window class. Hệ điều hành 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 window class ở đây.

cbWndExtra : Chỉ định số lượng extra bytes để cấp theo window instance . Hệ điều hành 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 được cài đặt bới dùng chỉ thị CLASS trong file nguồn, nó phải set thành phần này bằng DLGWINDOWEXTRA

hInstance : Instance handle (chỉ số thông hành hiện thời, thẻ quản chương trình) của module

hIcon : Handle (thẻ quản) của icon. Lấy nó từ call : LoadIcon

hCursor : Handle của cursor. Lấy nó từ call : LoadCursor

hbrBackground : màu nền của windows được cài đặt từ class

lpszMenuName : Mặc định (defautlt) menu handle (thẻ quản menu) cho windows được cài đặt từ class

lpszClassName :Tên của class window này

hIconSm : Handle của icon nhỏ (small icon)mà nó được liên kết với class window. Nếu thành phần này là NULL, hệ thống sẽ tìm nguồn icon được chỉ ra 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ý window class, chúng ta có thể gọi CreateWindowEx để cài đặt window của chúng ta trên nền tảng window class đã tính tóan. Chú ý có 12 tham số cho hàm này

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 Extra window. Đây là một tham số mới được thêm vào từ hàm củ là CreatWindow. Bạn có thể put một lọai window mới cho windows 95 và NT ở đây. Bạn có thể chỉ định kiểu window thông thường của bạn trong dwStyle nhưng nếu bạn muốn vài lọai window đặc biệt như topmost window, bạn phải chỉ rõ chúng tại đây. Bạn có thể dùng NULL nếu bạn ko muốn kiểu extra window

lpClassName : (cần thiết) Địa chỉ của chuổi string ASCIIZ chứa tên của window class mà bạn muốn dùng như một template cho window này. Class có thể là class do chính bạn registered (đã đăng ký) hay window class định nghĩa trước. Như ở trên, mọi window bạn đã cài đặt dựa trên nền tảng của window class

lpWindowName : Địa chỉ của string ASCIIZ chứa tên của window. Nó sẽ hiện ra trên thanh tiêu đề của window. Nếu thông số này là NULL, thanh tiêu đề của window là blank (trắng)

dwStyle : Kiểu của window Bạn có thể chỉ định sự hiển thị ở đây. Passing (chuyển cho nó giá trị là) NULL thì vẫn OK nhưng window sẽ ko có hệ thống menu box, ko có nút button minimize-maximize, và ko có button đóng window. Window sẽ ko có nhiều chức năng sử dụng. Bạn cần nhấn phím Alt-F4 để đóng nó. Kiểu window thông thường sử dụng nhiều nhất là WS_OVERLAPPEDWINDOW. Một kiểu window chỉ là một bit flag. Vì vậy bạn có thể kết hợp nhiều kiểu window khác nhau bằng cách “or” để hòan thành sự hiển thị đã định trú của window.

Kiểu WS_OVERAPPEDWINDOW thực tế là một kết hợp của nhiều kiểu window thông dụng nhất bằng cách kết hợp này.

X,Y : Tọa độ của góc trái trên của window. Thông thường giá trị này sẽ là CW_USEDEFAULT , đó là bạn muốn Windows (HĐH) lựa chọn cho bạn nơi để đặt window trên desktop.

nWidth, nHeight : chiều rộng và chiều cao của window tính bằng pixels. Bạn cũng có thể sử dụng CW_USEDEFLAULT để bảo Windows chọn lựa chiều rông và chiều cao thích hợp cho bạn

hWndParent :Một handle (thẻ quản) của cửa sổ cha (nếu tồn tại). Tham số này nói cho Windows (HĐH) rằng cửa sổ này là con (cấp thấp) của vài cửa số khác, nếu nó như vậy,sẽ có 1 cửa sổ nào là cửa sổ cha. Chú ý rằng đây ko phải là quan hệ cha-con như multiple document interface (MDI, nếu ai lập trình VB,Delphi...sẽ hiểu cụm từ này) . Cửa sổ con ko nhất định là vùng client của cửa sổ cha .Mối quan hệ này sử dụng bên trong window một cách rõ ràng. 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. Đối với ví dụ của chúng ta: Chỉ có một cửa sổ window, chúng ta sẽ chỉ định tham số này là NULL

hMenu : Một handle (thẻ quản) của menu window. NULL nếu class 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 *default* menu cho window class. Mọi window được cài đặt từ window class(lớp cửa sổ)này sẽ có menu giống nhau do mặc định. Trừ khi bạn chỉ định một *overriding* menu cho một window đặc biệt qua tham số hMenu. hMenu thực tế là một tham số mục-đích-kép (dual-purpose). Trong case bạn muốn cài đặt cửa sổ của một kiểu window được định nghĩa trước(ie control), hMenu được sử dụng như một control’ID (chỉ mục của một lọai điều khiển control). Windows (HĐH) có thể lựa chọn hMenu như là một handle menu hay là một control’ID bằng cách nhìn vào tham số lpClassName. Nếu nó là tên của một window class định nghĩa trước (ví dụ như các class button, edit ...), hMenu sẽ là một control ID. Nếu ko, nó là một handle (thẻ quản) cho menu window.

hInstance :Là instance handle (chỉ số thông hành hiện thời, thẻ quản của chương trình) cho module chương trình cài đặt window

lpParam : Con trỏ lựa chọn trỏ đến cấu trúc dữ liệu được chuyển đến window. Điều này được sử dụng bới MDI window để chuyển dữ liệu CLIENTCREATESTRUCT. Thông thường giá trị này set là NULL, có nghĩa là ko có dữ liệu được chuyển qua CreateWindow() . Cửa sổ window có thể khôi phục (lấy lại) 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 trả về (return) thành công từ hàm CreateWindowEx, handle của window được trả về trong eax. Chúng ta phải giử lấy giá trị này để dùng trong tương lai. Window mà chúng ta đã cài đặt ko tự động hiển thị. Chúng ta phải gọi hàm ShowWindow với handle window và *display state* (*trạng thái hiển thị*) đã tồn tại của window để làm cho nó hiển thị trên màn hình. Kế đến bạn gọi UpdateWindow để window 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 có thể bỏ quên hàm call này.

.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 window của bạn sẽ up lên màn hình. Nhưng nó ko thể nhận input từ thế giới bên ngòai. Vì vậy bạn phải *inform* (nói cho) cho nó biết các biến cố thích hợp. Chúng ta thực hiện hòan tòan đượ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 Windows với hàm GetMessage. GetMessage sẽ chuyển một con trỏ đến cấu trúc MSG của Windows . Cấu trúc MSG này sẽ được filled (lắp) với các thông tin về thông điệp mà Windows muốn gởi đến một cửa sổ trong module. Hàm GetMessage sẽ ko trả về cho đến khi có một thông điệp cho một cửa sổ trong module. Suốt trong thời gian đó, Windows có thể cho điều khiển đến một chương trình khác. Đây là hình thức của lược đồ đa nhiệm tác vụ trong win16 platform. GetMessage trả về FALSE nếu thông điệp WM_QUIT được nhận, trong vòng lặp thông điệp, sẽ kết thúc vòng lặp và thóat chương trình.

TranslateMessage là một hàm tiện ích bắt lấy input từ keyboard “thô” và sinh ra 1 thông điệp mới (WM_CHAR) được đặt trong dãy thông điệp tiếp nối nhau. Thông điệp WM_CHAR chứa giá trị ASCII từ key nhấn vào, chúng dễ dàng được đề cập đến hơn là với scan code của keyboard “thô”. Bạn có thể bỏ quên hàm này nếu chương trình của bạn ko tiến hành keystrokes (nhấn key bàn phím)

DispatchMessage gới dữ liệu thông điệp đến thủ tục window đảm trách cho cửa sổ window cá biệt mà thông điệp đó muốn gởi đến nó.

mov eax,msg.wParam
ret
WinMain endp

Nếu vòng lặp thông điệp kết thúc, exit code được cài nạp vào trong thành phần wParam của cấu trúc MSG. Bạn có thể nạp exit code này vào eax để quay về Windows. Tại thời điểm hiện hành, Windows sẽ ko dùng giá trị tr về, nhưng nó đảm bảo hơn về mặt an tòan và play đúng rule (quy tắc).

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM

Đây là thủ tục window. Bạn ko phải đặt tên nó là WndProc. Tham số đầu tiên là hWnd, là window handle (thẻ quản window) của window mà message (thông điệp) dành cho nó. uMsg là message (thông điệp). Chú ý rằng uMsg ko là một cấu trúc MSG. Thực ra nó chỉ là một số. Windows định nghĩa hàng trăm messages (thông điệp). Phần lớn chúng, chương trình của bạn ko liên quan đến. Windows sẽ gởi một thông điệp thích hợp đến một window trong trường hợp có liên quan đến window đó mà thông điệp đó xảy đến. Thủ tục window nhận thông điệp và tác động trở lại nó một cách thông minh. wParamIParam là tham số mở rộng được dùng bởi vài messages (thông điệp). Vài thông điệp (message) đó thực hiện gởi dữ liệu đi cùng, được thêm vào trong chính thông điệp đó. Dữ liệu này được chuyển đến 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 đọan code thông minh nhất của chương trình chúng ta. Đọan code này đáp ứng trả lời cho mỗi thông điệp window trong thủ tục window. Code của bạn phải kiểm tra thông điệp window để thấy nó có phải là một thông điệp ko và liên quan đến tác vụ nào.Nếu nó là 1 thông điệp, bạn muốn làm bất cứ điều gì để đáp ứng lại thông điệp và trả về giá trị zero trong eax. Nếu nó ko 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 tiến trình mặc định. Hàm DefWindowProc là một hàm API , thực thi thông điệp mà chương trình của bạn ko quan tâm.

Chỉ có thông điệp mà bạn phải đáp ứng là WM_DESTROY. Thông điệp này được gởi đến thủ tục window của bạn bất cứ khi nào window của bạn được closed. Trong thời gian cửa sổ của bạn nhận được thông điệp này, window của bạn sẽ rời khỏi màn hình. Đây chỉ là một sự thông báo cửa sổ của bạn mất hiệu lực, do chính bạn sẳn sàng trở về Windows. Trong việc đáp ứng thông điệp này , bạn có thể thi hành việc “quản gia” cho đến khi trở về Windows. Bạn ko có chọn lựa để quit (thóat) khi nó đến trạng thái này. Nếu bạn muốn có một sự thay đổi để stop người sử dụng lại từ việc đóng window của bạn , bạn sẽ thi hành message WM_CLOSE. Bây giờ trở về lại WM_DESTROY, sau khi thực hiện những việc vặc vảnh quản gia , bạn phải gọi PostQuitMessage nó sẽ post WM_QUIT trở về lại module của bạn. WM_QUIT sẽ make (làm) GetMessage trả về zero trong eax, trong lúc trở về, kết thúc vòng lặp thông điệp và quit window. Bạn có thể gởi thông điệp WM_DESTROY đến chính thủ tục window của bạn bằng cách gọi hàm DestroyWindow.

--------The End Tut---------------------------------

Benina (21/11/2004)

Update 21/11/2005

(Không đồng ý bất kỳ ai sử dụng tài liệu này cho mục đích thương mại nếu ko được phép của người dịch)