代码跟上个例子相比, 长了很多,这个例子中用了大量的 windows api函数, 下面我们来慢慢的解释:
这里面多了一个 gdi32.inc和 gdi32.lib, 这里要用 windows的画图函数,所以要用到这个库和头文件
start:
...
end start
这个 start:表示一个标签, 后面有一个伪指令 end start, 这告诉编译器, 程序从 start这个标签的位置开始运行, 同时到 end start 这个位置结束,也就是说:
end 标签
这个伪指令表明了程序的开始和结束位置
call _WinMain
表示调用自定义过程 _WinMain,相当于 windows编程中的 WinMain函数, 这里换成伪指令 invoke也是一样, 即:
call _WinMain 和 invoke _WinMain等价, 实际上经过我的测试
call _WinMain
invoke _WinMain
invoke offset _WinMain
invoke addr _WinMain
call offset _WinMain
都是正确的, 注意, addr 伪指令只能和 invoke配合使用,不能这样写: call addr _WinMain
_WinMain proc
....
_WinMain endp
定义一个过程,不带参数, 过程的名字可以任意, 这里的过程类似 c语言的函数
程序从这里开始运行, 我们来主意看看每行代码的含义:
local @stWC:WNDCLASSEX
local @stMsg:MSG
定义 2个局部变量, local表示局部变量的意思, 这个是局部变量定义伪指令
局部变量的名称分别为 @stWC和 @stMsg,类型分别为结构 WNDCLASSEX和 MSG
在汇编中, 根据个人习惯的不同, 各种变量以及标识符的写法不一样,但是必须遵循以下原则:
1. 可以用字母、数字、下划线以及符号 $、 @和 ?
2. 第一个符号不能是数字
3. 长度不能超过 240个字符
4. 不能使用汇编关键字
5. 在作用域内必须唯一,这个跟高级语言的作用域是一样的
invoke GetModuleHandle, NULL
mov hInstance, eax
调用 GetModuleHandle函数, 获取模块句柄,如果参数是 NULL, 则获取当前模块句柄
获取句柄后, 将句柄值存入全局变量 hInstance, 注意:
汇编语言中,所有函数调用的返回值都将放在 eax中
invoke RtlZeroMemory, addr @stWC, sizeof @stWC
调用 RtlZeroMemory函数,将结构 @stWC填充, 填充大小为 sizeof @stWC
WNDCLASSEX结构在 msdn中定义如下:
typedef struct _WNDCLASSEX {
UINT cbSize;
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
HICON hIconSm;
} WNDCLASSEX, *PWNDCLASSEX;
cbSize 为本结构的大小, 必须填充为 sizeof WNDCLASSEX
style 为窗口风格, 典型设置为 CS_VREDRAW or CS_HREDRAW, or表示或, 取两者的组合
lpfnWndProc 为窗口过程, 窗口过程的原型必须符合下列形式【过程名任意】:
WndProc Proto :dword, :dword, :dword, :dword
cbClsExtra 窗口类附加数据, 通常为 0
cbWndExtra 窗口附加数据,通常为 0
hInstance 当前实例句柄, 可以通过 GetModuleHandle获取
hIcon 程序图标
hCursor 光标
hbrBackground 窗口背景色
lpszMenuName 菜单名称,这里没有菜单,为 NULL
lpszClassName 窗口类名称
hIconSm 小图标
mov @stWC.cbSize, sizeof WNDCLASSEX
mov @stWC.style, CS_HREDRAW or CS_VREDRAW
mov @stWC.lpfnWndProc, offset _WinProc
push hInstance
pop @stWC.hInstance
invoke LoadIcon, NULL, IDI_APPLICATION
mov @stWC.hIcon, eax
mov @stWC.hIconSm, eax
invoke LoadCursor, NULL, IDC_ARROW
mov @stWC.hCursor, eax
invoke GetStockObject, BLACK_BRUSH
mov @stWC.hbrBackground, eax
mov @stWC.lpszClassName, offset szAppName
这段代码填充 WNDCLASSEX结构,
push hInstance
pop @stWC.hInstance
相当于 mov @stWC.hInstance, hInstance, 但是这条指令是错误的,不能直接在 2个内存变量之间传值,需要借助一个寄存器间接传值, 可以写成如下形式 :
mov eax, hInstance
mov @stWC.hInstance, eax
invoke LoadIcon, NULL, IDI_APPLICATION 获取程序的默认图标
invoke LoadCursor, NULL, IDC_ARROW 获取默认光标
invoke GetStockObject, BLACK_BRUSH 获取一个黑色的画刷,用于将窗口背景画成黑色
invoke RegisterClassEx, addr @stWC
.if !eax
invoke MessageBox, NULL, offset szErrorText, offset szError, MB_OK or MB_ICONERROR
ret
.endif
注册窗口类, 如果注册失败, 则弹出一个提示对话框, 并且退出程序
invoke CreateWindowEx, /
WS_EX_CLIENTEDGE, /
offset szAppName, /
offset szAppName, /
WS_OVERLAPPEDWINDOW, /
CW_USEDEFAULT, /
CW_USEDEFAULT, /
CW_USEDEFAULT, /
CW_USEDEFAULT, /
NULL, /
NULL, /
hInstance, /
NULL
.if !eax
invoke MessageBox, NULL, offset szErrCreateWnd, offset szError, MB_OK or MB_ICONERROR
ret
.endif
mov hWinMain, eax
invoke ShowWindow, hWinMain, SW_SHOWNORMAL
invoke UpdateWindow, hWinMain
创建一个标准的 windows窗口, CreateWindowEx在 msdn中定义如下:
HWND CreateWindowEx(
DWORD dwExStyle, // extended window style
LPCTSTR lpClassName, // registered class name
LPCTSTR lpWindowName, // 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, // menu handle or child identifier
HINSTANCE hInstance, // handle to application instance
LPVOID lpParam // window-creation data
);
dwExStyle 窗口扩展风格, 这里用了 WS_EX_CLIENTEDGE, 表示下沉的边框
lpClassName 注册的窗口类名称,必须和 RegisterClassEx中用到的类名称一样
lpWindowName 窗口标题
dwStyle 窗口风格,通常为 WS_OVERLAPPEDWINDOW表示一个层叠的窗口
x, y, nWidth, nHeight 表示窗口的位置和大小,这里全部默认为 CW_USEDEFAULT
hWndParent 父窗口, 这里没有, 设置为 NULL
hMenu 窗口菜单, 这里没有, 设置为 NULL
hInstance 当前实例句柄
lpParam 附加数据, 通常不用, 设置为 NULL
如果窗口注册失败, 则弹出一个错误提示对话框,并且退出程序,否则把创建的窗口句柄保存到全局变量 hWinMain中
窗口创建成功后, 调用 ShowWindow显示窗口, 然后必须再调用 UpdateWindow发送一个 WM_PAINT消息,让程序重画窗口, 这样你才能看到窗口
.while TRUE
invoke GetMessage, addr @stMsg, NULL, 0, 0
.break .if !eax
invoke TranslateMessage, addr @stMsg
invoke DispatchMessage, addr @stMsg
.endw
这里是一个消息循环, 程序不断的用 GetMessage获取消息, 如果 GetMessage返回值为 0, 表示退出程序,于是
.break .if !eax 退出循环, 整个程序结束运行
TranslateMessage 表示翻译消息, 这里主要用来翻译键盘消息
DispatchMessage 表示分发消息, 根据消息,调用窗口过程进行相应的处理
接下来, 我们要看看窗口过程:
_WinProc proc hWnd, uMsg, wParam, lParam
.............
_WinProc endp
这个和主过程 _WinMain不同的是, 它多了 4个参数, 分别是窗口句柄 hWnd, 消息 uMsg, 消息参数 wParam和 lParam
如果你在程序中用到了 ebx, esi, edi 等寄存器, 还需要通过以下的形式提前保存寄存器的值:
_WinProc proc uses ebx esi edi hWnd, uMsg, wParam, lParam
.............
_WinProc endp
local @stPS:PAINTSTRUCT
local @stRC:RECT
local @hDC
同样,这里定义三个变量, 2个结构变量和一个 DWOD变量, 在 32位汇编中, 用 local伪指令定义局部变量时,如果不指明变量类型,默认为 DWORD
接下来, 处理 2个我们感兴趣的消息 WM_PAINT和 WM_CLOSE, 不处理的消息用 DefWindowProc来处理
WM_PAINT消息主要用于绘制窗口客户区, 这里,我们在窗口的正中间,显示一行字:
invoke BeginPaint, hWnd, addr @stPS
mov @hDC, eax
invoke SetTextColor, @hDC, 0ff00h
invoke SetBkColor, @hDC, 0
invoke GetClientRect, hWnd, addr @stRC
invoke DrawText, @hDC, offset szText, -1, addr @stRC, DT_SINGLELINE or DT_VCENTER or DT_CENTER
invoke EndPaint, hWnd, addr @stPS
在绘画之前必须调用 BeginPaint获取绘画的 DC(device context)句柄,绘画结束, 需要调用 EndPaint释放资源
SetTextColor和 SetBkColor设定文字颜色和文字背景色
GetClientRect 获取客户区的坐标, 存储在结构变量 @stRC中,供后面绘画使用
DrawText 在窗口客户区显示文字, 它在 msdn中定义如下:
int DrawText(
HDC hDC, // handle to DC
LPCTSTR lpString, // text to draw
int nCount, // text length
LPRECT lpRect, // formatting dimensions
UINT uFormat // text-drawing options
);
hDC DC句柄
lpString 要显示的文字
nCount 显示的长度, 如果指定为 -1, 表示该字符串是以 0结尾的,函数会自动计算它的长度
lpRect 显示的矩形区域
uFormat 显示选项
DT_SINGLELINE 表示显示一行
DT_CENTER 表示水平居中
DT_VCENTER 表示垂直居中
程序中采用了它们 3个的组合
当窗口关闭的时候, 将收到 WM_CLOSE消息, 对于 WM_CLOSE消息处理如下:
invoke DestroyWindow, hWinMain
invoke PostQuitMessage, 0
先调用 DestroyWindow销毁主窗口, 然后再调用 PostQuitMessage向主线程发送一个 WM_QUIT消息, 让主线程结束消息循环,退出程序
WM_CLOSE消息必须处理,如果你不处理, 那么窗口关闭后, 主线程并不结束消息循环, 这样程序虽然看不到窗口了, 但是在任务管理器里面还存在这个进程。
对于整个源程序的解释就到这里, 很多 windows api函数我并没有很详细的说明, 自己用 google或者是 msdn查看一下, 然后对照着程序, 仔细研究一下就 ok了。
程序反汇编代码解析, 请看下一篇文章 。
<script type="text/javascript"> </script> <script src="http://pagead2.googlesyndication.com/pagead/show_ads.js" type="text/javascript"></script>