2005.7.10 |
Windows 汇编语言编程教程 |
Version 1.02
Copyright@2005,Jeff Huang.All rights reserved
Translator:fqh 2005.7.10
|
JEFF HUANG 作,fqh译 |
目录
介 绍 ……………………………………………………………………………2
为什么选用汇编语言 …………………………………………………………2
为什么选择Windows系统……………………………………………………2
Ⅰ开始学习之旅 ……………………………………………………………….…3
编译器 ………………………………………………………………………3
编辑器 …………………………………………………………………………3
Ⅱ第一个程序 ……………………………………………………………………4
控制台程序 ……………………………………………………………………4
窗体程序 ………………………………………………………………………6
ADDR 与 OFFSET ………………………………………………………6
Ⅲ汇编基础…………………………………………………………………………7
cpu寄存器 ……………………………………………………………………7
指令集基础 ……………………………………………………………………8
Push 和 Pop…………………………………………………………………8
Invoke ……………………………………………………………………9
程序例子 ………………………………………………………………………9
IV. 窗体程序基础…………………………………………………………………10
预备知识………………………………………………………………………10
宏……………………………………………………………………………10
过程…………………………………………………………………………10
变量…………………………………………………………………………10
一个简单的窗体程序…………………………………………………………11
IV. 深入汇编和系统………………………………………………………………13
字符串操作……………………………………………………………………13
文件管理………………………………………………………………………13
存储…………………………………………………………………………14
程序例子……………………………………………………………………14
控制……………………………………………………………………………15
附加资源 …………………………………………………………………………16
互联网………………………………………………………………………16
书籍…………………………………………………………………………16
MASM32……………………………………………………………………16
MSDN………………………………………………………………………16
新闻组………………………………………………………………………16
IRC …………………………………………………………………………16
介 绍 |
“This is for all you folks out there,who want to learn the magic art of Assembly programming”
-MAD
介 绍
我最近才开始学习windows系统汇编语言编程,这个教程是我在学习汇编语言的过程中写下来的。我阅读大量的在线教程、书本,以及通过新闻组以及IRC通讯工具请问他人,本人就是通过这些方式学习汇编语言的。互联网上有很多的汇编编程的教程,但这些教程只是侧重于X86汇编。因为这些教材都假定读者已经掌握了高级编程语言以及基本的计算机系统知识。
为什么选用汇编语言?
汇编语言具有若干的特色,使得在某此情况下,汇编语言是一种很好的选择。
1 快速 汇编语言程序运行的速度比高级语言程序要快。通常,要求运行效率高的子程序是用汇编语言编写的。
2 强大 运用汇编语言,你能得到不受限制的权力。相对的,高级语言则有种种限制,在实现某些特定的要求时变得困难。
3 体积小 汇编语言程序通常比其他语言程序要小得多。这种特性在空间有限的情况下是非常有用的。
为什么选择Windows系统?
在任何操作系统和处理器模式下,都可以编写相应的汇编语言程序的。但是当前,多数人在使用基于x86处理器的Windows系统,所以从编写运行于此种环境下的程序开始我们的教程。一旦一种汇编语言的基础知识掌握了,我们就会很容易写出在其他运行环境下汇编程序。
第一章 |
编写汇编程序,我们必须具备一些工具,它们是编译器以及编辑器。我们选择了一些能胜任这些工作的运行于Windows系统的工具如下。
编译器
编译器能把写下的汇编程序代码转换成机器码。通常,它附带有一个连接器。连接器用来连接可编译文件并从中生成可执行文件。Windows系统的可执行文件是以.exe为后缀的。下面给出一些流行的编译器:
1 MASM 这个编译器是本教程所选用的,在学习本教程的过程中,你可以使用它。它原先由微软公司开发,现在被包括在MASM32v8程序包内了。MASM32v8程序包还包括了其他的工具。你可以从这个网址得到它:http://www.masm32.com/.
注意:教程中有一些指令和宏指令,只有在MASM编译器才是有效的,所以强烈建议您从开始学习时选用MASM。 |
2. TASM 这是另一个受欢迎的编译器。由Borland公司开发,现在依然是个商业软件,所以你不能免费地获取到它。
3. NASM 一个免费开放源码的编译器,它也能在其他系统平台上使用。它可以从这个网址获取到http://sourceforge.net/projects/nasm/ 记住
编辑器
编辑器是在编译前编写程序代码的软件。编辑器可以个人自由选择。现在在很多种编辑器,你可以试用一下它们并选择一种你喜欢的。
1 Notepad 记事本,Windows系统自带的。虽然它缺少很多功能,但它使用简便。
2 Visual Studio 它不是免费的编辑器,但它出色的语法高亮显示功能能让你的代码更易于阅读。
3. 其他 – 还有很多其他的编辑器,在些不一一列出它们的名字。其中一些很受欢迎:
a. Ultraedit (我个人最喜欢的e) http://www.ultraedit.com/
b. Textpad http://www.textpad.com/
c. VIM http://www.vim.org/
d. Emacs http://www.gnu.org/software/emacs/emacs.html
e. jEdit http://www.jedit.org/
第二章 |
Ⅱ第一个程序
现在我们有了自己的工具,打开你的文本编辑器,跟着下面的介绍,开始学习编程吧。这是世上最普通的程序,“Hello World”程序。
控制台程序
控制台程序是运行在系统控制台的(也就大家所知的命令行)。为创建这个程序,首先粘贴下面的代码到你的文本编辑器上,并保存为文件“hello.asm”。
.386
.model flat, stdcall
option casemap :none
include /masm32/include/windows.inc
include /masm32/include/kernel32.inc
include /masm32/include/masm32.inc
includelib /masm32/lib/kernel32.lib
includelib /masm32/lib/masm32.lib
.data
HelloWorld db "Hello World!", 0
.code
start:
invoke StdOut, addr HelloWorld
invoke ExitProcess, 0
end start
现在,通过开始菜单,点“运行…”选项,输入“cmd”(没有引号)并回车,就能进入到命令行。接着在cmd下转到保存有”hello.asm”的目录,输入"/masm32/bin/ml /c /Zd /coff hello.asm"。期望编译器不会提示错误,你的程序能被正确编译!然后,我们还得连接它,所以接着输入"/masm32/bin/Link /SUBSYSTEM:CONSOLE hello.obj"。祝贺你!你已经成功编译了第一个汇编语言程序。在文件夹里出现了一个中Hello.exe的文件。在命令行下打"hello"来运行你的程序。它会输出"Hello World!"。可见,为了显示"Hello World!",我们只要编写很少的代码就可以了。
这些代码都起了什么作用呢?让我们一行一行地看下去。
.386
这条指令的作用是告知编译器使用.386指令集。当前,几乎没有处理器使用比.386更老的指明令集了。我们还可以选择使用.486.或586,但是.386是兼容性最好的指令集。
.model flat, stdcall
.MODEL 是一条指定你程序的内存模式的汇编指令。Flat是一种方便的系统程序模式,因为在这种模式下不再区分远指针(far)和近指针(far)。Stdcall 是一种系统函数传递参数的方法,它意味着你得以从右到左的顺序传递你的参数。
option casemap :none
强制你的程序代码大小写敏感,这意味着Hello和hello被看做不同的。很多高级编程语言同样是大小写敏感的,所以这是个编程的良好习惯。
include /masm32/include/windows.inc
include / masm32/include/kernel32.inc
include /masm32/include/masm32.inc
这是系统程序必需的包含文件。windows.inc通常必须包含的,因为它包含了Win32 API常量和定义的声明。kernel32.inc包含了我们所使用的ExitProcess函数。masm32.inc包含有StdOut函数。StdOut函数不是Win32函数,它是MASM32v8新增进去的。
includelib /masm32/lib/kernel32.lib
includelib /masm32/lib/masm32.lib
函数依赖库,基于这个目的,这些库得包含进去。
.data
程序中所有初始化的数据必须放在这条指令下面。此外,还有别的指令比如.data?和.const。它们分别位于未初始化数据和常量的前面,但是,在我们的”Hello World”程序中并没有用到它们。
HelloWorld db "Hello World!", 0
db代表“字节”,并声明HelloWorld为一个字符串。"Hello World!"后面跟着一个”NULL”字母,这是因为ANSI字符串必须以NULL结尾。
.code
这代表程序代码段的开始。
start:
你程序的代码位于这个标号的后面,但位于” end start”前面。
invoke StdOut, addr HelloWorld
Invoke调用一个函数及其参数,addr HelloWorld位于它后面。这一行所做的是传递"Hello World!"的地址和调用StdOut。注意StdOut函数只是在MASM32中有效的,它是一个调用其它函数来输出文件的宏。在别的编译器里,你得使用更多的代码并要用到win32函数WriteConsole.。
invoke ExitProcess, 0
显而易见,它传递参数0到ExitProcess函数,从而退出进程。
窗体程序
我们也可以编写一个有窗体版本的“Hello World”程序。粘贴下面文本到你的文件编辑器里并保存为文件"hellow.asm".
.386
.model flat, stdcall
option casemap :none
include /masm32/include/windows.inc
include /masm32/include/kernel32.inc
include /masm32/include/user32.inc
includelib /masm32/lib/kernel32.lib
includelib /masm32/lib/user32.lib
.data
HelloWorld db "Hello World!", 0
.code
start:
invoke MessageBox, NULL, addr HelloWorld, addr HelloWorld, MB_OK
invoke ExitProcess, 0
end start
现在,再打开命令行并转到"hellow.asm"所在目录。输入"/masm32/bin/ml /c /Zd /coff hellow.asm"回车,接着输入"/masm32/bin/Link /SUBSYSTEM:WINDOWS hellow.obj"并回车。注意,subsystem是WINDOWS不再是CONSOLE。这个程序弹出一个显示"Hello World!"的信息框。
与控制台版相比,窗体版本的代码只有3行是不同的。其中有两行把masm32包含文件和库文件更换为user32包含文件和库文件,这是因为我们现在是使用MessageBox函数,而不是使用StdOut了。第3个不同的行是用MessageBox函数代替了StdOut函数。不同之处就这么多而已!
ADDR 与 OFFSET
在我们“Hello World!”程序例子中,我们使用了'addr' 来获取字符串"Hello World!"的地址。还有另外一个类似的指令'offset',虽然两者的目的都是在程序执行中获取变是变量的地址。它们主要的区别是'offset' 只能获取全局变量的地址,然而addr能获取全局变以及局部变量的地址。然而我们不讨论局部变量,所以不用担心这种区别。但是我们还是要记住这种区别的。
Ⅲ
第三章 |
cpu寄存器
现在我们已经能够编写并运行一个简单的程序了。让我们转到本教程的核心内容-汇编基本语法吧。你要写出自己的汇编程序,这些基本的知识是要掌握的。 32位通用寄存器有8个。它们其中前面四个也就是eax,ebx,ecx,edx,也能用它们16位或8位的名字形式进行存取。比如,ax存取eax的低16位,al存取低8位,还有ah存取的是9到16位。其余的寄存器也能以类似的方式进行存取。就如大家想象的那样,这些通用寄存器虽然大多有专用的用途,但它们有通用的地方。
地址 |
名称 |
描述 |
EAX* |
累加寄存器 |
进行计算操作和保存数据结果 |
EBX |
基址寄存器 |
指向数据寄存器中的数据 |
ECX* |
计数寄存器 |
字符串以及循环的计数 |
EDX* |
数据寄存器 |
输入/输出的指针 |
ESI |
源变址寄存器 |
字符串操作中的源指针 |
EDI |
目的变址寄存器 |
字符串操作中的目的指针 |
ESP |
堆栈指针寄存器 |
堆栈指针,不能人为使用它 |
EBP |
堆栈基址寄存器 |
指向堆栈中的数据 |
注意:虽然它们被称为通用寄存器,但是只有那些标有*号的才能在窗体程序编程中使用。 |
下面是6个16位的段寄存器。它们定义在存储器的段。
地址 |
名称 |
描述 |
CS |
代码段寄存器 |
保存要运行的指令 |
DS,ES,FS,GS |
数据段寄存器 |
数据段 |
SS |
堆栈段寄存器 |
当前程序的堆栈 |
最后,还有2个32位的没有归类的寄存器
地址 |
名称 |
描述 |
EFLAGE |
标志寄存器 |
状态,控制,系统标志 |
EIP |
指令指针寄存器 |
下一个要执行的指针的偏移 |
指令集基础
x86指令集非常宏大,但是我们通常并没全都使用到了它们。下面介绍一些我们应该掌握的指令。
指令 |
描述 |
ADD* reg/memory, reg/memory/constant |
把两个操作数相加并把结果保存进第一个操作数。如果有进位的话,它会设置CF标志位 |
SUB* reg/memory, reg/memory/constant |
第一个操作数减去第二个操作数,并把结果保存到第一个操作数里 |
AND* reg/memory, reg/memory/constant |
两个操作数逻辑与,并把结果存到第一个操作数里 |
OR* reg/memory, reg/memory/constant |
两个操作数逻辑或,并把结果存到第一个操作数里 |
XOR* reg/memory, reg/memory/constant |
两者异或,并把结果存到第一个操作数里。注意你不能对 两个存储器操作数进行异或操作 |
MUL reg/memory |
操作数与累加器寄存器相乘,而后把结果存进累加器寄存 器 |
DIV reg/memory |
累加器寄存器被操作数除并把结果存到累加器 |
INC reg/memory |
操作数的值增1并把结果存进操作数 |
DEC reg/memory |
操作数的值减1并把结果存进操作数 |
NEG reg/memory |
操作数的值取补并把结果存进操作数 |
NOT reg/memory |
操作数的值取反并把结果存进操作数 |
PUSH reg/memory/constant |
把操作数压进堆栈顶端 |
POP reg/memory |
弹出堆栈顶端的值并保存到操作数 |
MOV* reg/memory, reg/memory/constant |
把第二个操作数的值保存到第一个操作数里面 |
CMP* reg/memory, reg/memory/constant |
第一个操作数减第二个操作数,并设置相应当的标志位。通常与JMP,REP等指令一起使用 |
JMP** label |
跳转到标号处 |
LEA reg, memory |
取第二个操作数的地址偏移,并把结果保存进第一个操作数 |
CALL subroutine |
调用另一个过程直到程序返回 |
RET |
程序返回到调用者 |
INT constant |
调用操作数指定的中断 |
*指令不能有两个存储器操作数
**这个指令可以结合条件来使用。比如,JNB(不小于),是只有在CF=0这一条件下才会跳转。
最新的全部指令集参考可以从下面这个网址得到
http://www.intel.com/design/pentium4/manuals/index.htm.
Push 和 Pop
Push和pop是操作堆栈的指令。Push获取一个数据并把它压进堆栈的顶端。Pop获取堆栈顶端的数据,弹出并保存它。因此,堆栈是使用一个先进后出的存取方式(LIFO)。堆栈是计算机中一个常见的数据结构,所以如果你在编程过程中对堆栈操作感到不顺手的话我建议你先掌握这一知识。
Invoke
Invoke是MASM特有的一个伪指令。它使得在调用函数前不必先传递参数。这让我们省略了很多的代码。
举个例子说明如下
invoke SendMessage, [hWnd], WM_CLOSE, 0, 0
等效于:
push 0
push 0
push WM_CLOSE
push [hWnd]
call [SendMessage]
程序例子
下面是一个完整的程序。它说明了如何去使用指令和寄存器。看看是否全部弄懂了它。
.386
.model flat, stdcall
option casemap :none
include /masm32/include/windows.inc
include /masm32/include/kernel32.inc
include /masm32/include/masm32.inc
includelib /masm32/lib/kernel32.lib
includelib /masm32/lib/masm32.lib
.data
ProgramText db "Hello World!", 0
BadText db "Error: Sum is incorrect value", 0
GoodText db "Excellent! Sum is 6", 0
Sum sdword 0
.code
start:
; eax
mov ecx, 6 ; set the counter to 6 ?
xor eax, eax ; set eax to 0 0
_label: add eax, ecx ; add the numbers ?
dec ecx ; from 0 to 6 ?
jnz _label ; 21
mov edx, 7 ; 21
mul edx ; multiply by 7 147
push eax ; pushes eax into the stack
pop Sum ; pops eax and places it in Sum
cmp Sum, 147 ; compares Sum to 147
jz _good ; if they are equal, go to _good
_bad: invoke StdOut, addr BadText
jmp _quit
_good: invoke StdOut, addr GoodText
_quit: invoke ExitProcess, 0
end start
注意:“;”符号表示注释。所有跟在它后面的字符都不会被编译。把提示和注意点放在注释中是个好主意,它能让你的代码易读。 |
第四章 |
IV. 窗体程序基础
窗体程序通常由一个或几个窗体组成。因此,做为windows程序员至少要懂得怎么创建一个简单的窗体。很不幸,它不是那么容易的事,但是本教程会指导你怎么去做。
预备知识
在编写窗体程序前我们还要讨论几个主题。让我们花点时间复习一下预备知识。
宏
MASM有几个让汇编编程变得非常容易的宏。我们已经接触到’invoke’,它简单地调用一个函数。下面列出其他几种,之前在你用高级语言编程时它们的用法是很明显的。
o .if, .else, .endif
o .while, .break, .endw
过程
与高级语言类似,MASM让你定义各种过程使得你的代码易于阅读。它们的格式如下所示:
ret
返回值保存在eax寄存器里,这个过程用下面格式来调用
invoke
返回值可以用下面指令来获取
mov RetVal, eax
变量
变量被分配在内存中的,并用来存储你的数据。在没有足够的寄存器可用的情况下,变量是非常有用的。变量有两种类型—全局变量和局部变量。如果全局变量已被初始化,它们被置于.data块;如果它们没被初始化的话,就被置于.data?块。还有,如果全局变量被初始化并且不会被改变的话,它们就被置于.const块。声明全局变量的格式如下:
局部变量是被放于过程内部的,暂时保存,供过程内部使用。它们在创建时不能被初始化。格式如下:
local
有几种变量类型以后将会遇到。其中有几种常见,比如’byte’,’word’ (4 bytes),’dword’(8 bytes)。还有更多,但是它们常常与这三种类型中的一种是相同的,只不过名称不同。
一个简单的窗体程序
窗体程序有两个主要的部分。第一部分是WinMain,它创建窗体还包含叫实现消息循环的代码。“消息循环”监视消息并分派消息。第二部分是过程返回WndProc,它是接收消息的,这部分处理你的鼠标事件及刷新窗口等。
.386
.model flat, stdcall
option casemap :none
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
以上是我们通常必需的
WinMain proto :DWORD, :DWORD, :DWORD, :DWORD
This is a function prototype. It let's us call the WinMain function later in the program.
It can be compared to a C/C++ function declaration.
这是函数原型。在稍后的程序里我们称它为WinMain函数。
.data
ClassName db "WinClass", 0
AppName db "Simple Window", 0
我们声明我们的字符变量
.data?
hInstance HINSTANCE ?
变量hInstance保存模块实例的句柄,以便与窗体相关联。稍后我们将把它传递到CreateWindow函数中。
.code
start:
invoke GetModuleHandle, NULL
mov hInstance, eax
invoke WinMain, hInstance, NULL, NULL, 0
invoke ExitProcess, eax
获取模块句柄并把它保存到变量hInstance中。接着调用WinMain函,而已退出。WinMain是这个程序的核心,所以我们将深入研究它。
注意:从这点来看,我们假设你能够从MSDN中查找windows函数。它有函数参数,返回值,还有其它你必须了解的信息。你可以在附加资源这个章节里获取关于MSDN的信息。 |
WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR,
CmdShow:DWORD
local wc:WNDCLASSEX
local msg:MSG
local hwnd:HWND
这是WinMain函数的开头部分。我们声明了三个局部变量:wc,msg,,还有hwnd。Wc保存我们创建的窗口类,窗体类是一个创建窗体的模板。Msg保存消息循环中返的消息。Hwnd保存我们窗本的句柄。
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
注意:在窗体程序中,’or’操作运算符常常用来联合参数中的标志。 |
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
这些是填充我们先前声明的wc结构。然后以wc为参数调用RegisterClassEx。至于更多关于wc的每个成员的信息,请在MSDN中查找WNDCLASSEX结构的资料。
invoke CreateWindowEx, 0, addr ClassName, addr AppName, WS_OVERLAPPEDWINDOW
or WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL,
NULL, hInst, NULL
mov hwnd, eax
调用CreateWindowEx函数创建窗体。其中有很多参数被传递进去来表明怎么创建窗体。窗体的句柄会返回并保存到变量hwnd中。
.while TRUE
invoke GetMessage, addr msg, NULL, 0, 0
.break .if (!eax)
invoke TranslateMessage, addr msg
invoke DispatchMessage, addr msg
.endw
这个while循环也就是先前提到的消息循环。当一个输入事件发生,系统会传递这个事件到一个消息里,并把消息放进程序的消息队列中去。GetMessage取回这些消息并保存进变量msg里。TranslateMessage把键盘消息转换成字符消息。最后,DispatchMessage把这些消息发送到WndProc函数里。在WndProc函数中,这些消息将会被处理。
mov eax, msg.wParam
ret
WinMain endp
返回值保存进msg.wParam,WinMain函数结束。
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.if uMsg == WM_DESTROY
invoke PostQuitMessage, 0
.else
invoke DefWindowProc, hWnd, uMsg, wParam, lParam
ret
.endif
xor eax, eax
ret
WndProc endp
WndProc函数是处理消息的地方。唯一一个一定要处理的消息是WM_DESTROY,它通过调用PostQuitMessage来退出程序。如果有其它你要处理的事件,你可以在这里把它们加进来。普通要处理的消息是WM_CREATE(当创建窗体时),WM_PAINT (当窗体必须重画时), 还有WM_CLOSE (关闭窗体时)。其它没有处理的消息被传递给DefWindowProc函数对消息进行默认处理
end start
就这些了。你已经了解了怎么去创建一个窗体!
第五章 |
下面有一些资料来扩展你关于汇编以及系统编程方面的知识:字符串操作,文件处理,还有系统窗体的控制。
字符串操作
字符串,数组都是程序中的基本部分。如果你想显示文本或者请求使用者输入,它们就用得了。它们使用到了如下的寄存器:esi,edi,ecx,eflag中的方向控制标志。方向控制标志是指定移动字符串时的方向。一些常见的字符串操作指令是movsb, cmpsb, stasb, and stosb.为了操作字符串,你可以在字符串控制指令中使用某些rep?的形式。下面是在串操作指令中可以使用到rep?前缀
前缀 |
串操作指令 |
描述 |
rep |
movsb |
复制字符串 |
repe |
cmpsb |
比较字符串 |
repne |
scasb |
扫描字符串中一个字符 |
rep |
stosb |
保存一个字符到字符串中 |
下面给个复制字符串的例子
cld ; sets the direction flag to forward
mov esi, source ; move the source address in to esi
mov edi, dest ; move the destination address in to edi
mov ecx, length ; move the length to copy in to ecx
rep movsb ; copy length bytes from esi to edi
文件管理
在旧的DOS系统世界时,文件是通过使用中断进行操作的。在windows系统里,我们通过使用系统函数访问文件。其中可供我们使用的4种函数是:
CreateFile –创建或打开一个文件,并返回它的句柄
ReadFile – 从文件中读取数据
WriteFile – 写数据到文件里
CloseHandle – 关闭你用CreateFile函数得到的句柄
存储
为了读取文件内容,我们必须分配一些内存来存储数据。内存可以如你所愿地被分配,锁定,但是最后记得解锁和释放。做这些工作的函数是GlobalAlloc, GlobalLock, GlobalUnlock, 还有GlobalFree。相当容易,呵呵!
程序例子
这个程序读取"c:/test.txt"的内容,并通过一个消息框输出。
.386
.model flat, stdcall
option casemap :none
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
一些通常的包含文件
.data
FileName db "c:/test.txt", 0
.data?
hFile HANDLE ?
hMemory HANDLE ?
pMemory DWORD ?
ReadSize DWORD ?
我们定义字符串,还声明了四个将会用到的变量
.const
MEMORYSIZE equ 65535
这是说明分配多大的内存,这样有足够的空间保存我们的文件
.code
start:
invoke CreateFile, addr FileName, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
mov hFile, eax
调用CreateFile函数并保存文件句柄到hFile变量。通常,放置一个'h'在句柄之前,而放置'p'在指针之前
invoke GlobalAlloc, GMEM_MOVEABLE or GMEM_ZEROINIT, MEMORYSIZE
mov hMemory, eax
invoke GlobalLock, hMemory
mov pMemory, eax
分配并锁定我们的内存
invoke ReadFile, hFile, pMemory, MEMORYSIZE-1, addr ReadSize, NULL
invoke MessageBox, NULL, pMemory, addr FileName, MB_OK
这些行是读文件到内存中并输出其内容
invoke GlobalUnlock, pMemory
invoke GlobalFree, hMemory
invoke CloseHandle, hFile
invoke ExitProcess, NULL
end start
可别忘清除工作啊
控制
一旦创建了一个窗体程序,我们就会想在上面放置一些按钮还有文件框在上面。幸运地,这是容易的事!它的语法非常类似于创建一个窗体,除此外,我们不必调用RegisterClassEx,因为我们的类将为我们预先定义了。为了做到这些,从第4章内容那里编辑WndProc函数来响应WM_CREATE消息
.elseif uMsg == WM_CREATE
invoke CreateWindowEx, NULL, addr ButtonClassName, addr ButtonText, WS_CHILD
or WS_VISIBLE or BS_DEFPUSHBUTTON, 10, 50, 80, 30, hWnd, ButtonID, hInstance, NULL
mov hButton, eax
invoke CreateWindowEx, WS_EX_CLIENTEDGE, addr EditClassName, NULL, WS_CHILD
or WS_VISIBLE, 10, 10, 100, 20, hWnd, EditID, hInstance, NULL
mov hEdit, eax
在.data块,你还要增加几个变量。定义EditClassName为"edit",ButtonClassName 为"button"。还有,你要定义EditID 和ButtonID为常量。它们的值是什么并没关系,只要它们在其它控制里不出现相同的ID。此外,你不能对变量hEdit和hButton进行初始化,因为它们都是句柄类型。最后,ButtonText必须赋值为字符串,它将显示在按钮上。现在我们还想知道按钮是什么时候被按下的。这可以通过监视WM_COMMAND消息获知,一个如果按下按钮将会发送的消息。
.elseif uMsg == WM_COMMAND
mov eax, wParam
.if ax == ButtonID
shr eax, 16
wParam参数包含了有关消息的信息。我们可以检查它是否是那个按钮发送的消息,因为既然我们不想处理其他控制的消息。Shr是右移位操作指令,它把wParam右移16位。这是一种存取32位寄存器的高16位好方法,这样我们通过访问ax就可以很容易做到了。
.if ax == BN_CLICKED
.endif
.endif
现在我们知道按钮已经被按下,我们可以对其做一些事了。如果有兴趣学习更多的窗体知识,看一下附加资源这章节。它列出了一些不错有关系统常规编程的参考书和网站
附加资源
互联网
http://www.xs4all.nl/~smit/ -有有用的x86汇编编程教程
http://win32asm.cjb.net/ -有优秀的关于windows汇编编程的教程
http://board.win32asmcommunity.net/ -.一个很活跃的讨论关于windows汇编编程的在线论坛
书籍
Programming Windows, 第四版,Charles Petzold著作,是一本出色的系统编程书。它给出了很多系统编程的代码例子,覆盖了关于系统编程的很大范围的主题。
Intel Pentium 4 Processors Manuals, 可从http://www.intel.com/design/pentium4/manuals/ 获取到。它是x86汇编编程的完整的参考。
The Art of Assembly Programming, Randall Hyde所著, 可从
http://webster.cs.ucr.edu/AoA.html获取到。它是我见过最好的和最全面的的x86汇编编程书中
MASM32
在你的/masm32/HELP/目录里。它是一个叫masm32.hlp的文件,包含了MASM32指南。它有所有关于宏,标志,代码优化等资料。这是学习汇编特别是MASM32非常好的参考。
MSDN
MSDN通常是Visual Studio自带有的,你也可以从网站http://msdn.microsoft.com/在线游览。它包括了关于Windows的系统函数,常量,及所有能够被想象到资料。
新闻组
目前有两个涉及x86汇编的新闻组。它们是comp.lang.asm.x86 和 alt.lang.asm。两者都有相当高的访问量,拥有很多知识渊博的读者
IRC
有一个关于windows汇编编程的IRC(internet relay chat)频道,#win32asm on EFNet [http://www.efnet.org/]