(译)win32asm教程-12-完结

类别:编程语言 点击:0 评论:0 推荐:

 这个是本系列教程的最后一篇了。下面我可能会贴出关于用汇编写一个游戏的实例。仍然是面向基础,面向初学者的。在此,也要感谢原作者: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