这个是本系列教程的最后一篇了。下面我可能会贴出关于用汇编写一个游戏的实例。仍然是面向基础,面向初学者的。在此,也要感谢原作者:james的大力支持,无偿提供本文的中文译权。
-译者taowen2002
13.0 Windows中的窗口
在本章中,我们将创建一个有窗口的程序
12.1窗口
你可能已经猜到了Windows之所以称为Windows的原因了。在Windows中,有两种程序:GUI程序和控制台程序。控制台模式的程序看上去就像Dos程序,它们在一个似-dos的窗口中运行。你使用的大多数程序是GUI(图形用户界面)程序,它们有一个用于和用户交互的图形界面。这是由创建窗口来完成的。几乎你在Windows中看见的每一件东西都是窗口。首先,你创建一个父窗口,然后是像编辑框,静态控件(文本标签-译者注),按钮等的自窗口(控件)。
13.2窗口类
每一个窗口都有名字。你为你的父窗口定义你自有的类。对于控件,你可以使用Windows的标准类名(例如,“Edit”,“Static”,“Button”)
13.3结构
你程序中的窗口类是用“RegisterClassEx“函数注册的。(RegisterClassEx是RegisterClass的扩展版本,后者已经不太使用了)这个函数的声明是:
ATOM RegisterClassEx(
CONST WNDLCASSEX *lpwcx//有类数据的结构之地址
);
lpwcx:指向WNDCLASSEX结构。在把它传递给函数之前,你必须用适当的类属性填写结构。
唯一的参数是指向结构的指针。先来看看一些结构的基本知识:
一个结构是一些变量(数据)的集合。它用STRUCT定义:
SOMESTRUCTURE STRUCT
dword1 dd ?
dword2 dd ?
some_word dw ?
abyte db ?
anotherbyte db ?
SOMESTRUCTURE ENDS
(结构名不一定要大写)
你可以用问号把你的变量定义在未初始化data部分。现在你可以根据定义创建一个结构:
Initialized
Initializedstructure SOMESTRUCTURE <100,200,10,'A',90h>
Uninitialized
UnInitializedstructure SOMESTRUCTURE <>
在第一个例子中,创建了一个新的结构(用初始化了的结构保存它的offset),而且结构的每一个元素用初始化数值填写了。第二个例子只是告诉masm为结构名分配内存,而且每个数据元素用0初始化。在创建了结构之后,你可以在代码中使用它:
mov eax, Initializedstructure.some_word
; eax现在是 10
inc UnInitializedstructure.dword1
; 结构的dword1步增
这是结构怎样存在内存中的:
内存地址
内容
offset of Initializedstructure
100 (dword, 4 bytes)
offset of Initializedstructure + 4
200 (dword, 4 bytes)
offset of Initializedstructure + 8
10 (word, 2 bytes)
offset of Initializedstructure + 10
65 or 'A' (1 byte)
offset of Initializedstructure + 11
90h (1 byte)
12.3 WNDCLASSEX
现在已经了解了足够多的结构知识,让我们处理RegisterClassEx吧。在《win32程序员参考》中,你可以查找WNDCLASSEX结构的定义。
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;
解释
cbSize
WNDCLASSEX结构体的大小。用于Windows的认证。你可以用SIZEOF得到它的大小:
mov wc.cbsize, SIZEOF WNDCLASSEX
style
为类指定一个样式(如果窗口要有滚动条,加上重画标志。等等)
lpfnWndProc
指向Windows过程的指针(本章后面有更多内容)
cbClsExtra
在Windows类结构后本配多少额外内存。对我们不重要
cbWndExtra
在Windows实例后分配多少额外内存。对我们也不重要
hInstance
你程序的实力句柄。你可以用GetMoudleHandle函数得到这个句柄
hIcon
窗口图标资源的句柄
hCursor
窗口光标资源的句柄
hbrBackground
用于填充背景的画刷句柄,或是标准刷子类型中的一个,如 COLOR_WINDOW, COLOR_BTNFACE , COLOR_BACKGROUND.
lpszMenuName
指向一个指定菜单类名的零结尾字符串
lpszClassName
指向一个指定窗口类名的零结尾字符串
hIconSm
一个和窗口类关联的小图标句柄
在你的Win32文件夹中创建一个名为firstWindow的文件夹并在这个文件夹中创建一个名为window.asm的新文件,输入一下内容:
.486
.model flat, stdcall
option casemap:none
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\gdi32.lib
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
include \masm32\include\gdi32.inc
然后创建一个名为make.bat的.bat文件。把这些文本粘贴进去:
@echo off
ml /c /coff window.asm
link /subsystem:windows window.obj
pause>nul
从现在开始,为了节省空间,仅显示小段的代码。你可以通过点<view code>来显示教程此处的全部代码。完整的代码在新窗口中显示。
译者注:为了方便,我又把这些放回来了。
13.4注册类
现在我们在名为WinMain的过程中注册类。该过程中完成窗口的初始化。
把这些加入你的汇编文件:
WinMain PROTO STDCALL :DWORD, :DWORD, :DWORD
.data?
hInstance dd ?
.code
invoke GetModuleHandle, NULL
mov hInstance, eax
invoke WinMain, hInstance, NULL, NULL, SW_SHOWNORMAL
end start
这些代码通过GetModuleHandle得到模块句柄,并把模块句柄放入hInstance变量中。这个句柄在Windows API中频繁使用。然后它调用WinMain过程。这不是一个API函数,而是一个我们将要定义的过程。原型是:WinMain PROTO STDCALL :DWORD, :DWORD, :DWORD, :DWORD,因而是一个带4个参数的函数:
<view code>
现在把这些代码放在end start:前
WinMain proc hInst:DWORD, hPrevInst:DWORD, CmdLine:DWORD, CmdShow:DWORD
ret
WinMain endp
你根本就不需要用这个winmain过程,但这是一种十分普遍的处世化你的程序的方法。Visual C自动初始化这个函数的参数,但我们必须自己来做。现在不要管hPrevInst和CmdLine。集中注意在hInst和CmdShow上。Hinst是实例句柄(=模块句柄),CmdShow是定义窗口该如何显示的标志。(你可以在API参考关于ShowWindows部分发现更多)
在前面代码中的"invoke WinMain, hInstance, NULL, NULL, SW_SHOWNORMAL"用正确的实例句柄和显示标志调用这个函数。现在我们可以在WinMain中写我们的初始化代码了。
WinMain proc hInst:DWORD, hPrevInst:DWORD, CmdLine:DWORD, CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL hwnd:DWORD
ret
WinMain endp
这有我们将在过程中要用的两个局部变量
.data
ClassName db "FirstWindowClass",0
.code
WinMain proc hInst:DWORD, hPrevInst:DWORD, CmdLine:DWORD, CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL hwnd:DWORD
; now set all the structure members of the WNDCLASSEX structure wc:
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 hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW
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
ret
WinMain endp
让我们来看看发生了什么:
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
初始化了结构的大小(这是RegisterClassEx要求的)。设置类的样式为”CS_HREDRAW or CS_VREDRAW”,然后设置了窗口过程的offset。你在后面会知道什么是窗口过程,现在你仅需要记住你需要WndProc过程的地址。该地址可以通过“offset WndProc”获得。Cb.ClsExtra和cb.WndExtra我们没有使用因而设它们为Null。
Push hInst
Pop wc.hInstance
Wc.hInstance设为WinMain的hInst参数。为什么我们不用:mov wc.hInstance, hInst?因为mov指令不允许从一个地址移到另一个地址。通过push/pop,值被压入栈,然后又弹入目标中。
mov wc.hbrBackground, COLOR_WINDOW
mov wc.lpszMenuName, NULL
mov wc.lpszClassName, OFFSET ClassName
类的背景色被设为COLOR_WINDOW,没有定义菜单(null)而且lpszClassName设为一个指向零结尾的类名字符串:“FirstWindowClass”它应该是一个在你的程序中定义的唯一名字。
invoke LoadIcon, NULL, IDI_APPLICATION
mov wc.hIcon, eax
mov wc.hIconSm, eax
窗口需要一个图标。但又因为我们要一个指向图标的句柄,我们使用LoadIcon来载入图标并获得句柄。LoadIcon有两个参数:hInstance和lpIconName。HInstance是包含图标的可执行文件的模块句柄。LpIconName是一个指向图标资源和图标ID的字符串的指针。如果你用NULL为hInstance,你可以从一些标准图表中选这一个(这却是是因为我们在这里还没有图标资源)hIconSm是小图标,你可以对它使用相同的句柄。
invoke LoadCursor,NULL,IDC_ARROW
mov wc.hCursor,eax
对光标也一样。NULL作hInstance,并用一个标准光标类型:IDC_ARROW,标准Windows箭头型光标。
invoke RegisterClassEx, ADDR wc
现在,最终用RegisterClassEx来注册类,通过一个指向WNDCLASSEX结构的指针作参数。
<view code>
13.5创建窗口
现在,你已经注册了一个类,你可以使用它创建一个窗口:
HWND CreateWindowEx(
DWORD dwExStyle, // extended window style
LPCTSTR lpClassName, // pointer to registered class name
LPCTSTR lpWindowName, // pointer to window name
DWORD dwStyle, // window style
int x, // horizontal position of window
int y, // vertical position of window
int nWidth, // window width
int nHeight, // window height
HWND hWndParent, // handle to parent or owner window
HMENU hMenu, // handle to menu, or child-window identifier
HINSTANCE hInstance, // handle to application instance
LPVOID lpParam // pointer to window-creation data
);
DwExstyle和dwStyle是两个决定窗口样式的参数。
LpClassName 是一个指向你注册了的类名的指针。
LpWindowName 是你窗口的名字(如果有的话,这将成为你窗口的标题)
X, Y, nWidth, nHeight 决定你窗口的位置和大小
HMenu 是菜单窗口的句柄(在后面讨论,现在为空)
HInstance 是程序实例的句柄
LpPararm 是你能在你的程序中使用的扩展值
.data
AppName "FirstWindow",0
.code
INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,400,300,NULL,NULL,\
hInst,NULL
mov hwnd, eax
invoke ShowWindow, hwnd, SW_SHOWNORMAL
invoke UpdateWindow, hwnd
(注意\使汇编器读下一行的时候好像还在同一行)
我们的代码将用我们刚刚注册的类名创建一个新的窗口。标题是“FirstWindow”(程序名,AppName),样式是WS_OVERLAPPEDWINDOW,这是一个创建有标题,系统菜单,可缩放边框和最大化/最小化按钮的窗口样式。CW_USERDEFAULT作为x和y的位置会使Windows为新窗口使用缺省位置。窗口的(初始)大小是400×300象素。
函数的返回值是窗口句柄,HWND。它储存在局部变量hwnd中。然后窗口用ShowWindow显示。UpdateWindow确保窗口被画出。
13.6消息循环
窗口可以通过消息和你的程序以及其他窗口通讯。无论何时,一条消息被发送给指定的窗口。它的窗口过程都要被调用。每个窗口都有一个消息循环或消息泵(pump)。这是一个无止尽的检查是否给有你的窗口的消息的循环。而且如果有,把消息传递给dispatchMessage函数。这个函数会调用你的窗口过程。消息循环和窗口过程是两个完全不同的东西!!!
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL hwnd:DWORD
LOCAL msg:MSG ;<<<NEW
........
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
这是消息循环看上去的样子。.WHILE TRUE, .ENDW循环到eax为0之前都会继续。如果它接到了WM_QUIT消息,GetMessage返回0,这将关闭窗口因而程序应该在不论GetMessage返回0时退出。如果不是这样(0),消息被传递给TranslateMessage(这个函数把按键翻译为消息)而且消息被Windows用DispatchMessage函数解包。消息本身在一个消息循环的组成部分MSG结构中(LOCAL msg: MSG被加入过程,增加了一个称为msg的局部消息结构)你可以在你的所有程序中用这个消息循环。
13.7窗口过程
消息会被发送往窗口过程。一个窗口过程看上去总是这样:
WndProc PROTO STDCALL :DWORD, :DWORD, :DWORD, :DWORD
.code
WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
mov eax, uMsg
.IF eax==XXXX
.ELSEIF eax==XXXX
.ELSE
invoke DefWindowProc, hWnd, uMsg, wParam, lParam
.ENDIF
ret
WndProc endp
窗口过程总是有4个参数
hWnd 包含窗口句柄
uMsg 消息
wParam 消息的第一个参数(由消息定义)
lParam 消息的第二个参数(由消息定义)
窗口不处理的消息应该传递给DefWindowProc,它会处理这些。一个窗口过程的例子:
WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
mov eax, uMsg
.IF eax==WM_CREATE
invoke MessageBox, NULL, ADDR AppName, ADDR AppName, NULL
.ELSEIF eax==WM_DESTROY
invoke PostQuitMessage, NULL
.ELSE
invoke DefWindowProc, hWnd, uMsg, wParam, lParam
.ENDIF
ret
WndProc endp
这段代码在窗口初始化时显示程序名称。也要注意我加入了WM_DESTROY消息的处理。这条消息在窗口将要关闭的时候发送。程序要用PostQuitMessage作出反应。
现在看看最终的代码:
.486
.model flat, stdcall
option casemap:none
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\gdi32.lib
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
include \masm32\include\user32.inc
include \masm32\include\gdi32.inc
WinMain PROTO STDCALL :DWORD, :DWORD, :DWORD, :DWORD
WndProc PROTO STDCALL :DWORD, :DWORD, :DWORD, :DWORD
.data?
hInstance dd ?
.data
ClassName db "FirstWindowClass",0
AppName db "FirstWindow",0
.code
start:
invoke GetModuleHandle, NULL
mov hInstance, eax
invoke WinMain, hInstance, NULL, NULL, SW_SHOWNORMAL
invoke ExitProcess, NULL
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL hwnd:DWORD
LOCAL msg:MSG
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 hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW
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
INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW-WS_SIZEBOX-WS_MAXIMIZEBOX,CW_USEDEFAULT,\
CW_USEDEFAULT,400,300,NULL,NULL,\
hInst,NULL
mov hwnd,eax
invoke ShowWindow, hwnd,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
mov eax, uMsg
.IF eax==WM_CREATE
invoke MessageBox, NULL, ADDR AppName, ADDR AppName, NULL
.ELSEIF eax==WM_DESTROY
invoke PostQuitMessage, NULL
.ELSE
invoke DefWindowProc, hWnd, uMsg, wParam, lParam
.ENDIF
ret
WndProc endp
end start
本文地址:http://com.8s8s.com/it/it29711.htm