Tutorial 2: MessageBox
第二课:消息框
________________________________________
In this tutorial, we will create a fully functional Windows program that displays a message box saying "Win32 assembly is great!".
在这一课,我们将要创建一个完整的windows窗口程序用来弹出一个消息框并显示“Win32 assembly is great!”
Download the example file here.
从这里下载这个例子文件
Theory:
原理:
Windows prepares a wealth of resources for Windows programs. Central to this is the Windows API (Application Programming Interface). Windows API is a huge collection of very useful functions that reside in Windows itself, ready for use by any Windows programs. These functions are stored in several dynamic-linked libraries (DLLs) such as kernel32.dll, user32.dll and gdi32.dll. Kernel32.dll contains API functions that deal with memory and process management. User32.dll controls the user interface aspects of your program. Gdi32.dll is responsible for graphics operations. Other than "the main three", there are other DLLs that your program can use, provided you have enough information about the desired API functions.
Windows 为窗口程序准备了大量的资源,Windows API (应用程序接口)是其中最重要的一种。Windows API是一个宠大的非常有用的函数集合,这些函数驻留在windows内部,并且时刻准备着被windows程序调用。这些函数被存储在几个动态链接库中(dlls )如kernel32.dll, user32.dll and gdi32.dll。 kernel32.dll包含的API函数用来处理内存和进程管理。User32。dll 控制并管理用户程序的界面外观。Gdi32。dll 为图形操作负责。除了这三个主要的,你也可以在程序中用其他的DLLS 假如你有足够的信息来描述这些API 函数。
Windows programs dynamically link to these DLLs, ie. the codes of API functions are not included in the Windows program executable file. In order for your program to know where to find the desired API functions at runtime, you have to embed that information into the executable file. The information is in import libraries. You must link your programs with the correct import libraries or they will not be able to locate API functions.
Windows 程序在执行的动态链接这些dlls 文件,动态链接库里的API函数代码并不真正的包含在windows 程序的可执行文件中。 为了让你的程序在运行时知道在那儿能找到它想要的API函数。这些信息被输入到程序库文件中。你必须将你的程序和输入库文件准确的连接,否则它们将不能当作局部API函数使用。
When a Windows program is loaded into memory, Windows reads the information stored in the program. That information includes the names of functions the program uses and the DLLs those functions reside in. When Windows finds such info in the program, it'll load the DLLs and perform function address fixups in the program so the calls will transfer control to the right function.
当一个WINDOWS程序被装进内存的时候,windows操作系统读这存储在这个程序中的信息。这些信息包括程序使用的函数名和这些函数驻留在那些dll 中,当windows操作系统在程序中找到了这些信息,windows将 装入这个dll 并且修正函数的执行地址,这样在调用时才能正确的将控制权转移到函数内部。
There are two categories of API functions: One for ANSI and the other for Unicode. The names of API functions for ANSI are postfixed with "A", eg. MessageBoxA. Those for Unicode are postfixed with "W" (for Wide Char, I think). Windows 95 natively supports ANSI and Windows NT Unicode.
We are usually familiar with ANSI strings, which are arrays of characters terminated by NULL. ANSI character is 1 byte in size. While ANSI code is sufficient for European languages, it cannot handle several oriental languages which have several thousands of unique characters. That's why UNICODE comes in. A UNICODE character is 2 bytes in size, making it possible to have 65536 unique characters in the strings.
But most of the time, you will use an include file which can determine and select the appropriate API functions for your platform. Just refer to API function names without the postfix.
这里有两种类别的API函数:一种是ANSI (美国国家标准协会)另一种是统一字符编码标准。ANSI标准的API函数名字后缀是A 如:MessageBoxA 。而Unicode的后缀是 W (因为是宽字符,我认为),windows95 天然的支持ANSI和Windows NT支持 Unicode.我们通常熟悉的是ANSI字串串是以NULL为结束符的字符数组。ANSI字符占一个字节。虽然ANSI编码对于欧洲语言来说已经足够。但对于有几千个唯一字符的东方语言体系而言,就只能用UNICODE了。一个unicode占两个字节。这样就可以在一个字串中表示65546个unicode字符了。
但是大多数时候,你将用以个include文件就能为你的平台确定并选择适当的API函数。不过访问的API函数已经没有后缀。
{
实际上是在定义 .h 头文件时,我们用了预处理命令来告诉编译器应该选择那种类别的API函数 如:
#ifdef UNICODE
#define foo() fooW()
#else
#define foo() fooA()
#endif
}
Example:
I'll present the bare program skeleton below. We will flesh it out later.
我将在下面介绍一个空的程序框架,稍后,我们再充实它。
.386
.model flat, stdcall
.data
.code
start:
end start
The execution starts from the first instruction immediately below the label specified after end directive. In the above skeleton, the execution will start at the first instruction immediately below start label. The execution will proceed instruction by instruction until some flow-control instructions such as jmp, jne, je, ret etc is found. Those instructions redirect the flow of execution to some other instructions. When the program needs to exit to Windows, it should call an API function, ExitProcess.
可执行文件从END后面那个标号指定的第一条指令处开始执行。在上面的框架中,可执行文件将立即起始于START标号后的第一条指令,然后顺序地执行后续指令直到如 JMP, JNE,JE,这样一些控制跳转指令被发现。这些指令将使程序将执行控制权转移给其它指令。 (即,跳到跳转指令后面的指令处执行)当一个程序需要退出WINDOWS时 ,它应该调用Exitprocess 这个API函数
ExitProcess proto uExitCode:DWORD
The above line is called a function prototype. A function prototype defines the attributes of a function to the assembler/linker so it can do type-checking for you. The format of a function prototype is like this:
上面这行是被调用的函数原型。一个函数的原型定义了这个函数的属性,用来告诉汇编程序或是连接器该函数的属性。这样汇编程序和编译器将为你做相关的类型检查。函数原型的格式是这样地:
FunctionName PROTO [ParameterName]:DataType,[ParameterName]:DataType,...
函数名字 PROTO [参数名]:数据类型,[参数名] :数据类型
In short, the name of the function followed by the keyword PROTO and then by the list of data types of the parameters,separated by commas.
简而言之,就是在函数名后面跟随关键字PROTO,再在PROTO后面跟随一个函数参数的数据类型链表,各个参数间用逗号隔开。,
In the ExitProcess example above, it defines ExitProcess as a function which takes only one parameter of type DWORD. Functions prototypes are very useful when you use the high-level call syntax, invoke. You can think of invoke as a simple call with type-checking. For example, if you do:
在上面ExitProcess的例子中,它定义一个ExitProcess的函数,这个函数仅接收一个DWORD类型的参数。当你使用高级调用语法INVOKE 的时候,函数原型是很有用的。你可以将INVOKE看成一个简单的类型检查的调用。例如,假设你这样写:
call ExitProcess
without pushing a dword onto the stack, the assembler/linker will not be able to catch that error for you. You'll notice it later when your program crashes. But if you use:
这种调用方式,没有将一个双字压进堆栈,你的编译器将不能为你捕获到错误。毫无疑问,你稍后就可以注意到你的程序崩溃。但是如果你这样用:
invoke ExitProcess
The linker will inform you that you forgot to push a dword on the stack thus avoiding error. I recommend you use invoke instead of simple call. The syntax of invoke is as follows:
连接器将通知你,你忘记推一个DWORD的数据进栈 ,这样就避免了错误。我推荐你使用INVOKE来替代简单调用,invoke的语法如下:
INVOKE expression [,arguments]
expression can be the name of a function or it can be a function pointer. The function parameters are separated by commas.
表达式可以是一个函数的名字也可以是一函数的指针。函数的参数被逗号隔开。
Most of function prototypes for API functions are kept in include files. If you use hutch's MASM32, they will be in MASM32/include folder. The include files have .inc extension and the function prototypes for functions in a DLL is stored in .inc file with the same name as the DLL. For example, ExitProcess is exported by kernel32.lib so the function prototype for ExitProcess is stored in kernel32.inc.
对于大多数API函数原型声明被保存在INCLUDE所包含的头文件中,如果您用的是 hutch 的 MASM32,这些头文件在文件夹MASM32/include 下,这些头文件以inc作为后缀。DLL文件中的函数原型被存储在inc.file中并且与动态连接库有着一样的名字。例如:exitprocess 被导出在kernel32。Lib(库文件)所以exitprocess这个函数原型被存储在kerner32。Inc 中。
You can also create function prototypes for your own functions.
Throughout my examples, I'll use hutch's windows.inc which you can download from http://win32asm.cjb.net
你也可以为你自己的函数创建函数原型声明。
在我所有的例子中。 我将用HUTCH’S的windows。Inc 你可以从 http://win32asm.cjb.net 上下载它。
Now back to ExitProcess, uExitCode parameter is the value you want the program to return to Windows after the program terminates. You can call ExitProcess like this:
现在回到 exitprocess ,uExitcode这个参数的值是当你的程序结束时返回给windows的。你可以这样来调用这个函数:
invoke ExitProcess, 0
Put that line immediately below start label, you will get a win32 program which immediately exits to Windows, but it's a valid program nonetheless.
把这一行直接放在开始标号下面 ,你将使得这个win32 程序立即退出windows。但是它仍然是一个有效的WIN32 程序。
.386
.model flat, stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.data
.code
start:
invoke ExitProcess,0
end start
option casemap:none tells MASM to make labels case-sensitive so ExitProcess and exitprocess are different. Note a new directive, include.
Option casemap:none 告诉MASM要区分大小写,所以ExitProcess 和exitprocess是不同的。请注意新的伪指令 include
This directive is followed by the name of a file you want to insert at the place the directive is. In the above example, when MASM processes the line include \masm32\include\windows.inc, it will open windows.inc which is in \MASM32\include folder and process the content of windows.inc as if you paste the content of windows.inc there. hutch's windows.inc contains definitions of constants and structures you need in win32 programming. It doesn't contain any function prototype. windows.inc is by no means comprehensive. hutch and I try to put as many constants and structures into it as possible but there are still many left to be included. It'll be constantly updated. Check out hutch's and my homepage for updates.
这条伪指令后面跟随一文件名,它的作用是告诉编译器将你想要的文件插入在这条指令所在的地方。在上面的例子中,当MASM处理 include \masm32\include\windows.inc这一行时,它将打开在\masm32\inlude 文件夹的windows.inc 文件并且处理windows.inc的内容,就好像你将windows.inc这个文件的内容粘贴在这里一样。 Hutch的windows.inc文件定义许多你需要在WIN32 程序中使用的的数据结构和常数。它并不包含任何函数原型。尽管hutch和我试图把许多的常数和数据结构尽可能多的包含进去,但仍然与许多没有被包含进去。它需要经常的更新,请点击hutch和我的主页进行更新。
From windows.inc, your program got constant and structure definitions. Now for function prototypes, you need to include other include files. They are all stored in \masm32\include folder.
从WINDOWS.inc 你的程序得到了常量和数据结构的定义。现在为了得到函数原型,你需要包含其它的一些文件。它们被存储在 \masm32\include folder
In our example above, we call a function exported by kernel32.dll, so we need to include the function prototypes from kernel32.dll. That file is kernel32.inc. If you open it with a text editor, you will see that it's full of function prototypes for kernel32.dll. If you don't include kernel32.inc, you can still call ExitProcess but only with simple call syntax. You won't be able to invoke the function. The point here is that: in order to invoke a function, you have to put its function prototype somewhere in the source code. In the above example, if you don't include kernel32.inc, you can define the function prototype for ExitProcess anywhere in the source code above the invoke command and it will work. The include files are there to save you the work of typing out the prototypes yourself so use them whenever you can.
在我们上面的例子中:我们调用一个被KERNEL32.DLL导出的函数。所以我们需要从kerner32.dll中把函数原型包含进来。这个文件就是kernel32。Inc 如果你用文本编辑软件打开它,你将看到它全部都是kernel32.dll的函数原型声明。
如果你不用include包含kernel32.inc 文件,你将不能调用Exitprocess但是你可以使用简单的调用语法来调用 call ExitProcessni 你将不能用INVOKE这个语法句型来调用函数。这里指出 :为了使用invoke调用函数,你必须知道函数原型的声明放在源文件的某处。 在上面的例子中,如果你不把KERNEL32.INC这个文件包含进来,你只能为Exitprocess函数在源代码中定义函数原型并且必须在INVOKE 调用命令前。这些包含文件节在那里 节省了你自己进行函数原型声明的工作,所以你可以在任何时候使用它们 。
Now we encounter a new directive, includelib. includelib doesn't work like include. It 's only a way to tell the assembler what import library your program uses. When the assembler sees an includelib directive, it puts a linker command into the object file so that the linker knows what import libraries your program needs to link with. You're not forced to use includelib though. You can specify the names of the import libraries in the command line of the linker but believe me, it's tedious and the command line can hold only 128 characters.
现在我们遭遇一个新的指令,includelib includelib不想include一样来工作。它仅仅是告诉汇编程序你的程序用了那些导人库。当汇编程序看到一个includelib指令的时候,它把连接器命令装进目标文件,以致一让连接器知道在你的程序中那些导人库需要被连接。尽管你可以非使用includelib不可,你还能用连接器的命令来指定导人库的名字,但是相信我,这是单调乏味的,因为命令行只能传递128个字符(所以你要不停的在那敲代码。)
Now save the example under the name msgbox.asm. Assuming that ml.exe is in your path, assemble msgbox.asm with
现在用msgbox.asm这个命令保存这个例子,把ml.exe放进你的环境变量种,用下面这个命令来汇编msgbox.asm :
ml /c /coff /Cp msgbox.asm
• /c tells MASM to assemble only. Do not invoke link.exe. Most of the time, you would not want to call link.exe automatically since you may have to perform some other tasks prior to calling link.exe.
/c 告诉MASM仅仅汇编。并没有调用LINK。Exe 在大多时候,你将不愿意自动调用link.exe 因为你必须在调用link。Exe之前完成一些任务。
/coff tells MASM to create .obj file in COFF format. MASM uses a variation of COFF (Common Object File Format) which is used under Unix as its own object and executable file format.
/coff 告诉MASM 创建一个COFF格式的文件。MASM的格式是COFF(通用目标文件格式)的一个变种,它被用在UNIX作为它自己的目标文件和可执行文件格式。
/Cp tells MASM to preserve case of user identifiers. If you use hutch's MASM32 package, you may put "option casemap:none" at the head of your source code, just below .model directive to achieve the same effect.
/CP 告诉MASM保持用户定义的标识符的状态不变。如果你用HUTCH的MASM32 软件包,你可以放置 “option casemap:none ”在你的源代码的.model 指令下面可以达到同样的效果。
After you successfully assemble msgbox.asm, you will get msgbox.obj. msgbox.obj is an object file. An object file is only one step away from an executable file. It contains the instructions/data in binary form. What is lacking is some fixups of addresses by the linker.
在你成功的汇编了MSGBOX。ASM后,你将得到msgbox。Obj 目标文件。一个目标文件只需要一步就可以成为一个可执行文件。它包含二进制格式的代码和数据,它缺少的只是被连接器组织好的地址系列。 也就是重定位信息。
Then go on with link:
继续连接:
link /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm32\lib msgbox.obj
/SUBSYSTEM:WINDOWS informs Link what sort of executable this program is
/SUBSYSTEM:WINDOWS 告诉连接器可执行文件运行的平台是
/LIBPATH:<path to import library> tells Link where the import libraries are. If you use MASM32, they will be in MASM32\lib folder.
/LIBPATH:<导入库文件的路径>告诉连接器导入库在那,如果你用MASM32,它们将在MASM32\LIB 文件夹中
Link reads in the object file and fixes it with addresses from the import libraries. When the process is finished you get msgbox.exe.
连接器把这个OBJ文件读进内存并组织它来自导入库的地址信息(也叫重定位信息)。当这个过程完成你得到了msgbox.exe可执行文件。
Now you get msgbox.exe. Go on, run it. You'll find that it does nothing. Well, we haven't put anything interesting into it yet. But it's a Windows program nonetheless. And look at its size! In my PC, it is 1,536 bytes.
现在你生成了msgbox.exe。继续下去,运行它,你将发现它什么都没做。好。
我们还没有放置任何有趣的东西在它里面。但是它已经是一个WINDOWS窗口程序了,(windows的程序有两种,一种是窗口程序,(run可见的),一种是控制台程序run 不可见)我们再看它的大小在我的 Pc里,它占1536个字节。
Next we're going to put in a message box. Its function prototype is:
下面我们放置一个消息框在这个程序中。它的函数原型是这样声明的:
MessageBox
PROTO hwnd:DWORD,
lpText:DWORD,
lpCaption:DWORD,
uType:DWORD
hwnd is the handle to parent window. You can think of a handle as a number that represents the window you're referrring to. Its value is not important to you. You only remember that it represents the window. When you want to do anything with the window, you must refer to it by its handle.
lpText is a pointer to the text you want to display in the client area of the message box. A pointer is really an address of something. A pointer to text string==The address of that string.
lpCaption is a pointer to the caption of the message box
Lpcaption 是一个指向 消息框标题的指针。
uType specifies the icon and the number and type of buttons on the message box
utype 指定在消息框上的一个图标或是按钮的编号类型。
Let's modify msgbox.asm to include the message box.
让我们修改MSGBOX的源程序文件让它包含这个消息框。
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
.data ; 数据节区定义 在8086中的数据段。
MsgBoxCaption db "Iczelion Tutorial No.2",0
MsgBoxText db "Win32 Assembly is Great!",0
.code
start:
invoke MessageBox, NULL, addr MsgBoxText, addr MsgBoxCaption, MB_OK
invoke ExitProcess, NULL
end start
Assemble and run it. You will see a message box displaying the text "Win32 Assembly is Great!".
汇编并运行它,你将看到一个消息框显示一串字符“win32 Assembly is great”
Let's look again at the source code.
让我们在看一源代码:
We define two zero-terminated strings in .data section.
我们定义两个以0 结束的字符串在 。data 节区
Remember that every ANSI string in Windows must be terminated by NULL (0 hexadecimal).
记得吗 所有windows中的ANSI字符串必须终止于NULL字符(define NULL 0X 0 )
We use two constants, NULL and MB_OK. Those constants are documented in windows.inc. So you can refer to them by name instead of the values. This improves readability of your source code.
我们用两个常量,NULL和MB_OK,这两个常量在WINDOWS。INC 文件中有定义。
所有你能用名字来代替它们的值来引用它。这样可以改进你程序的可读性。
The addr operator is used to pass the address of a label to the function. It's valid only in the context of invoke directive. You can't use it to assign the address of a label to a register/variable, for example. You can use offset instead of addr in the above example. However, there are some differences between the two:
这ADDR 操作被用来传递以个标号的地址给函数。它仅仅对有INVOKE指令的上下文环境中有效。你不能用它分配一个标号的地址给寄存器或是变量。 例如:你能用offset 来替代 addr 在上面的例子中。然而它们两个之间是有一些不同的:
1. addr cannot handle forward reference while offset can. For example, if the label is defined somewhere further in the source code than the invoke line, addr will not work.
Addr 不能处理向前引用而offset能,例如:如果标号被定义在源代码中距离invoke这一行代码较远的地方,addr 将不能工作。
invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
......
MsgBoxCaption db "Iczelion Tutorial No.2",0
MsgBoxText db "Win32 Assembly is Great!",0
MASM will report error. If you use offset instead of addr in the above code snippet, MASM will assemble it happily.
MASM将报告错。如果你在上面的代码片断中用OFFSET替代了ADDR,masm 将愉快的汇编它。
2. addr can handle local variables while offset cannot. A local variable is only some reserved space in the stack. You will only know its address during runtime. offset is interpreted during assembly time by the assembler.
addr 能操作局部变量而OFFSET不能 。一个局部变量是一些在堆栈中的备用空间。你只能当程序运行的时才知道这个局部变量的具体地址。Offset是伪指令,它只能在汇编程序汇编的时间才能被解释。
So it's natural that offset won't work for local variables. addr is able to handle local variables because of the fact that the assembler checks first whether the variable referred to by addr is a global or local one. If it's a global variable, it puts the address of that variable into the object file. In this regard, it works like offset. If it's a local variable, it generates an instruction sequence like this before it actually calls the function:
所yi,很自然的,offset指令不能为局部变量工作。Addr能操作局部变量是因为事实上汇编程序首先检查被ADDR引用的这个变量是局部的还是全局的。如果是全局变量,它就把这个变量的地址装进OBJ目标文件。在这点上,它工作像offset。如果它是局部变量,那么在它被函数调用前将产生像这样的一系列指令。
lea eax, LocalVar
push eax
Since lea can determine the address of a label at runtime, this works fine.
既然lea能在运行时确定以个标号的地址,这个工作就是完美的。
________________________________________
文章下载自 http://asm.yeah.net
风向改变翻译于2007年12.1日 下午
Tutorial 3: A Simple Window
第三课:一个简单的窗口程序
________________________________________
In this tutorial, we will build a Windows program that displays a fully functional window on the desktop.
Download the example file here
在这一课中,我们将要创建一个程序,它能在桌面上显示一个标准的窗口
Theory:
原理:
Windows programs rely heavily on API functions for their GUI. This approach benefits both users and programmers. For users, they don't have to learn how to navigate the GUI of each new programs, the GUI of Windows programs are alike.
Windows 程序的图形用户界面依赖于大量的 API函数调用。这种方法对用户和程序员来说都大有好处。对用户而言,他们没必要去学习如何操作每一个新建程序的图形用户界面。因为窗口程序的图形用户界面是一样的。
For programmers, the GUI codes are already there,tested, and ready for use. The downside for programmers is the increased complexity involved. In order to create or manipulate any GUI objects such as windows, menu or icons, programmers must follow a strict recipe. But that can be overcome by modular programming or OOP paradigm.
I'll outline the steps required to create a window on the desktop below:
在下面,我将略述 在桌面上 创建一个窗口 所需要的步骤:
1. Get the instance handle of your program (required)
获取你的程序的实例句柄 (必须的)
2. Get the command line (not required unless your program wants to process a command line)
获取命令行(不是必须的,除非你的程序想处理一个命令行)
3. Register window class (required ,unless you use predefined window types, eg. MessageBox or a dialog box)
注册窗口类( 必须的 ,除非你使用预定义的窗口类型 : 如: MessageBox
或者是一个对话框 )
4. Create the window (required)
创建一个窗口(必须的)
5. Show the window on the desktop (required unless you don't want to show the window immediately)
在桌面上显示你的窗口(必须的除非你不想立即看到你的窗口)
6. Refresh the client area of the window
刷新窗口的客户区
7. Enter an infinite loop, checking for messages from Windows
进入一个死循环,检查来自windows 的消息(即消息循环)
8. If messages arrive, they are processed by a specialized function that is responsible for the window
如果有消息到达,它们被由为这个窗口负责的一个专门的函数处理
9. Quit program if the user closes the window
如果用户关闭了窗口,则退出程序。
As you can see, the structure of a Windows program is rather complex compared to a DOS program. But the world of Windows is drastically different from the world of DOS. Windows programs must be able to coexist peacefully with each other. They must follow stricter rules. You, as a programmer, must also be more strict with your programming style and habit.
和你看到的一样,这个WINDOWs 程序的结构比起DOS程序的结构来说是相当复杂的。但是这个windows的世界也彻底的不同一DOS的世界。windows程序彼此之间必须能无干扰的同时存在.这样他们就必须遵循严格的规则.你.作为一个程序员,必须更加严格的遵循程序设计规范(格式) 并且要养成习惯.,
Content:
内容:
Below is the source code of our simple window program. Before jumping into the gory details of Win32 ASM programming, I'll point out some fine points which will ease your programming.
下面是我们这个简单的窗口程序的源代码.在跳进这个WIN32程序的细节之前,我将指出几个精细的要点,它将让你的程序设计变得简单.
• You should put all Windows constants, structures and function prototypes in an include file and include it at the beginning of your .asm file. It'll save you a lot of effort and typo. Currently, the most complete include file for MASM is hutch's windows.inc which you can download from his page or my page. You can also define your own constants & structure definitions but you should put them into a separate include file.
你应该将所有的windows常量,数据结构,和函数原型放在一个头文件中,并在你的asm.file 源文件的开始处包含这个源文件.它将节省你许多的排字(如果不包含,这些声明都得有泥自己来输入)工作.目前为MASM写的最完整的头文件是HUTCH 写的windows.inc 这个文件你能从他的主页或是我的主页下载到.你也可以定义你自己的常量和结构说明,但是你应该把它们放进一个单独的头文件中.
• Use includelib directive to specify the import library used in your program. For example, if your program calls MessageBox, you should put the line:
includelib user32.lib
at the beginning of your .asm file. This directive tells MASM that your program will make uses of functions in that import library. If your program calls functions in more than one library, just add an includelib for each library you use. Using IncludeLib directive, you don't have to worry about import libraries at link time. You can use /LIBPATH linker switch to tell Link where all the libs are.
用includelib 这条指令来指定在你程序中要用到的导入库.例如,如果你的程序调用MessageBox,你应该在你的asm.file 文件的开始处.放置这一行:
Includelib user32.lib
这条指令告诉MASM你的程序将要用到的函数在那个导入库中.如果你需要调用的函数在不止一个的导入库中,你只需为你使用的每一个导入库增加一条includelib 指令.你并不用担心连接器是如何处理这么多的includelib导入库指令的.你只需要在连接的时候用连接开关/LIPBATH 来指明这些导入库文件在那
• When declaring API function prototypes, structures, or constants in your include file, try to stick to the original names used in Windows include files, including case. This will save you a lot of headache when looking up some item in Win32 API reference.
当在你的包含文件中声明了API函数原型,数据结构,或是常量的时候.注意它们的名称应该和 windows 包含文件中原始的名称保持一致.包括大小写.当你在Win32 API 参考中查找一些项目的时候,这样做可以减少很多令你头痛的事.
• Use makefile to automate your assembling process. This will save you a lot of typing.
用makefile 文件来自动汇编程序,这可以节省你不少时间。
.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
Analysis:
You may be taken aback that a simple Windows program requires so much coding. But most of those codes are just *template* codes that you can copy from one source code file to another. Or if you prefer, you could assemble some of these codes into a library to be used as prologue and epilogue codes. You can write only the codes in WinMain function. In fact, this is what C compilers do. They let you write WinMain codes without worrying about other housekeeping chores. The only catch is that you must have a function named WinMain else C compilers will not be able to combine your codes with the prologue and epilogue. You do not have such restriction with assembly language. You can use any function name instead of WinMain or no function at all.
你可能会吃惊这样一个简单的窗口程序竟然需要如此多的代码。但是这些代码仅仅是“模版”代码,你可以把它们从一个文件复制到另外一个文件。或者,要是你喜欢,你可以把这些代码汇编进一个库中作为代码的开始和收尾。 你仅仅能在WinMain函数中写代码。事实上,这和一些C编译器一样 。它们让你写winmain函数的代码而不需要你关心其它杂务。唯一不同的是 C 编译器要求您的源代码有必须有一个函数叫 WinMain。否则 C 编译器无法将你的代码和有关的前后代码链接。在汇编语言中你不必有这个约束。你能用任何名字来代替WinMain函数或者甚至没有winmain这个函数。
Prepare yourself. This's going to be a long, long tutorial. Let's analyze this program to death!
你得做好准备I,这将是一个很长的讲解,让我们分析这段代码到结束 .
.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
The first three lines are "necessities". .386 tells MASM we intend to use 80386 instruction set in this program. .model flat,stdcall tells MASM that our program uses flat memory addressing model. Also we will use stdcall parameter passing convention as the default one in our program.
Next is the function prototype for WinMain. Since we will call WinMain later, we must define its function prototype first so that we will be able to invoke it.
We must include windows.inc at the beginning of the source code. It contains important structures and constants that are used by our program. The include file , windows.inc, is just a text file. You can open it with any text editor. Please note that windows.inc does not contain all structures, and constants (yet). hutch and I are working on it. You can add in new items if they are not in the file.
Our program calls API functions that reside in user32.dll (CreateWindowEx, RegisterWindowClassEx, for example) and kernel32.dll (ExitProcess), so we must link our program to those two import libraries. The next question : how can I know which import library should be linked to my program? The answer: You must know where the API functions called by your program reside. For example, if you call an API function in gdi32.dll, you must link with gdi32.lib.
This is the approach of MASM. TASM 's way of import library linking is much more simpler: just link to one and only one file: import32.lib.
这是MSAM的连接方式。TASM连接导入库的方式要简单的多: 它仅连接一个也仅有一个库文件: import32.lib
.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
Next are the "DATA" sections.
下一个是”DAT”节区
In .DATA, we declare two zero-terminated strings(ASCIIZ strings): ClassName which is the name of our window class and AppName which is the name of our window. Note that the two variables are initialized.
In .DATA?, two variables are declared: hInstance (instance handle of our program) and CommandLine (command line of our program). The unfamiliar data types, HINSTANCE and LPSTR, are really new names for DWORD. You can look them up in windows.inc. Note that all variables in .DATA? section are not initialized, that is, they don't have to hold any specific value on startup, but we want to reserve the space for future use.
在.DATA节区中,两个变量被声明:实例句柄(代表我们的应用程序)还有命令行(保存从命令行传入的参数 )HINSTANCE 和 LPSTR 是两个我们不熟悉的类型。它们是DWORD类型的一个新名字。你能在windows。Inc中看到它们。 注意在.data?中的这两个变量是未初始化的。 这就是说,在程序启动时它们并不保存任何值,只不过占有一定大小的内存空间.以备我们在未来使用.
.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 contains all your instructions. Your codes must reside between <starting label>: and end <starting label>. The name of the label is unimportant. You can name it anything you like so long as it is unique and doesn't violate the naming convention of MASM.
Our first instruction is the call to GetModuleHandle to retrieve the instance handle of our program. Under Win32, instance handle and module handle are one and the same. You can think of instance handle as the ID of your program. It is used as parameter to several API functions our program must call, so it's generally a good idea to retrieve it at the beginning of our program.
我们的第一条指令是调用GetModuleHandle 来得到我们应用程序的实例句柄.在WIN32中,实例句柄和模块句柄是一样的.你可以认为实例句柄就是你的应用程序ID值.它在我们应用程序中作为必须调用的几个API函数的参数来使用 .所以通常的一个做法是在我们程序的一开始就得到应有程序实例句柄.
Note: Actually under win32, instance handle is the linear address of your program in memory.
注意: 实际上,在win32中,实例句柄就是你的应用程序在内存中的线性地址.
Upon returning from a Win32 function, the function's return value, if any, can be found in eax. All other values are returned through variables passed in the function parameter list you defined for the call.
A Win32 function that you call will nearly always preserve the segment registers and the ebx, edi, esi and ebp registers. Conversely, ecx and edx are considered scratch registers and are always undefined upon return from a Win32 function.
一个win32程序的API函数如果有返回值,通常将返回值保存在EAX中.其它的返回值通过你为了调用这个函数而定义的函数参数表中的变量来传递.
一个win32函数在被你调用时几乎总要保存段寄存器和ebx,edi,esi和ebp这几个寄存器. 而ecx和edx被当做临时寄存器,这两个寄存器从win32函数中返回的值是永远不可知的,有偶然性.
Note: Don't expect the values of eax, ecx, edx to be preserved across API function calls.
注意:从 Windows API 函数中返回后,eax,ecx,edx 中的值和调用前不一定相同
The bottom line is that: when calling an API function, expects return value in eax. If any of your function will be called by Windows, you must also play by the rule: preserve and restore the values of the segment registers, ebx, edi, esi and ebp upon function return else your program will crash very shortly, this includes your window procedure and windows callback functions.
最底一行是说: 当你调用一个windowsAPI函数的时候,返回值被保存在eax中.如果你自己的任何函数被windows调用,你必须遵守这样的规则:在函数入口处保存段寄存器和ebx,edi,esi,ebp这几个寄存器的值并在函数返回的时候恢复它.否则你的程序将立即崩溃. 这个包括你们的程序和windows回调函数.
The GetCommandLine call is unnecessary if your program doesn't process a command line. In this example, I show you how to call it in case you need it in your program.
如果你的程序不处理命令行, 这个GetCommandLine函数就是多余的.在这个例子中,我只是告诉你万一在你的程序中需要调用这个函数时你应该怎样调用.
Next is the WinMain call. Here it receives four parameters: the instance handle of our program, the instance handle of the previous instance of our program, the command line and window state at first appearance. Under Win32, there's NO previous instance. Each program is alone in its address space, so the value of hPrevInst is always 0. This is a leftover from the day of Win16 when all instances of a program run in the same address space and an instance wants to know if it's the first instance. Under win16, if hPrevInst is NULL, then this instance is the first one.
下一行是winmain函数调用.在这儿它接受四个参数:我们的应用程序实例句柄,我们应用程序的前一实例的实例句柄。命令行参数和窗口初看起来的样子。(窗口样式)在win32中,那儿没有前一实例这一概念。每一个win32应用程序在它的地址空间都是独立的.所以hPrevinst 这个值总为0 .这个参数是win16终止后的残留物,在win16一个运行的程序的所有实例都在相同的地址空间而当一个实例句柄想知道它是不是第一个运行的实例的时候,它就得用到hPrevinst这个参数.如果这个值为null则表示这个实例句柄是第一个开始的.
Note: You don't have to declare the function name as WinMain. In fact, you have complete freedom in this regard. You don't have to use any WinMain-equivalent function at all. You can paste the codes inside WinMain function next to GetCommandLine and your program will still be able to function perfectly.
注意: 你不是必须得把函数名字声明为WinMain .实际在这点上你有完全的自主权.你根本就没必要用任何和WinMain等同的函数。你只要把在WINMain函数中的代码粘贴到GetCommandLine这行之后,你程序的功能将一样的完美。
Upon returning from WinMain, eax is filled with exit code. We pass that exit code as the parameter to ExitProcess which terminates our application.
在winmain函数返回时,eax保存着退出代码。我们把这个退出代码作为参数传递给ExitProcess,这个函数终止了我们的应用程序。
WinMain proc Inst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
The above line is the function declaration of WinMain. Note the parameter:type pairs that follow PROC directive. They are parameters that WinMain receives from the caller. You can refer to these parameters by name instead of by stack manipulation. In addition, MASM will generate the prologue and epilogue codes for the function. So we don't have to concern ourselves with stack frame on function enter and exit.
上面这行是winmain函数的声明。注意跟在PROC指令后面parameter:type( 参数:类型)形式的参数.这些参数是winmain函数从调用者拿接收到的.你能引用这些参数通过它们的名字而不是栈处理操作.另外,MASM将为这函数产生前序和后序代码.所以我们没必要使我们自己在函数装入和退出的时候去关心堆栈结构.
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND
LOCAL directive allocates memory from the stack for local variables used in the function. The bunch of LOCAL directives must be immediately below the PROC directive.
LOCAL指令为在函数中使用的局部变量从存储堆栈中分配内存. 这一串的LOCAL指令必须立即跟在PROC指令的下面.
The LOCAL directive is immediately followed by <the name of local variable>:<variable type>. So LOCAL wc:WNDCLASSEX tells MASM to allocate memory from the stack the size of WNDCLASSEX structure for the variable named wc. We can refer to wc in our codes without any difficulty involved in stack manipulation. That's really a godsend, I think. The downside is that local variables cannot be used outside the function they're created and will be automatically destroyed when the function returns to the caller. Another drawback is that you cannot initialize local variables automatically because they're just stack memory allocated dynamically when the function is entered . You have to manually assign them with desired values after LOCAL directives.
LOCAL声明变量是在LOCAL指令后面跟随 <局部变量的名称>:<局部变量的类型>这种形式.所以 LOCAL wc:WNDCLASSEX 告诉MASM 为这个变量名为wc的变量从存储栈中分配长度为WNDCLASSEX结构大小的空间。然后我们在程序中用wc来直接引用变量而无须考虑复杂棘手的堆栈处理问题。考虑到 DOS 下的汇编,我认为这实在是一种恩赐。一个局部变量的作用域仅属创建了它们的函数内部。当被调用的函数将控制权返回给它的调用者时,被调用函数内部创建的局部变量也将被自动销毁。另一个缺点是你不能机械的初始化局部变量因为它们仅当函数装入内存时才动态分配内存堆栈空间。你不得不在用了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
The inimidating lines above are really simple in concept. It just takes several lines of instruction to accomplish. The concept behind all these lines is window class. A window class is nothing more than a blueprint or specification of a window.
上面这几行从概念上说是非常简单的。它仅用几行指令就能实现。在这些行的概念之后是window class 。(窗口类)一个窗口类只不过是一张蓝图或者说成是一个窗口的说明书。
It defines several important characteristics of a window such as its icon, its cursor, the function responsible for it, its color etc. You create a window from a window class. This is some sort of object oriented concept. If you want to create more than one window with the same characteristics, it stands to reason to store all these characteristics in only one place and refer to them when needed. This scheme will save lots of memory by avoiding duplication of information. Remember, Windows is designed in the past when memory chips are prohibitive and most computers have 1 MB of memory. Windows must be very efficient in using the scarce memory resource. The point is: if you define your own window, you must fill the desired characteristics of your window in a WNDCLASS or WNDCLASSEX structure and call RegisterClass or RegisterClassEx before you're able to create your window. You only have to register the window class once for each window type you want to create a window from.
它定义了一个窗口的几个重要特征面。例如它的图标,它的光标, 由哪个 函数为它负责, 它的颜色等等。你创建的每一个窗口都来自于这样的一个窗口类。这多少有些面向对象。如果你要创建不止一个具有相同特征的窗口。显而易见的仅在一个内存空间存储所有的这些特征并且当你需要的时候你就可以引用它。这种设计通过消除副本从而节省了许多内存。请记住,设计windows的时候,存储芯片价格高得惊人 从而大多数计算机内存只有1M,windows必须有效的利用稀有的内存空间。有一点要指出:如果你定义你自己的窗口,在你创建窗口之前,你必须在一个WNDCLASS或WNDCLASSEX结构中填入你想得到的窗口的特征面(属性)并且调用RegisterClass 或者是RegisterClassEx注册这个窗口类.你只要为你想创建的窗口类型注册一次窗口类.
Windows has several predefined Window classes, such as button and edit box. For these windows (or controls), you don't have to register a window class, just call CreateWindowEx with the predefined class name.
Windows有几个预定义的窗口类,如:按钮和编辑框.为这些窗口( 或控件) 你没必要去注册一个窗口类.只要在调用CreateWindowEx的时候指定这些预定义的类名即可.
The single most important member in the WNDCLASSEX is lpfnWndProc. lpfn stands for long pointer to function. Under Win32, there's no "near" or "far" pointer, just pointer because of the new FLAT memory model. But this is again a leftover from the day of Win16. Each window class must be associated with a function called window procedure. The window procedure is responsible for message handling of all windows created from the associated window class.
在WNDCLASSEX中最重要的一个成员是lpfnWndProc .lpfn代表一个指向一个函数的长指针.在 win32中,这里没有near 或是 far 的调用区别.因为win32 中内存模式是平坦模式.(没有段内和段间之分.这在之前作者已经说明)但这再一次作为win16的残留物.每一个窗口类必须与窗口过程函数相关联.这个窗口过程为它关联的窗口类所创建的所有窗口的消息处理负责.
Windows will send messages to the window procedure to notify it of important events concerning the windows it 's responsible for,such as user keyboard or mouse input. It's up to the window procedure to respond intelligently to each window message it receives. You will spend most of your time writing event handlers in window procedure.
I describe each member of WNDCLASSEX below:
Windows 将发送消息给窗口过程用来通报关联窗口的重要事件.这就是它负责的原因.例如:用户键盘或是键盘输入.由于窗口过程几乎智能的响应了每一个它所接受的窗口消息.你大多数的时间只需在窗口过程中写事件处理程序.
下面,我描述每一个WNDCLASSEX类的成员:
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: The size of WNDCLASSEX structure in bytes. We can use SIZEOF operator to get the value.
WNDCLASSEX结构的字节数大小.我们能用SIZEOF操作来获得这个值.
style: The style of windows created from this class. You can combine several styles together using "or" operator.
从这个类创建的窗口的样式.你能用or操作把几个单独的样式结合在一起.
lpfnWndProc: The address of the window procedure responsible for windows created from this class.
为这个类创建的窗口负责的窗口过程的地址.(窗口处理函数地址)
cbClsExtra: Specifies the number of extra bytes to allocate following the window-class structure. The operating system initializes the bytes to zero. You can store window class-specific data here.
指定在window-class 结构后面分配的附加字节数.。操作系统初始化这个字节数为0 .你可以在这里存储窗口类的特有数据.
cbWndExtra: Specifies the number of extra bytes to allocate following the window instance. The operating system initializes the bytes to zero. If an application uses the WNDCLASS structure to register a dialog box created by using the CLASS directive in the resource file, it must set this member to DLGWINDOWEXTRA.
指定分配在窗口实例后的一扩展字节数量.操作系统初始化这个字节数为0.如果应用程序用WNDCLASS结构注册以个对话框类并在资源文件中用class指令创建它.你必须设置这个成员为DLGWINDOWEXTRA.
hInstance: Instance handle of the module.
应用程序模块的实例句柄.
hIcon: Handle to the icon. Get it from LoadIcon call.
应用程序的图标句柄.调用LoadIcon API函数获得
hCursor: Handle to the cursor. Get it from LoadCursor call.
应用程序的光标句柄.调用LoadCursor API函数获得
hbrBackground: Background color of windows created from the class.
从这个类中创建的窗口的背景颜色.
lpszMenuName: Default menu handle for windows created from the class.
指向菜单的指针
lpszClassName: The name of this window class.
这个窗口类的名字.
hIconSm: Handle to a small icon that is associated with the window class. If this member is NULL, the system searches the icon resource specified by the hIcon member for an icon of the appropriate size to use as the small icon.
和窗口类关联的小图标。如果该值为NULL。则把hCursor中的图标转换成大小合适的小图标。
invoke CreateWindowEx, NULL,\
ADDR ClassName,\
ADDR AppName,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
CW_USEDEFAULT,\
NULL,\
NULL,\
hInst,\
NULL
After registering the window class, we can call CreateWindowEx to create our window based on the submitted window class. Notice that there are 12 parameters to this function.
在注册了窗口类后,我们调用CreateWindowEx函数来创建基于在上面提供的窗口类的窗口.注意这个函数有12个参数.
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
Let's see detailed description of each parameter:
让我们来看每一个参数的详细说明:
dwExStyle: Extra window styles. This is the new parameter that is added to the old CreateWindow. You can put new window styles for Windows 95 & NT here.You can specify your ordinary window style in dwStyle but if you want some special styles such as topmost window, you must specify them here. You can use NULL if you don't want extra window styles.
扩展窗口样式。相对于旧的CreateWindow这是一个新的参数。您可以为windows95和NT设置新的窗口风格。您可以在dwStyle中指定普通的窗口风格,但是一些特殊的窗口风格,如顶层窗口则必须在此参数中指定。如果您不想指定任何特别的风格,则把此参数设为NULL
lpClassName: (Required). Address of the ASCIIZ string containing the name of window class you want to use as template for this window. The Class can be your own registered class or predefined window class. As stated above, every window you created must be based on a window class.
(必须的)。你想作为模版用于这个窗口的窗口类名(ASCIIZ字符串)的地址,这个类可以使你自己注册的类,也可以是windows预定义的类。按照上面的规定,你创建的每一个窗口都必须基于以个窗口类。
lpWindowName: Address of the ASCIIZ string containing the name of the window. It'll be shown on the title bar of the window. If this parameter is NULL, the title bar of the window will be blank.
包含窗口名字的ASCIIZ字符串的地址.它将在窗口的标题栏显示.如果这个参数为空,这个窗口的标题栏将是空白.
dwStyle: Styles of the window. You can specify the appearance of the window here. Passing NULL is ok but the window will have no system menu box, no minimize-maximize buttons, and no close-window button. The window would not be of much use at all. You will need to press Alt+F4 to close it. The most common window style is WS_OVERLAPPEDWINDOW. A window style is only a bit flag. Thus you can combine several window styles by "or" operator to achieve the desired appearance of the window. WS_OVERLAPPEDWINDOW style is actually a combination of the most common window styles by this method.
窗口的样式. 你能在这里指定窗口的外貌.传递NULL是好但是那样窗口将没有系统菜单栏,没有最小化最大化按钮.也没有关闭按钮.这样的窗口将完全没有多少用处.你不得不按ALT+ F4 来关闭它.大多数普通窗口的样式是WS_OVERLAPPEDWINDW.一个窗口的样式仅一个比特位的标志.因而你可以通过or操作来联合几种窗口的样式来达到你想得到的窗口外观.
X,Y: The coordinate of the upper left corner of the window. Normally this values should be CW_USEDEFAULT, that is, you want Windows to decide for you where to put the window on the desktop.
窗口左上角的坐标.通常这个值应该是CW_USEDEFAULT.换句话说.你想让windows 决定在桌面的那个位置放置窗口.
nWidth, nHeight: The width and height of the window in pixels. You can also use CW_USEDEFAULT to let Windows choose the appropriate width and height for you.
以像素为单位来决定窗口的高和宽.你也可以用CW_USEDEFAULT 来让widows为你选择一个适当的高和宽。
hWndParent: A handle to the window's parent window (if exists). This parameter tells Windows whether this window is a child (subordinate) of some other window and, if it is, which window is the parent. Note that this is not the parent-child relationship of multiple document interface (MDI). Child windows are not bound to the client area of the parent window. This relationship is specifically for Windows internal use. If the parent window is destroyed, all child windows will be destroyed automatically. It's really that simple. Since in our example, there's only one window, we specify this parameter as NULL.
一个指向父窗口的句柄,(如果父窗口存在的话)这个参数告诉windows这个窗口是否是其他窗口的孩子(从属关系)。如果是,那么那一个窗口是它的父亲。(从属于那个窗口)注意这并不是多文档界面MDI的父子关系。子窗口并不一定要在父窗口的客户区内.这种关系是指在windows内部维护使用的关系.(进程树)如果父窗口被销毁,那么所有的子窗口都将自动被销毁.就这么简单.在我们以后的例子中,都只有也仅有一个窗口,因此我们指定这个参数为NULL.
hMenu: A handle to the window's menu. NULL if the class menu is to be used. Look back at the a member of WNDCLASSEX structure, lpszMenuName. lpszMenuName specifies *default* menu for the window class. Every window created from this window class will have the same menu by default. Unless you specify an *overriding* menu for a specific window via its hMenu parameter. hMenu is actually a dual-purpose parameter. In case the window you want to create is of a predefined window type (ie. control), such control cannot own a menu. hMenu is used as that control's ID instead. Windows can decide whether hMenu is really a menu handle or a control ID by looking at lpClassName parameter. If it's the name of a predefined window class, hMenu is a control ID. If it's not, then it's a handle to the window's menu.
一个窗口菜单句柄。如果这个类的菜单被使用,则用NULL。回顾一下WNDCLASSEX 的成员LpszMenuName.lpszMenuName为这个窗口类指定了一个“默认”的菜单。每一个从这个类创建的窗口都将有着相同的菜单。除非你通过hMenuc参数为某一窗口指定一个“基本”的菜单。hMenu 实际是一个有双重目的的参数。万一你创建的窗口是系统预定义的窗口类型(如:控件)这样的控件并不拥有自己的菜单。hMenu被那个控件的ID所代替。Windows 通过查看lpClassName 参数来判定这是一个实在的菜单句柄还是仅仅是一个控件的ID值。如果这个名字是预定义的窗口类, hMenu是一个控件ID 。否则。它就是一个窗口的句柄 。
hInstance: The instance handle for the program module creating the window.
创建这个窗口程序模块的实例句柄。
lpParam: Optional pointer to a data structure passed to the window. This is used by MDI window to pass the CLIENTCREATESTRUCT data. Normally, this value is set to NULL, meaning that no data is passed via CreateWindow(). The window can retrieve the value of this parameter by the call to GetWindowLong function.
可选择的指针,指向一个传递给窗口的数据结构。如在MDI中在产生窗口时传递 CLIENTCREATESTRUCT 结构的参数通常,这个值被设置为空。没有数据通过CreateWindow()函数被传递。Windows能够通过调用GetWindowlong这个函数来获得(检索)这个参数的值。
mov hwnd,eax
invoke ShowWindow, hwnd,CmdShow
invoke UpdateWindow, hwnd
On successful return from CreateWindowEx, the window handle is returned in eax. We must keep this value for future use. The window we just created is not automatically displayed. You must call ShowWindow with the window handle and the desired *display state* of the window to make it display on the screen. Next you can call UpdateWindow to order your window to repaint its client area. This function is useful when you want to update the content of the client area. You can omit this call though.
我们在上面已经成功的从CreateWindowEX返回,这个窗口的句柄被存放在eax中。 我们必须保存这个值以便在将来使用它。我们仅是创建了这个窗口并不会自动的显示。你必须传递窗口句柄给ShowWindow并调用这个函数来将窗口按我们想要的显示状态显示在屏幕上。你得按顺序在下一步用UpdateWindow函数来重绘它的客户区.当你想更新客户区的内容时,这个函数时非常有用的.尽管这个调用可以被忽略.
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
At this time, our window is up on the screen. But it cannot receive input from the world. So we have to *inform* it of relevant events. We accomplish this with a message loop. There's only one message loop for each module. This message loop continually checks for messages from Windows with GetMessage call. GetMessage passes a pointer to a MSG structure to Windows.
在那个时候 ,我们的窗口已经显示在屏幕上了。但是它不能从我们这世界接受任何输入。所以我们必须通告它有关的事件。我们用一个消息循环来实现了这个通告。每一个模块都有一个消息循环。这个消息循环调用GetMessage不断的检查从windows来的消息。GetMessage传递一个MSG结构给windows
This MSG structure will be filled with information about the message that Windows want to send to a window in the module. GetMessage function will not return until there's a message for a window in the module. During that time, Windows can give control to other programs. This is what forms the cooperative multitasking scheme of Win16 platform. GetMessage returns FALSE if WM_QUIT message is received which, in the message loop, will terminate the loop and exit the program.
这个MSG结构的信息将被填充,然后由windows向在模块中的窗口发送。GetMessage函数将不返回任何值直到在模块中的窗口中有这个消息为止。 在这段时间,windows能将控制权转移给其它程序。这就是窗体在win16平台中的协同多任务处理模式。在消息循环中,如果WM_QUIT消息被接受,GetMessage将返回FALSE ,并终止消息循环和退出这个程序.
TranslateMessage is a utility function that takes raw keyboard input and generates a new message (WM_CHAR) that is placed on the message queue. The message with WM_CHAR contains the ASCII value for the key pressed, which is easier to deal with than the raw keyboard scan codes. You can omit this call if your program doesn't process keystrokes.
DispatchMessage sends the message data to the window procedure responsible for the specific window the message is for.
TranslateMessage是一个实用的函数,它接受键盘输入原始消息并在消息队列中产生一个新的消息(WM_CHAR) 这个WM_CHAR 包含这个按键的ASCII值。这比原始的键盘扫描码容易分配的多。如果你的程序不需要击键你可以忽略这个调用。
DispatchMessage、发送消息数据 给为具体的窗口 负责消息处理的窗口过程。
mov eax,msg.wParam
ret
WinMain endp
If the message loop terminates, the exit code is stored in wParam member of the MSG structure. You can store this exit code into eax to return it to Windows. At the present time, Windows does not make use of the return value, but it's better to be on the safe side and plays by the rule.
如果消息循环终止,退出码被存储在消息结构的wParam成员中。你可以在eax中保存这个退出码并把它返回给windows。到目前为止,windows并没有用到这个返回值。但为求保险我们最好遵守这条规则。
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
This is our window procedure. You don't have to name it WndProc. The first parameter, hWnd, is the window handle of the window that the message is destined for. uMsg is the message. Note that uMsg is not a MSG structure. It's just a number, really. Windows defines hundreds of messages, most of which your programs will not be interested in. Windows will send an appropriate message to a window in case something relevant to that window happens. The window procedure receives the message and reacts to it intelligently. wParam and lParam are just extra parameters for use by some messages. Some messages do send accompanying data in addition to the message itself. Those data are passed to the window procedure by means of lParam and wParam.
这是我们的窗口过程,你不必要非要用WndProc这个名字。第一个参数 hWnd是一个窗口句柄,使消息去往的地方。uMsg是一个消息。注意这个uMsg不是一个消息结构。它仅仅是一个实在的数字。Windows定义了许多的消息,大多时候你的应用程序对它们并不感兴趣。万一在窗口中有某一事件发生,,windows将发送一个以之相适应的消息给这窗口。窗口过程接受这个消息并智能化的处理它 。wParam 和lParam 被一些消息用作额外的参数,一些消息发送时除了发送它自身外还随同发送一些数据。这些数据主要通过wParam 和 lParam 传递给窗口过程。
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
Here comes the crucial part. This is where most of your program's intelligence resides. The codes that respond to each Windows message are in the window procedure. Your code must check the Windows message to see if it's a message it's interested in. If it is, do anything you want to do in response to that message and then return with zero in eax. If it's not, you MUST call DefWindowProc, passing all parameters you received to it for default processing.. This DefWindowProc is an API function that processes the messages your program are not interested in.
这儿是至关重要的一部份。这是你程序大多数智能化的代码驻留的地方。这些代码必须在这里回应每一个在窗口过程中windows消息。你的代码必须检查windows消息 来看看这个消息是不是它感兴趣的消息。如果它是,在那个消息的响应代码中做任何你想做的事情然后在eax中用0返回。如果它不是,你必须调用DefWindowProc 传递你接收到所有参数给这个函数做缺省处理。这个DefWindowPro 是一个API函数,用来处理在程序中那些你并不感兴趣的消息的。
The only message that you MUST respond to is WM_DESTROY. This message is sent to your window procedure whenever your window is closed. By the time your window procedure receives this message, your window is already removed from the screen. This is just a notification that your window was destroyed, you should prepare yourself to return to Windows.
In response to this, you can perform housekeeping prior to returning to Windows. You have no choice but to quit when it comes to this state. If you want to have a chance to stop the user from closing your window, you should process WM_CLOSE message. Now back to WM_DESTROY, after performing housekeeping chores, you must call PostQuitMessage which will post WM_QUIT back to your module. WM_QUIT will make GetMessage return with zero value in eax, which in turn, terminates the message loop and quits to Windows. You can send WM_DESTROY message to your own window procedure by calling DestroyWindow function.
仅有一个消息是你必须应答的,它就是WM_DESTROY.这个消息是当你的程序被关闭时传递给你的窗口处理过程的.当你的窗口程序接收到这条消息,你的窗口就已经从屏幕上移除了.这仅仅是通知你应用程序的窗口已经被销毁,你必须自己准备返回windows系统.
在响应这个消息时, 你能执行一些在返回windows前的内务处理代码.当进入这个状态的时候,你除了退出别无它法. 如果您要那样做的话,(指还可以在关闭窗口后不退出)可以处理 WM_CLOSE 消息。在处理完清理工作后,您必须调用 PostQuitMessage,该函数会把 WM_QUIT 消息传回您的应用程序,而该消息会使得 GetMessage 返回,并在 eax 寄存器中放入 0,然后会结束消息循环并退回 WINDOWS。您可以在您的程序中调用 DestroyWindow 函数,它会发送一个 WM_DESTROY 消息给您自己的应用程序,从而迫使它退出。
________________________________________
This article come from Iczelion's asm page
风向改变翻译于 07年12.3日
Tutorial 4: Painting with Text
________________________________________
In this tutorial, we will learn how to "paint" text in the client area of a window. We'll also learn about device context.
Theory:
原理:
Text in Windows is a type of GUI object. Each character is composed of numerous pixels (dots) that are lumped together into a distinct pattern. That's why it's called "painting" instead of "writing". Normally, you paint text in your own client area (actually, you can paint outside client area but that's another story). Putting text on screen in Windows is drastically different from DOS. In DOS, you can think of the screen in 80x25 dimension. But in Windows, the screen are shared by several programs. Some rules must be enforced to avoid programs writing over each other's screen. Windows ensures this by limiting painting area of each window to its own client area only. The size of client area of a window is also not constant. The user can change the size anytime. So you must determine the dimensions of your own client area dynamically.
文本在windows中属于GUI对象类型.每一个字符由许多像素组成,这些像素被集中在一起就成为一清晰的图案 .这就是它为什么被认为是”绘制”而不是”写”的原因. 通常,你都在应有程序的的客户区绘制一文本. ( 实际上,你也可以在客户外绘制文本,但那是另外一回事).在Widnows中绘制文本和在DOS中绘制文本是截然不同的.你DOS中,你可以认为屏幕在80*25的一个区域范围内。但是在windows中,屏幕被多个应用程序共享.一些规则必须被执行以便允许应用程序彼此之间能重绘屏幕.windows限制了应用程序只能在它的客户区内绘画从而保证了这一点( 屏幕共享).客户区的大小并不是固定的,用户可以在任何时候改变它.所以你必须动态的求出你自己客户区的面积大小.
Before you can paint something on the client area, you must ask for permission from Windows. That's right, you don't have absolute control of the screen as you were in DOS anymore. You must ask Windows for permission to paint your own client area. Windows will determine the size of your client area, font, colors and other GDI attributes and sends a handle to device context back to your program. You can then use the device context as a passport to painting on your client area.
What is a device context? It's just a data structure maintained internally by Windows. A device context is associated with a particular device, such as a printer or video display. For a video display, a device context is usually associated with a particular window on the display.
Some of the values in the device context are graphic attributes such as colors, font etc. These are default values which you can change at will. They exist to help reduce the load from having to specify these attributes in every GDI function calls.
When a program need to paint, it must obtain a handle to a device context. Normally, there are several ways to accomplish this.
当你的程序需要绘画时,它必须得到一个设备环境句柄.通常,这里有几种方式可以实现它.
call BeginPaint in response to WM_PAINT message.
在响应WM_PAINY消息的时候调用.
call GetDC in response to other messages.
在响应其他消息的时候调用.
call CreateDC to create your own device context
你可以创建你自己的设备环境.
One thing you must remember, after you're through with the device context handle, you must release it during the processing of a single message. Don't obtain the handle in response to one message and release it in response to another.
有一件事你必须记住,在你抛弃一个设备环境句柄的时候,你必须在处理一个单独的消息过程的时候释放它.在响应一个消息时获得的句柄不要在响应另一个消息的时候释放它.
Windows posts WM_PAINT messages to a window to notify that it's now time to repaint its client area. Windows does not save the content of client area of a window. Instead, when a situation occurs that warrants a repaint of client area (such as when a window was covered by another and is just uncovered), Windows puts WM_PAINT message in that window's message queue.
It's the responsibility of that window to repaint its own client area. You must gather all information about how to repaint your client area in the WM_PAINT section of your window procedure, so the window procudure can repaint the client area when WM_PAINT message arrives.
Another concept you must come to terms with is the invalid rectangle. Windows defines an invalid rectangle as the smallest rectangular area in the client area that needs to be repainted. When Windows detects an invalid rectangle in the client area of a window , it posts WM_PAINT message to that window. In response to WM_PAINT message, the window can obtain a paintstruct structure which contains, among others, the coordinate of the invalid rectangle. You call BeginPaint in response to WM_PAINT message to validate the invalid rectangle. If you don't process WM_PAINT message, at the very least you must call DefWindowProc or ValidateRect to validate the invalid rectangle else Windows will repeatedly send you WM_PAINT message.
Below are the steps you should perform in response to a WM_PAINT message:
下面这些步骤你应该在WM_PAINT消息中完成.
Get a handle to device context with BeginPaint.
用BeginPaint获取一个设备环境句柄
Paint the client area.
绘制客户区面积
Release the handle to device context with EndPaint
用EndPaint函数释放设备环境句柄.
Note that you don't have to explicitly validate the invalid rectangle. It's automatically done by the BeginPaint call. Between BeginPaint-Endpaint pair, you can call any GDI functions to paint your client area. Nearly all of them require the handle to device context as a parameter.
注意,你不必要显然的让无效区有效. 它被BeginPaint函数调用自动完成。BeginPaint-Endpaint是一对的,你能调用任何GDI函数来绘制你的客户区。几乎所有的这些函数都需要用设备环境句柄作为参数。
Content:
内容:
We will write a program that displays a text string "Win32 assembly is great and easy!" in the center of the client area.
我们将写一个程序用来在客户区的中间显示一个字符串“win32 assembly is gereat and easy!”
.386
.model flat,stdcall
option casemap:none
WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
OurText db "Win32 assembly is great and easy!",0
.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.CODE
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd: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
push hInst
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
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,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:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
LOCAL rect:RECT
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke GetClientRect,hWnd, ADDR rect
invoke DrawText, hdc,ADDR OurText,-1, ADDR rect, \
DT_SINGLELINE or DT_CENTER or DT_VCENTER
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax, eax
ret
WndProc endp
end start
Analysis:
分析:
The majority of the code is the same as the example in tutorial 3. I'll explain only the important changes.
这里大多数代码和第三课的例子相同。我仅说明其中不同的地方。
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
LOCAL rect:RECT
These are local variables that are used by GDI functions in our WM_PAINT section. hdc is used to store the handle to device context returned from BeginPaint call. ps is a PAINTSTRUCT structure. Normally you don't use the values in ps. It's passed to BeginPaint function and Windows fills it with appropriate values. You then pass ps to EndPaint function when you finish painting the client area. rect is a RECT structure defined as follows:
这些局部变量被在WM_PAINT消息中的GDI函数调用。Hdc被用来存放调用BeginPaint函数中返回的设备环境句柄。Ps是一个PAINTSTRUCT的结构体。通常你不会用到在PS中的值。它被传递给BeginPaint函数并且由windows用适当的值填满它。当你在客户区中完成绘制时,再传递PS 结构给EndPaint函数。Rect是一个RECT结构,下面是它的定义:
RECT Struct
left LONG ?
top LONG ?
right LONG ?
bottom LONG ?
RECT ends
Left and top are the coordinates of the upper left corner of a rectangle Right and bottom are the coordinates of the lower right corner. One thing to remember: The origin of the x-y axes is at the upper left corner of the client area. So the point y=10 is BELOW the point y=0.
Left 和 top 是这个矩形的左上角坐标。Right 和 bottom 是这个矩形的右下角坐标。有一件事你要记住:x-y坐标的起点在客户区的左上角,这样y=10这个点就在y=0这个点的下面。
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke GetClientRect,hWnd, ADDR rect
invoke DrawText, hdc,ADDR OurText,-1, ADDR rect, \
DT_SINGLELINE or DT_CENTER or DT_VCENTER
invoke EndPaint,hWnd, ADDR ps
In response to WM_PAINT message, you call BeginPaint with handle to the window you want to paint and an uninitialized PAINTSTRUCT structure as parameters. After successful call, eax contains the handle to device context. Next you call GetClientRect to retrieve the dimension of the client area. The dimension is returned in rect variable which you pass to DrawText as one of its parameters. DrawText's syntax is:
在响应WM_PAINT消息的时候,你调用BeginPaint函数,并将一个句柄和一个未初始化的PAINTSTRUCT结构传给那个你想绘制的窗口。在成功调用之后,在eax中返回以个设备环境句柄。下一步你调用GetClientRect来得到客户区的大小。客户区的大小被返回在rect变量中,并将rect作为一个参数传递给DrawText函数。Drawtext的句法是:
DrawText proto hdc:HDC, lpString:DWORD, nCount:DWORD, lpRect:DWORD, uFormat:DWORD
DrawText is a high-level text output API function. It handles some gory details such as word wrap, centering etc. so you could concentrate on the string you want to paint. Its low-level brother, TextOut, will be examined in the next tutorial. DrawText formats a text string to fit within the bounds of a rectangle. It uses the currently selected font,color and background (in the device context) to draw the text.Lines are wrapped to fit within the bounds of the rectangle. It returns the height of the output text in device units, in our case, pixels. Let's see its parameters:
DrawText是一个高层的文本输出函数。它能处理诸如文本换行,置于中心等等这些杂事。所以你只要把精力集中在你想绘制的文本上。它的低层调用兄弟,TextOut 将在下一课中讲解。DrawText 在矩形中格式化一个文本字符串。它通常用当前选择的字体,颜色,和背景(在设备环境中)来绘制文本。在这个矩形的内部文本以行为单位。它会返回以设备逻辑单位度量的文本的高度。在我们的例子中,我们的设备逻辑单位是像素 。让我们看看它的参数:
hdc handle to device context
设备环境句柄
lpString The pointer to the string you want to draw in the rectangle. The string must be null-terminated else you would have to specify its length in the next parameter, nCount.
nCount The number of characters to output. If the string is null-terminated, nCount must be -1. Otherwise nCount must contain the number of characters in the string you want to draw.
需要输出的字符数.如果这个字符窜时以NULL结尾的.nCount必须是-1 否则nCount必须包含你想绘制的字符串的字符数.
lpRect The pointer to the rectangle (a structure of type RECT) you want to draw the string in. Note that this rectangle is also a clipping rectangle, that is, you could not draw the string outside this rectangle.
指向一个你想在它内部绘制字符的矩形的指针( 一个RECT 的结构指针)。注意这个矩形是一个受限制的矩形。也就是,你不能将字符画在这个矩形外。
uFormat The value that specifies how the string is displayed in the rectangle. We use three values combined by "or" operator:
这个值指定字符串如何在显示在矩形中,我们可以用or操作来讲三个值组合在一起。
o DT_SINGLELINE specifies a single line of text
指定文本单行显示。
o DT_CENTER centers the text horizontally.
文本水平居中
o DT_VCENTER centers the text vertically. Must be used with DT_SINGLELINE.
文本垂直居中,必须和DT_SINGLELINE 一起使用 .
After you finish painting the client area, you must call EndPaint function to release the handle to device context.
在你完成客户区的绘制工作后,你必须调用EndPaint函数来释放这个设环境句柄.
That's it. We can summarize the salient points here:
我们在这里总结一下绘制文本重要的几点:
• You call BeginPaint-EndPaint pair in response to WM_PAINT message.
你在WM_PAINT消息中调用 BeginPaint-EndPaint这对函数来响应它。
• Do anything you like with the client area between the calls to BeginPaint and EndPaint.
在BeginPaint和EndPaint两个调用之间做任何你想在客户区中做的事。
• If you want to repaint your client area in response to other messages, you have two choices:
如果你想在其它消息中充绘你的客户区,你必须注意两点:
o Use GetDC-ReleaseDC pair and do your painting between these calls
你的绘制工作必须用在GetDC 和ReleaseDC这两个函数之间。
o Call InvalidateRect or UpdateWindow to invalidate the entire client area, forcing Windows to put WM_PAINT message in the message queue of your window and do your painting in WM_PAINT section
调用InvalidataRect 或者是UpdataWindow 来使整个客户区无效。强制windows放置一个消息在你窗口的消息队列中并在WM_PAINT消息中绘制你想绘制的任何东西。
________________________________________
This article come from Iczelion's asm page
风向改变翻译于 12月6日
Tutorial 5: More about Text
更多的文本属性
________________________________________
We will experiment more with text attributes, ie. font and color.
Download the example file here.
我们将实验更多的文本属性,例如:字体和颜色 。
Theory:
理论:
Windows color system is based on RGB values, R=red, G=Green, B=Blue. If you want to specify a color in Windows, you must state your desired color in terms of these three major colors. Each color value has a range from 0 to 255 (a byte value). For example, if you want pure red color, you should use 255,0,0. Or if you want pure white color, you must use 255,255,255. You can see from the examples that getting the color you need is very difficult with this system since you have to have a good grasp of how to mix and match colors.
Widnows的颜色系统是基于RGB值的,R= 红色 , G=绿色,B=蓝色.如果你想在文本中指定一颜色值,你必须根据上面三个主要的颜色值来声明它.每一个颜色可在0-255(一个字节的值)之间取值.例如:如果你想要纯红色,你应该使用255.0.0或者你想纯白色,你必须用255.255.255.从我们的例子中你能看到,在系统设置一个你需要的颜色是非常困难的,因为你必须深刻的了解如何混合和匹配这些颜色值.
For text color and background, you use SetTextColor and SetBkColor, both of them require a handle to device context and a 32-bit RGB value. The 32-bit RGB value's structure is defined as:
至于文本的颜色和背景色,你能用SetTextColor和SetBkColor函数,它们需要两个参数,一个是设备环境的句柄,一个是32位的RGB值.这个32位的RGB值的结构定义如下:
RGB_value struct
unused db 0
blue db ?
green db ?
red db ?
RGB_value ends
Note that the first byte is not used and should be zero. The order of the remaining three bytes is reversed,ie. blue, green, red. However, we will not use this structure since it's cumbersome to initialize and use. We will create a macro instead. The macro will receive three parameters: red, green and blue values. It'll produce the desired 32-bit RGB value and store it in eax. The macro is as follows:
注意第一个字节没有被使用并且应该为0。 剩下的三个字节是颠倒的。例如:蓝,绿,红。我们将不用这个结构,因为这个结构的初始化和使用是麻烦的。我们将创建一个宏来代替。 这个宏将接收这三个参数:红,绿和蓝的颜色值。它将创建你想要的32-位RGB颜色值并将这个值储存在eax中,这个宏如下:
RGB macro red,green,blue
xor eax,eax
mov ah,blue
shl eax,8 //逻辑左移,将蓝色保存在eax的第二个字节.
mov ah,green
mov al,red
endm
You can put this macro in the include file for future use.
You can "create" a font by calling CreateFont or CreateFontIndirect. The difference between the two functions is that CreateFontIndirect receives only one parameter: a pointer to a logical font structure, LOGFONT. CreateFontIndirect is the more flexible of the two especially if your programs need to change fonts frequently. However, in our example, we will "create" only one font for demonstration, we can get away with CreateFont. After the call to CreateFont, it will return a handle to a font which you must select into the device context. After that, every text API function will use the font we have selected into the device context.
你能将这个宏放在include文件中以便将来使用.你通过调用CreateFont或者是CreateFontIndirect 函数来创建一个字体.这两个API函数的不同之处在于CreateFontIndirect 函数仅接收一个参数: 一个指向逻辑字体的结构LOGFONT.CreateFontIndirect函数是这两个函数中最灵活的一个特别当你参数需要频繁的改变字体时.然而在我们的例子中,我们仅为这个实例创建一个字体.我们用CreateFont就可以了.它将返回一个字体句柄,你必须把这个句柄选进设备环境中.在这样做了之后,每一个API文本函数都将使用我们在设备环境中选进的这个字体.
Content:
内容:
.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
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib
RGB macro red,green,blue
xor eax,eax
mov ah,blue
shl eax,8
mov ah,green
mov al,red
endm
.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
TestString db "Win32 assembly is great and easy!",0
FontName db "script",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd: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
push hInst
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
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,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:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
LOCAL hfont:HFONT
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke CreateFont,24,16,0,0,400,0,0,0,OEM_CHARSET,\
OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,\
DEFAULT_QUALITY,DEFAULT_PITCH or FF_SCRIPT,\
ADDR FontName
invoke SelectObject, hdc, eax
mov hfont,eax
RGB 200,200,50
invoke SetTextColor,hdc,eax
RGB 0,0,255
invoke SetBkColor,hdc,eax
invoke TextOut,hdc,0,0,ADDR TestString,SIZEOF TestString
invoke SelectObject,hdc, hfont
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
Analysis:
invoke CreateFont,24,16,0,0,400,0,0,0,OEM_CHARSET,\
OUT_DEFAULT_PRECIS,CLIP_DEFAULT_PRECIS,\
DEFAULT_QUALITY,DEFAULT_PITCH or FF_SCRIPT,\
ADDR FontName
CreateFont creates a logical font that is the closest match to the given parameters and the font data available. This function has more parameters than any other function in Windows. It returns a handle to logical font to be used by SelectObject function. We will examine its parameters in detail.
CreateFont创建一种逻辑字体,这种字体与给定的参数的值是接近相等的并且这个字体的数据是可用的.比起其它在windows中的其它函数,这个参数有更多的值.它返回一个逻辑字体句柄被SelectObject函数使用.我们将详细分析它的参数.
CreateFont proto nHeight:DWORD,\
nWidth:DWORD,\
nEscapement:DWORD,\
nOrientation:DWORD,\
nWeight:DWORD,\
cItalic:DWORD,\
cUnderline:DWORD,\
cStrikeOut:DWORD,\
cCharSet:DWORD,\
cOutputPrecision:DWORD,\
cClipPrecision:DWORD,\
cQuality:DWORD,\
cPitchAndFamily:DWORD,\
lpFacename:DWORD
nHeight The desired height of the characters . 0 means use default size.
你想设置的字符的高度( 字符的高度),0意味着使用默认值.
nWidth The desired width of the characters. Normally this value should be 0 which allows Windows to match the width to the height. However, in our example, the default width makes the characters hard to read, so I use the width of 16 instead.
你想设置的字符的高度.通常这个值应该为0,0允许windows为字符的高度匹配一个合适的宽.然而在我们的例子中,这个默认的宽值将使字符阅读困难,所以我们用16这个值来代替默认值(也叫缺省值)
nEscapement Specifies the orientation of the next character output relative to the previous one in tenths of a degree. Normally, set to 0. Set to 900 to have all the characters go upward from the first character, 1800 to write backwards, or 2700 to write each character from the top down.
相对于上一个输出字符指定下一个输出字符向东旋转十分之一的度数.通常设为0, 900代表转90度,1800转190度,2700转270度。
nOrientation Specifies how much the character should be rotated when output in tenths of a degree. Set to 900 to have all the characters lying on their backs, 1800 for upside-down writing, etc.
指定当这个字符输出时被旋转多少度( 设定的值的10分之一) ,设置900将使所有的字符置后.1800将使它们倒置.等等.
nWeight Sets the line thickness of each character. Windows defines the following sizes:
设置每一个字符的笔画宽度, widows定义了下面几种尺寸:
FW_DONTCARE equ 0
FW_THIN equ 100
FW_EXTRALIGHT equ 200
FW_ULTRALIGHT equ 200
FW_LIGHT equ 300
FW_NORMAL equ 400
FW_REGULAR equ 400
FW_MEDIUM equ 500
FW_SEMIBOLD equ 600
FW_DEMIBOLD equ 600
FW_BOLD equ 700
FW_EXTRABOLD equ 800
FW_ULTRABOLD equ 800
FW_HEAVY equ 900
FW_BLACK equ 900
cItalic 0 for normal, any other value for italic characters.
通常为0,任何其它的值将使字体倾斜.
cUnderline 0 for normal, any other value for underlined characters.
通常为0,任何其它的值将给这个字体加上下划线.
cStrikeOut 0 for normal, any other value for characters with a line through the center.
通常为0,任何其它的值将使字符中间加上一横线(删除线)
cCharSet The character set of the font. Normally should be OEM_CHARSET which allows Windows to select font which is operating system-dependent.
设置这个字体的字符集。通常应该是OEM_CHARSET,这个值将允许windows选择与操作系统相关的字符集。
cOutputPrecision Specifies how much the selected font must be closely matched to the characteristics we want. Normally should be OUT_DEFAULT_PRECIS which defines default font mapping behavior.
指定我们选择的字体接近真实字体的精度。 一般选用OUT_DEFAULT_PRECIS,它定义为默认的映射状态
cClipPrecision Specifies the clipping precision. The clipping precision defines how to clip characters that are partially outside the clipping region. You should be able to get by with CLIP_DEFAULT_PRECIS which defines the default clipping behavior.
指定裁剪精度.这个裁剪精度定义了如何对在裁剪区域外面的字符进行修剪.你应该通过CLIP_DEFAULT_PRECTS来获取系统默认的裁剪方式.
cQuality Specifies the output quality. The output quality defines how carefully GDI must attempt to match the logical-font attributes to those of an actual physical font. There are three choices: DEFAULT_QUALITY, PROOF_QUALITY and DRAFT_QUALITY.
指定输出的质量.这个输出质量规定了图形设备接口如何谨慎的让逻辑字体和那些实际的物理字体尽可能的相一致.( 无论是在数量还是质量上)这里有三个选项:DEFAULT_QUALITY, PROOF_QUALITY and DRAFT_QUALITY.
cPitchAndFamily Specifies pitch and family of the font. You must combine the pitch value and the family value with "or" operator.
指定字体的间距和字体的家族.你必须用or操作将间距值和家族值联合在一起.
lpFacename A pointer to a null-terminated string that specifies the typeface of the font.
一个指向以NULL结束的字符串,这个字符串指定了字体的字面(名字).
The description above is by no means comprehensive. You should refer to your Win32 API reference for more details.
这上面的描述绝对不是容易理解的.你还应该参考你的WIN32API 手册来得到更多的详细资料.
invoke SelectObject, hdc, eax
mov hfont,eax
After we get the handle to the logical font, we must use it to select the font into the device context by calling SelectObject. SelectObject puts the new GDI objects such as pens, brushs, and fonts into the device context to be used by GDI functions. It returns the handle to the replaced object which we should save for future SelectObject call. After SelectObject call, any text output function will use the font we just selected into the device context.
在我们得到这个逻辑字体的句柄之后,我们必须通过调用SelectObject函数来将这个字体选进设备环境中才能使用它。SelectObject的作用是放置一个新的GDI对象例如 画笔,画刷,和字体等在设备环境中以便被GDI函数使用。它返回它替换的那个对象的句柄,我们应该保存这个句柄以便在将来SelectObjict调用。在selectObject函数调用之后,任何文本输出函数都将使用我们刚刚选进设备环境中的这个字体。
RGB 200,200,50
invoke SetTextColor,hdc,eax
RGB 0,0,255
invoke SetBkColor,hdc,eax
Use RGB macro to create a 32-bit RGB value to be used by SetColorText and SetBkColor.
用RGB宏创建的32位RGB值被用于SetColorText和SetBkColor函数中。
invoke TextOut,hdc,0,0,ADDR TestString,SIZEOF TestString
Call TextOut function to draw the text on the client area. The text will be in the font and color we specified previously.
调用TextOut函数在客户区中画一个文本。 这个文本将使用我先前指定的字体和颜色。
invoke SelectObject,hdc, hfont
When we are through with the font, we should restore the old font back into the device context. You should always restore the object that you replaced in the device context.
当我们不使用这个字体的时候,我们应该在设备环境中恢复它以前的字体。你只要在设备环境中替换了某一对象,你都应该恢复它。
________________________________________
This article come from Iczelion's asm page
风向改变 翻译于 12.9日
Tutorial 6: Keyboard Input
第六课:键盘输入
________________________________________
We will learn how a Windows program receives keyboard input.
我们将学习windows程序是如何接收键盘输入的。
Download the example here.
Theory:
Since normally there's only one keyboard in each PC, all running Windows programs must share it between them. Windows is responsible for sending the key strokes to the window which has the input focus.
Although there may be several windows on the screen, only one of them has the input focus. The window which has input focus is the only one which can receive key strokes. You can differentiate the window which has input focus from other windows by looking at the title bar. The title bar of the window which has input focus is highlighted.
Actually, there are two main types of keyboard messages, depending on your view of the keyboard. You can view a keyboard as a collection of keys. In this case, if you press a key, Windows sends a WM_KEYDOWN message to the window which has input focus, notifying that a key is pressed. When you release the key, Windows sends a WM_KEYUP message. You treat a key as a button.
Another way to look at the keyboard is that it's a character input device. When you press "a" key, Windows sends a WM_CHAR message to the window which has input focus, telling it that the user sends "a" character to it. In fact, Windows sends WM_KEYDOWN and WM_KEYUP messages to the window which has input focus and those messages will be translated to WM_CHAR messages by TranslateMessage call. The window procedure may decide to process all three messages or only the messages it's interested in. Most of the time, you can ignore WM_KEYDOWN and WM_KEYUP since TranslateMessage function call in the message loop translate WM_KEYDOWN and WM_KEYUP messages to WM_CHAR messages. We will focus on WM_CHAR in this tutorial.
Example:
例子:
.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
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib
.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
char WPARAM 20h ; the character the program receives from keyboard
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd: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
push hInst
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
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,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:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_CHAR
push wParam
pop char
invoke InvalidateRect, hWnd,NULL,TRUE
.ELSEIF uMsg==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
invoke TextOut,hdc,0,0,ADDR char,1
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
Analysis:
分析:
char WPARAM 20h ; the character the program receives from keyboard
这个程序从键盘接收的字符.
This is the variable that will store the character received from the keyboard. Since the character is sent in WPARAM of the window procedure, we define the variable as type WPARAM for simplicity. The initial value is 20h or the space since when our window refreshes its client area the first time, there is no character input. So we want to display space instead.
这个变量将存储那个从键盘接收来的字符.这个字符是通过WPARAM传给窗口过程的.我们把这个变量定义为WPARAM类型只是为了简单性.我们用20H或者是空格符的值来初始化这个变量. 因为我们的窗口第一次更新窗口客户区的时候是没有键盘输入的.所以我们应该用显示一个空白符来代替其它.
.ELSEIF uMsg==WM_CHAR
push wParam
pop char
invoke InvalidateRect, hWnd,NULL,TRUE
This is added in the window procedure to handle the WM_CHAR message. It just puts the character into the variable named "char" and then calls InvalidateRect. InvalidateRect makes the specified rectangle in the client area invalid which forces Windows to send WM_PAINT message to the window procedure. Its syntax is as follows:
这是添加在窗口程序中用来处理WM_CHAR消息的.它仅是把一个字符放进变量名为 char的变量中然后调用InvalidateRect函数.InvalidateRect函数使在客户区中的一块矩形区域无效(指定一无效区域) 以便强制windows系统发送一个WM_PAINT消息给窗口处理程序.它的句法如下:
InvalidateRect proto hWnd:HWND,\
lpRect:DWORD,\
bErase:DWORD
lpRect is a pointer to the rectagle in the client area that we want to declare invalid. If this parameter is null, the entire client area will be marked as invalid.
LpRect是一个指向那个在客户区中我们希望它无效的矩形(即无效区域)的指针.如果这个参数为空(NULL) 那么它将使整个窗口的客户区都成为无效区域.
bErase is a flag telling Windows if it needs to erase the background. If this flag is TRUE, then Windows will erase the backgroud of the invalid rectangle when BeginPaint is called.
bErase是一个标志,它告诉windows是否需要擦除背景.如果这个标志是TRUE(真) ,那么当BeginPaint函数被调用的时候,windows系统将擦除无效区域的背景.
So the strategy we used here is that: we store all necessary information relating to painting the client area and generate WM_PAINT message to paint the client area. Of course, the codes in WM_PAINT section must know beforehand what's expected of them. This seems a roundabout way of doing things but it's the way of Windows.
因此,我们在这里用的策略是: 我们将保存所有关于重绘客户区的数据,然后发送WM_PAINT消息,随后处理WM_PAINT消息的程序段将根据相关的数据来重绘客户区。这样看起来像是绕了很大的弯子来做这样一件事 ,但是它就是windows的方式。
Actually we can paint the client area during processing WM_CHAR message by calling GetDC and ReleaseDC pair. There is no problem there. But the fun begins when our window needs to repaint its client area. Since the codes that paint the character are in WM_CHAR section, the window procedure will not be able to repaint our character in the client area. So the bottom line is: put all necessary data and codes that do painting in WM_PAINT. You can send WM_PAINT message from anywhere in your code anytime you want to repaint the client area.
实际上我们可以通过在处理WM_CHAR消息的时候调用GetDC和ReleadeDC这对API函数来绘制客户区。这是没有任何问题的。但是这个函数在我们的窗口需要重绘它的客户区时才开始执行。因为绘制字符的代码是在WM_PAINT代码段中, 这样窗口过程将不能在客户区中重绘我们的字符。所以下面这一行是:把所有绘画的必需数据放在WM_PAINT中,这样,无论你想在什么时间重绘客户区,你都可以从源代码的任何地方发送WM_PAINT消息来重绘它。
invoke TextOut,hdc,0,0,ADDR char,1
When InvalidateRect is called, it sends a WM_PAINT message back to the window procedure. So the codes in WM_PAINT section is called. It calls BeginPaint as usual to get the handle to device context and then call TextOut which draws our character in the client area at x=0, y=0. When you run the program and press any key, you will see that character echo in the upper left corner of the client window. And when the window is minimized and maximized again, the character is still there since all the codes and data essential to repaint are all gathered in WM_PAINT section.
当InvalidateRect 函数被调用时,它发送一个WM_PAINT消息给窗口过程函数,所以在WM_PAINT代码段中的代码将被调用.它照常的调用BeginPaint函数来得到设备环境句柄然后调用TextOut函数在客户区中横纵坐标为0的地方为 开始画我们的字符.当你运行这个程序的时候或者是按了任何健盘上的按键,你将在窗口客户区的左上角看到这些字符.或者当你的窗口最小化后又再一次最大化时,这些字符仍然在那.因为所有重绘窗口客户区的代码和数据都在处理WM_PAINT消息的代码段中.
________________________________________
This article come from Iczelion's asm page
风向改变 翻译于 2007年 12月10日
Tutorial 7: Mouse Input
________________________________________
We will learn how to receive and respond to mouse input in our window procedure. The example program will wait for left mouse clicks and display a text string at the exact clicked spot in the client area.
我们将要学习如何在我们的窗口程序中接收和响应鼠标输入。示例程序将等待鼠标单击事件并在客户区中鼠标点击的地方显示一个字符串。
Download the example here.
Theory:
原理:
As with keyboard input, Windows detects and sends notifications about mouse activities that are relevant to each window. Those activities include left and right clicks, mouse cursor movement over window, double clicks. Unlike keyboard input which is directed to the window that has input focus, mouse messages are sent to any window that the mouse cursor is over, active or not. In addition, there are mouse messages about the non-client area too. But most of the time, we can blissfully ignore them. We can focus on those relating to the client area.
和键盘输入一样,windows检测并发送鼠标活动消息给与之相关联的窗口.这些活动包括单击鼠标左右键,鼠标光标在窗口上移动或双击鼠标. 鼠标输入并不像键盘输入那样把注意力集中在窗口的输入焦点上.任何鼠标光标经过的窗口都将接收到鼠标消息.而不管它是不是在窗口上有活动.另外,这些鼠标消息对于非客户区也是一样,( WM_NCMOVE) 但是大多时候我们充满喜悦的忽略了它们 .( 这些非客户区消息) ,这样我们能把焦点放在涉及客户区的输入上.
There are two messages for each mouse button:
每一个鼠标按钮都有两个消息:
WM_LBUTTONDOWN,WM_RBUTTONDOWN and WM_LBUTTONUP, WM_RBUTTONUP messages.
For a mouse with three buttons, there are also WM_MBUTTONDOWN and WM_MBUTTONUP. When the mouse cursor moves over the client area, Windows sends WM_MOUSEMOVE messages to the window under the cursor.
单击鼠标左键,单击鼠标右键和释放鼠标左键,释放鼠标右键消息.对于有三个按钮的鼠标,还有WM_MBUTTONDOWN和WM_MBUTTONUP消息.当鼠标光标在客户区上移动的时候,windows发送WM_MOUSEMOVE消息给在光标下的那个窗口.
A window can receive double click messages, WM_LBUTTONDBCLK or WM_RBUTTONDBCLK, if and only if its window class has CS_DBLCLKS style flag, else the window will receive only a series of mouse button up and down messages.
一个窗口可以接收鼠标双击消息, WM_LBUTTONDBCLK (左键双击事件消息) 或是WM_RBUTTONDBCLK(右键双击事件消息),如果一个窗口想接收这两个消息,那么它的窗口类必须设置CS_DBLCLKS式样。,否则这个窗口将接收到一连串的鼠标按下和释放的消息。
For all these messages, the value of lParam contains the position of the mouse. The low word is the x-coordinate, and the high word is the y-coordinate relative to upper left corner of the client area of the window. wParam indicates the state of the mouse buttons and Shift and Ctrl keys.
在所有的这些窗口中,Lparam包含鼠标的位置.低位字是X坐标,高位字是y坐标,这两个坐标值是相对于关联窗口的客户区左上角而言的.wParam用于指明鼠标按钮shift键和ctrl键的状态.
Example:
.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
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib
.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
MouseClick db 0 ; 0=no click yet
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hitpoint POINT <>
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd: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
push hInst
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
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,SW_SHOWNORMAL
invoke UpdateWindow, hwnd
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_LBUTTONDOWN
mov eax,lParam
and eax,0FFFFh
mov hitpoint.x,eax
mov eax,lParam
shr eax,16
mov hitpoint.y,eax
mov MouseClick,TRUE
invoke InvalidateRect,hWnd,NULL,TRUE
.ELSEIF uMsg==WM_PAINT
invoke BeginPaint,hWnd, ADDR ps
mov hdc,eax
.IF MouseClick
invoke lstrlen,ADDR AppName
invoke TextOut,hdc,hitpoint.x,hitpoint.y,ADDR AppName,eax
.ENDIF
invoke EndPaint,hWnd, ADDR ps
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
Analysis:
.ELSEIF uMsg==WM_LBUTTONDOWN
mov eax,lParam
and eax,0FFFFh
mov hitpoint.x,eax
mov eax,lParam
shr eax,16
mov hitpoint.y,eax
mov MouseClick,TRUE
invoke InvalidateRect,hWnd,NULL,TRUE
The window procedure waits for left mouse button click. When it receives WM_LBUTTONDOWN, lParam contains the coordinate of the mouse cursor in the client area. It saves the coordinate in a variable of type POINT which is defined as:
窗口程序等待鼠标左键单击事件.当它接收到WM_LBUTTONDOWM消息的时候,lparam参数中包含了在客户区中的鼠标光标的坐标值.这个值被保存在一个POINT类型的坐标变量结构中,这个结构定义如下:
POINT STRUCT
x dd ?
y dd ?
POINT ENDS
and sets the flag, MouseClick, to TRUE, meaning that there's at least a left mouse button click in the client area.
然后设置标志量MouseClick为TRUE,意思是在这个客户区中至少有一个鼠标左键单击事件发生.
mov eax,lParam
and eax,0FFFFh
mov hitpoint.x,eax
Since x-coordinate is the low word of lParam and the members of POINT structure are 32-bit in size, we have to zero out the high word of eax prior to storing it in hitpoint.x.
因为x坐标值规定存放在lparam的低字节(16位)中而我们的存放在point结构变量中的坐标值是32位的 ,所以我们必须用0填充eax的高字节(16位)并把它存贮在hitpoint.x变量中.
shr eax,16
mov hitpoint.y,eax
Because y-coordinate is the high word of lParam, we must put it in the low word of eax prior to storing it in hitpoint.y. We do this by shifting eax 16 bits to the right.
因为y坐标是在lparam的高字节中,我们通过在eax中右移16位来把eax中高位字存储在hitpoint.y中.
After storing the mouse position, we set the flag, MouseClick, to TRUE in order to let the painting code in WM_PAINT section know that there's at least a click in the client area so it can draw the string at the mouse position. Next we call InvalidateRect function to force the window to repaint its entire client area.
在储存了鼠标的位置后,我们设置mouseclick标志为TRUE是为了让在WM_PAINT消息中的绘画代码知道在客户区中至少有一个单击事件发生以便让它能将字符串绘制在鼠标点击的位置.下一步,我们将调用InvalidateRect函数来强制windows重绘它的整个客户区.
.IF MouseClick
invoke lstrlen,ADDR AppName
invoke TextOut,hdc,hitpoint.x,hitpoint.y,ADDR AppName,eax
.ENDIF
The painting code in WM_PAINT section must check if MouseClick is true, since when the window was created, it received a WM_PAINT message which at that time, no mouse click had occurred so it should not draw the string in the client area. We initialize MouseClick to FALSE and change its value to TRUE when an actual mouse click occurs.
If at least one mouse click has occurred, it draws the string in the client area at the mouse position. Note that it calls lstrlen to get the length of the string to display and sends the length as the last parameter of TextOut function.
在WM_PAINT节区的绘画代码中必须检查mouseclick是否为真.因为当窗口被创建时,它在那时接收一个WM_PAINT消息,如果没有鼠标单击事件出现,那么它就不会在客户区中绘制这个字符串.我们初始化mouseclick标志为FALSE并且当实际的鼠标单击事件发生时将它的值改为TRUE.
如果有一个鼠标单击事件发生,它将在客户区中鼠标光标的位置处绘制这个字符串.注意,它调用lstrlen函数来获得需要显示的字串长度并传递这个长度值给Textout函数的最后一个参数.
________________________________________
This article come from Iczelion's asm page
风向改变 翻译于 12.13日 晚
Tutorial 8: Menu
第八课:菜单
________________________________________
In this tutorial, we will learn how to incorporate a menu into our window.
Download the example 1 and example 2.
在这一课中,我们将要学习如何将一个菜单加到我们的窗口中。
Theory:
原理:
Menu is one of the most important components in your window. Menu presents a list of services a program offers to the user. The user doesn't have to read the manual included with the program to be able to use it, he can peruse the menu to get an overview of the capability of a particular program and start playing with it immediately. Since a menu is a tool to get the user up and running quickly, you should follow the standard. Succinctly put, the first two menu items should be File and Edit and the last should be Help. You can insert your own menu items between Edit and Help. If a menu item invokes a dialog box, you should append an ellipsis (...) to the menu string.
菜单在你的窗口中是最重要的一部分。当前菜单将一系列服务程序提供给用户使用。用户没有必要去阅读程序手册来了解如何使用这个应用程序,他只要细读一下所有的菜单项就能够对这个特殊的应用程序的功能有一个全面的了解并且可以立即开始使用它。因为菜单给用户程序提供了方便快捷的运行方式。所以当你在你的应用程序中加入菜单时应该遵循一般的标准。最简便的放置方式是:一开始的那两个菜单项应该是“文件”和“编辑”而最后一个是“帮助”。你能在“编辑”和“帮助”之间插入你自己的菜单项。 如果一个菜单项调用一个对话框,你应该为这个菜单项的名字追加一个省略号(。。。 )
Menu is a kind of resource. There are several kinds of resources such as dialog box, string table, icon, bitmap, menu etc. Resources are described in a separated file called a resource file which normally has .rc extension. You then combine the resources with the source code during the link stage. The final product is an executable file which contains both instructions and resources.
菜单是一类资源。 这里有几种类型的资源例如:对话框,字符串表,图表,位图,菜单等等。资源通常被说明在一个分离的文件中,被调用的资源文件通常以.rc为后缀。你可以在连接程序的时候将这些资源和程序源代码组合在一起。
最后的产物是一个含有指令和资源的可执行文件。
You can write resource scripts using any text editor. They're composed of phrases which describe the appearances and other attributes of the resources used in a particular program Although you can write resource scripts with a text editor, it's rather cumbersome. A better alternative is to use a resource editor which lets you visually design resources with ease. Resource editors are usually included in compiler packages such as Visual C++, Borland C++, etc.
你能用任何文本编辑软件来写资源脚本。 它们是用来描述在某特定程序中的资源的外部特征和属性的短语组合。 虽然你能在文本编辑中写资源脚本,但它是相当麻烦的。 一个更好的方案是用资源编辑器,它容易让你设计出一个可视的资源。 资源编辑器通常被包含在编译程序包之中,如:visual c++ ,Borland C ++ 等。
You describe a menu resource like this:
你应该像这样来描述一个菜单资源:
MyMenu MENU
{
[menu list here]
}
C programmers may recognize that it is similar to declaring a structure. MyMenu being a menu name followed by MENU keyword and menu list within curly brackets. Alternatively, you can use BEGIN and END instead of the curly brackets if you wish. This syntax is more palatable to Pascal programmers.
C程序设计员可以认为它和声明一个结构有相似之处。MyMenu是菜单资源的名字,跟随在MENU关键字后面波形括号中的是菜单项。作为选择,如果你想,你还可以用BEGIN和END来替代这个波形括号。这种句法对PASCAL的程序员来说更称心。
Menu list can be either MENUITEM or POPUP statement.
菜单列表中的菜单项可以用MENUITEM或 POPUP语句来声明。
MENUITEM statement defines a menu bar which doesn't invoke a popup menu when selected.The syntax is as follows:
MENUITEM "&text", ID [,options]
MENUITEM语句定义一个菜单项,当它被选择时并不调用弹出式菜单。 句法如下:
MENUITEM "&text", ID [,options]
It begins by MENUITEM keyword followed by the text you want to use as menu bar string. Note the ampersand. It causes the character that follows it to be underlined. Following the text string is the ID of the menu item. The ID is a number that will be used to identify the menu item in the message sent to the window procedure when the menu item is selected. As such, each menu ID must be unique among themselves.
Options are optional. Available options are as follows:
Options是可选项,可用的选项如下所示:
GRAYED The menu item is inactive, and it does not generate a WM_COMMAND message. The text is grayed.
GRAYED 表该菜单项处于非激活状态, 即当其被选中时不会产生 WM_COMMAND消息。该菜单项显示为灰色.
• INACTIVE The menu item is inactive, and it does not generate a WM_COMMAND message. The text is displayed normally.
INACTIVE 表该菜单项处于非激活状态, 即当其被选中时不会产生 WM_COMMAND消息。该菜单以正常颜色显示
• MENUBREAK This item and the following items appear on a new line of the menu.
MENUBREAK 这个菜单项和以后的菜单项列到新的一列中
• HELP This item and the following items are right-justified.
这个菜单项和以后的菜单项右对齐.
You can use one of the above option or combine them with "or" operator. Beware that INACTIVE and GRAYED cannot be combined together.
POPUP statement has the following syntax:
弹出式的菜单句法如下:
POPUP "&text" [,options]
{
[menu list]
}
POPUP statement defines a menu bar that, when selected, drops down a list of menu items in a small popup window. The menu list can be a MENUITEM or POPUP statement. There's a special kind of MENUITEM statement, MENUITEM SEPARATOR, which will draw a horizontal line in the popup window.
POPUP定义了这样一个菜单项,当它被选择时,弹出一个小窗口这个小窗口中包含一系列菜单项.这些菜单项能被MENUITEM 或 POPUP声明,这里有一种特别的MENUITEM语句,MENUITEM SEPARATOR ,它将在弹出式窗口中画一条水平线.
The next step after you are finished with the menu resource script is to reference it in your program.
在你完成菜单资源脚本的定义后,下一步的工作就是在程序中引用它.
You can do this in two different places in your program.
你能用如下方式在程序中的两个不同的地方使用它:
• In lpszMenuName member of WNDCLASSEX structure. Say, if you have a menu named "FirstMenu", you can assigned the menu to your window like this:
在WNDCLASSEX结构成员变量lpszMenuName中。看,如果你有一个菜单的名字是“FirstMenu“你能像这样把这个菜单分配在你的窗口中:
.DATA
MenuName db "FirstMenu",0
...........................
...........................
.CODE
...........................
mov wc.lpszMenuName, OFFSET MenuName
...........................
• In menu handle parameter of CreateWindowEx like this:
在作为CreateWindowEx函数的参数的菜单句柄中像这样使用:
.DATA
MenuName db "FirstMenu",0
hMenu HMENU ?
...........................
...........................
.CODE
...........................
invoke LoadMenu, hInst, OFFSET MenuName
mov hMenu, eax
invoke CreateWindowEx,NULL,OFFSET ClsName,\
OFFSET Caption, WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,\
NULL,\
hMenu,\
hInst,\
NULL\
...........................
So you may ask, what's the difference between these two methods?
When you reference the menu in the WNDCLASSEX structure, the menu becomes the "default" menu for the window class. Every window of that class will have the same menu.
如此,你可能会问,为什么要定义两种不同的引用方法?
当你在WNDCLASSEX结构中引用这个菜单时,这个菜单就变成窗口类的默认菜单。
每一个基于这个类创建的窗口都将有着相同的菜单。
If you want each window created from the same class to have different menus, you must choose the second form. In this case, any window that is passed a menu handle in its CreateWindowEx function will have a menu that "overrides" the default menu defined in the WNDCLASSEX structure.
如果你想在基于同于一个窗口类创建的不同窗口中拥有不同的菜单,你必须用第二种形式。 既然这样,任何窗口只要传递一个菜单句柄
给CreateWindowEx函数,它将忽略那个在WNDCLASSEX结构中定义的默认菜单。
Next we will examine how a menu notifies the window procedure when the user selects a menu item.
When the user selects a menu item, the window procedure will receive a WM_COMMAND message. The low word of wParam contains the menu ID of the selected menu item.
Now we have sufficient information to create and use a menu. Let's do it.
现在,我们已经有充分的信息来创建和使用一个菜单,让我们试一试。
Example:
例子:
The first example shows how to create and use a menu by specifying the menu name in the window class.
第一个例子将显示通过在窗口类中指定一菜单名的方式来创建和使用这个菜单。
.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
.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
MenuName db "FirstMenu",0 ; The name of our menu in the resource file.
Test_string db "You selected Test menu item",0
Hello_string db "Hello, my friend",0
Goodbye_string db "See you again, bye",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.const
IDM_TEST equ 1 ; Menu IDs
IDM_HELLO equ 2
IDM_GOODBYE equ 3
IDM_EXIT equ 4
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd: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
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName ; Put our menu name here
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,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,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 DispatchMessage, ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF ax==IDM_TEST
invoke MessageBox,NULL,ADDR Test_string,OFFSET AppName,MB_OK
.ELSEIF ax==IDM_HELLO
invoke MessageBox, NULL,ADDR Hello_string, OFFSET AppName,MB_OK
.ELSEIF ax==IDM_GOODBYE
invoke MessageBox,NULL,ADDR Goodbye_string, OFFSET AppName, MB_OK
.ELSE
invoke DestroyWindow,hWnd
.ENDIF
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
**************************************************************************************************************************
Menu.rc
**************************************************************************************************************************
#define IDM_TEST 1
#define IDM_HELLO 2
#define IDM_GOODBYE 3
#define IDM_EXIT 4
FirstMenu MENU
{
POPUP "&PopUp"
{
MENUITEM "&Say Hello",IDM_HELLO
MENUITEM "Say &GoodBye", IDM_GOODBYE
MENUITEM SEPARATOR
MENUITEM "E&xit",IDM_EXIT
}
MENUITEM "&Test", IDM_TEST
}
Analysis:
分析:
Let's analyze the resource file first.
首先,让我们分析一下资源文件.
#define IDM_TEST 1 /* equal to IDM_TEST equ 1*/
#define IDM_HELLO 2
#define IDM_GOODBYE 3
#define IDM_EXIT 4
The above lines define the menu IDs used by the menu script. You can assign any value to the ID as long as the value is unique in the menu.
上面这些行用来声明在菜单资源脚本中使用的ID值。你能给菜单项的ID分配任何值只要这些值在菜单中是唯一的。
FirstMenu MENU
Declare your menu with MENU keyword.
用MENU关键字声明你的菜单。
POPUP "&PopUp"
{
MENUITEM "&Say Hello",IDM_HELLO
MENUITEM "Say &GoodBye", IDM_GOODBYE
MENUITEM SEPARATOR
MENUITEM "E&xit",IDM_EXIT
}
Define a popup menu with four menu items, the third one is a menu separator.
定义了一个有四个菜单项的弹出式菜单,第三个菜单是分隔符。
MENUITEM "&Test", IDM_TEST
Define a menu bar in the main menu.
Next we will examine the source code.
定义一个菜单项在主菜单中。
下面我们将分析源代码。
MenuName db "FirstMenu",0 ; The name of our menu in the resource file.
Test_string db "You selected Test menu item",0
Hello_string db "Hello, my friend",0
Goodbye_string db "See you again, bye",0
MenuName is the name of the menu in the resource file. Note that you can define more than one menu in the resource file so you must specify which menu you want to use. The remaining three lines define the text strings to be displayed in message boxes that are invoked when the appropriate menu item is selected by the user.
Define menu IDs for use in the window procedure. These values MUST be identical to those defined in the resource file.
定义在窗口过程中使用的菜单项ID值。这些值必须同定义在资源文件中的值一样。
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF ax==IDM_TEST
invoke MessageBox,NULL,ADDR Test_string,OFFSET AppName,MB_OK
.ELSEIF ax==IDM_HELLO
invoke MessageBox, NULL,ADDR Hello_string, OFFSET AppName,MB_OK
.ELSEIF ax==IDM_GOODBYE
invoke MessageBox,NULL,ADDR Goodbye_string, OFFSET AppName, MB_OK
.ELSE
invoke DestroyWindow,hWnd
.ENDIF
In the window procedure, we process WM_COMMAND messages. When the user selects a menu item, the menu ID of that menu item is sended to the window procedure in the low word of wParam along with the WM_COMMAND message. So when we store the value of wParam in eax, we compare the value in ax to the menu IDs we defined previously and act accordingly. In the first three cases, when the user selects Test, Say Hello, and Say GoodBye menu items, we just display a text string in a message box.
If the user selects Exit menu item, we call DestroyWindow with the handle of our window as its parameter which will close our window.
As you can see, specifying menu name in a window class is quite easy and straightforward. However you can also use an alternate method to load a menu in your window. I won't show the entire source code here. The resource file is the same in both methods. There are some minor changes in the source file which I 'll show below.
我们在窗口过程中处理WM_COMMAND消息。当用户选择一个菜单项时,这个菜单项的ID被存贮在WM_COMMAND消息的低位字wParam中并传递给窗口过程 。所以我们用eax存储这个参数。(wparam)我们用在ax中的值来和我们以前定义的ID值比较并且回应它。(消息处理代码)在第一个例子中, 当用户选择了test,say hello 和 say goodbye 菜单项时。我们仅在消息框中显示一字符。
如果用户选择的是exit菜单项,我们将用我们的窗口句柄作为DestroyWindow 的参数并调用这个函数来关闭我们的窗口。
如你所见,在窗口类中指定一个菜单名不仅是非常简单的而且更为直接。然而,你还能用另外一种方法来在你的窗口中装载一个菜单。我不想在这里展示所有的源代码。两种方法的资源文件是一样的,它们只是在源文件中有较小的一点改变。下面是源文件。
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hMenu HMENU ? ; handle of our menu
Define a variable of type HMENU to store our menu handle.
定义一个句柄变量用来存储我们的菜单句柄。
invoke LoadMenu, hInst, OFFSET MenuName
mov hMenu,eax
INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,hMenu,\
hInst,NULL
Before calling CreateWindowEx, we call LoadMenu with the instance handle and a pointer to the name of our menu. LoadMenu returns the handle of our menu in the resource file which we pass to CreateWindowEx
在调用CreateWindowEx之前,我们用实例句柄和指向我们菜单名的指针作为LoadMenu的参数来调用它。loadMenu返回我们在资源文件中定义的菜单句柄并作为参数传递给CreatewindowEx 函数。
________________________________________
This article come from Iczelion's asm page,
风向改变翻译于 07年12.14日
Tutorial 9: Child Window Controls
第九课:子窗口控件
________________________________________
In this tutorial, we will explore child window controls which are very important input and output devices of our programs.
在这一课, 我们将探究子窗口控件,这些子窗口控件在我们的应用程序中是非常重要的输入输出设备。
Download the example here.
Theory:
原理:
Windows provides several predefined window classes which we can readily use in our own programs. Most of the time we use them as components of a dialog box so they're usually called child window controls. The child window controls process their own mouse and keyboard messages and notify the parent window when their states have changed. They relieve the burden from programmers enormously so you should use them as much as possible. In this tutorial, I put them on a normal window just to demonstrate how you can create and use them but in reality you should put them in a dialog box.
Examples of predefined window classes are button, listbox, checkbox, radio button,edit etc.
例子中使用的子窗口控件类是:按钮类,列表框,复选框,单选按钮,编辑框等等。
In order to use a child window control, you must create it with CreateWindow or CreateWindowEx. Note that you don't have to register the window class since it's registered for you by Windows. The class name parameter MUST be the predefined class name. Say, if you want to create a button, you must specify "button" as the class name in CreateWindowEx. The other parameters you must fill in are the parent window handle and the control ID. The control ID must be unique among the controls. The control ID is the ID of that control. You use it to differentiate between the controls.
为了使用子窗口控件,你必须在CreateWindow或者是CreateWindowEx函数中使用它。 注意:你没有必要去注册这些窗口类,因为windows已经注册过了。 作为参数的类名必须是预定义的类名。比方说,如果你想创建一个按钮,你必须在createWindowEx函数中指定“button”作为窗口类参数名。其他必须被填写的参数还有父窗口的句柄和控件ID值。控件ID值必须是唯一的。控件的ID值就是你使用的那个控件的标识,你用它来区分这些控件。
After the control was created, it will send messages notifying the parent window when its state has changed. Normally, you create the child windows during WM_CREATE message of the parent window. The child window sends WM_COMMAND messages to the parent window with its control ID in the low word of wParam, the notification code in the high word of wParam, and its window handle in lParam. Each child window control has different notification codes, refer to your Win32 API reference for more information.
The parent window can send commands to the child windows too, by calling SendMessage function. SendMessage function sends the specified message with accompanying values in wParam and lParam to the window specified by the window handle. It's an extremely useful function since it can send messages to any window provided you know its window handle.
在这些控件被创建后,当其状态改变时,会向其父窗口发送消息。通常,你在父窗口的WM_CREATE消息中创建这些子控件窗口。 子控件发送WM_COMMAND消息给父窗口 , wParam参数的低字节是这个子窗口控件的ID值,wParam参数高字节是通知码,它的窗口句柄则放在lParam参数中。每一个子窗口控件都有不同的通知码,查阅你的Win32API手册可以获得更多的信息。父窗口也能发送命令给子窗口控件,但需要调用SendMessage函数。SendMessage函数发送一个指定的消息给父窗口,消息的附加值在wParam中并在lParam参数中指定子控件窗口句柄。这是一个非常有用的函数,因为你可以用它发送任何消息给任何窗口。只要你知道接收消息的窗口句柄。
So, after creating the child windows, the parent window must process WM_COMMAND messages to be able to receive notification codes from the child windows.
所以,在创建了子窗口后,父窗口必须处理WM_COMMAND消息才能从子窗口中接收到通知码。
Example:
We will create a window which contains an edit control and a pushbutton. When you click the button, a message box will appear showing the text you typed in the edit box. There is also a menu with 4 menu items:
我们将创建一个窗口,这个窗口包含一个编辑框和一个按钮。当你点击这个按钮时,一个消息框将出现并显示你在编辑框中输入的文本。这个窗口还有一个菜单,其中包含四个菜单项:
1. Say Hello -- Put a text string into the edit box
放置一个字符串在编辑框中
2. Clear Edit Box -- Clear the content of the edit box
清除编辑框中的内容
3. Get Text -- Display a message box with the text in the edit box
弹出一个消息框并显示编辑框中的文本
4. Exit -- Close the program.
退出程序。
.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
.data
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
MenuName db "FirstMenu",0
ButtonClassName db "button",0
ButtonText db "My First Button",0
EditClassName db "edit",0
TestString db "Wow! I'm in an edit box now",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwndButton HWND ?
hwndEdit HWND ?
buffer db 512 dup(?) ; buffer to store the text retrieved from the edit box
.const
ButtonID equ 1 ; The control ID of the button control
EditID equ 2 ; The control ID of the edit control
IDM_HELLO equ 1
IDM_CLEAR equ 2
IDM_GETTEXT equ 3
IDM_EXIT equ 4
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd: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
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_BTNFACE+1
mov wc.lpszMenuName,OFFSET MenuName
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,WS_EX_CLIENTEDGE,ADDR ClassName, \
ADDR AppName, WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT, CW_USEDEFAULT,\
300,200,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:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_CREATE
invoke CreateWindowEx,WS_EX_CLIENTEDGE, ADDR EditClassName,NULL,\
WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT or\
ES_AUTOHSCROLL,\
50,35,200,25,hWnd,8,hInstance,NULL
mov hwndEdit,eax
invoke SetFocus, hwndEdit
invoke CreateWindowEx,NULL, ADDR ButtonClassName,ADDR ButtonText,\
WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\
75,70,140,25,hWnd,ButtonID,hInstance,NULL
mov hwndButton,eax
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
.IF ax==IDM_HELLO
invoke SetWindowText,hwndEdit,ADDR TestString
.ELSEIF ax==IDM_CLEAR
invoke SetWindowText,hwndEdit,NULL
.ELSEIF ax==IDM_GETTEXT
invoke GetWindowText,hwndEdit,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
.ELSE
invoke DestroyWindow,hWnd
.ENDIF
.ELSE
.IF ax==ButtonID
shr eax,16
.IF ax==BN_CLICKED
invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0
.ENDIF
.ENDIF
.ENDIF
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
Analysis:
Let's analyze the program.
.ELSEIF uMsg==WM_CREATE
invoke CreateWindowEx,WS_EX_CLIENTEDGE, \
ADDR EditClassName,NULL,\
WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT\
or ES_AUTOHSCROLL,\
50,35,200,25,hWnd,EditID,hInstance,NULL
mov hwndEdit,eax
invoke SetFocus, hwndEdit
invoke CreateWindowEx,NULL, ADDR ButtonClassName,\
ADDR ButtonText,\
WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON,\
75,70,140,25,hWnd,ButtonID,hInstance,NULL
mov hwndButton,eax
We create the controls during processing of WM_CREATE message. We call CreateWindowEx with an extra window style, WS_EX_CLIENTEDGE, which makes the client area look sunken. The name of each control is a predefined one, "edit" for edit control, "button" for button control. Next we specify the child window's styles. Each control has extra styles in addition to the normal window styles. For example, the button styles are prefixed with "BS_" for "button style", edit styles are prefixed with "ES_" for "edit style". You have to look these styles up in a Win32 API reference. Note that you put a control ID in place of the menu handle. This doesn't cause any harm since a child window control cannot have a menu.
After creating each control, we keep its handle in a variable for future use.
我们在处理WM_CREATE消息的时候创建这些控件。我们调用CreateWindowEx函数并使用了一个扩展窗口样式,WS_EX_CLIENTEDGE,它使得客户区看起来是凹陷的。每一个控件的名字都是预定义的,“edit”是编辑框控件, “button”是按钮控件。随后我们将指定这个子窗口的样式。除了窗口样式外每一个子窗口控件都有一个扩展样式。例如:按钮类用“BS_"作为按钮样式的前缀。编辑框用
“ES_”作为编辑框样式的前缀。你可以在win32api手册中查看这些样式。注意,你应该把控件ID放在菜单句柄的位置。这并不会引起任何损伤因为一个子窗口控件不能拥有任何菜单。
在创建了一个控件后,我们将它的句柄存储在一变量中以便我们将来使用。
SetFocus is called to give input focus to the edit box so the user can type the text into it immediately.
Now comes the really exciting part. Every child window control sends notification to its parent window with WM_COMMAND.
SetFocus函数被调用后给编辑框一个输入焦点所以用户可以立即在它里面键入文本。
现在来到让我们真正兴奋的部分。每一个子窗口控件发送WM_COMMAND消息来通知它的父窗口。
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.IF lParam==0
Recall that a menu also sends WM_COMMAND messages to notify the window about its state too. How can you differentiate between WM_COMMAND messages originated from a menu or a control? Below is the answer
回想一下,我们说过,当一个菜单的状态改变时,它也会发送一个WM_CMMAND消息来通知窗口。 那么我们如何区分一个WM_COMMAND消息是来源于一个菜单还是一个控件呢?答案请看下面:
Low word of wParam High word of wParam lParam
Menu Menu ID 0 0
Control Control ID Notification code Child Window Handle
You can see that you should check lParam. If it's zero, the current WM_COMMAND message is from a menu. You cannot use wParam to differentiate between a menu and a control since the menu ID and control ID may be identical and the notification code may be zero.
你能从这个表看出,你应该检测lparam参数来区分消息(wmcommand)的来源。如果它的值是0,当前的WM_COMMAND消息来自于菜单。你不能用wparam参数来区分这个消息是来源于菜单还是控件。因为菜单的ID值和控件ID值可能相同并且通知码也可能都是0。
.IF ax==IDM_HELLO
invoke SetWindowText,hwndEdit,ADDR TestString
.ELSEIF ax==IDM_CLEAR
invoke SetWindowText,hwndEdit,NULL
.ELSEIF ax==IDM_GETTEXT
invoke GetWindowText,hwndEdit,ADDR buffer,512
invoke MessageBox,NULL,ADDR buffer,ADDR AppName,MB_OK
You can put a text string into an edit box by calling SetWindowText. You clear the content of an edit box by calling SetWindowText with NULL. SetWindowText is a general purpose API function. You can use SetWindowText to change the caption of a window or the text on a button.
To get the text in an edit box, you use GetWindowText.
你能调用SetWindowText函数来放置一文本在一个编辑框中。 你也可以在SetWindowText函数中传入NULL值来清除编辑框的内容。SetWindowText是一个通用的API函数。你能用SetWindowText函数来改变一个窗口的标题或者是一个按钮的文本。为了获得在编辑框中的文本,你可以用GetWindowText函数。
.IF ax==ButtonID
shr eax,16
.IF ax==BN_CLICKED
invoke SendMessage,hWnd,WM_COMMAND,IDM_GETTEXT,0
.ENDIF
.ENDIF
The above code snippet deals with the condition when the user presses the button. First, it checks the low word of wParam to see if the control ID matches that of the button. If it is, it checks the high word of wParam to see if it is the notification code BN_CLICKED which is sent when the button is clicked.
上面这个代码片断执行的条件是当用户按这个按钮时。首先,它检查wParam的低字节来看这个控件的ID是否与那个按钮相匹配。如果是,它检查wParam的高字节来看它是不是当按钮被单击时发送的通知码BN_CLICKED。
The interesting part is after it's certain that the notification code is BN_CLICKED. We want to get the text from the edit box and display it in a message box. We can duplicate the code in the IDM_GETTEXT section above but it doesn't make sense. If we can somehow send a WM_COMMAND message with the low word of wParam containing the value IDM_GETTEXT to our own window procedure, we can avoid code duplication and simplify our program. SendMessage function is the answer. This function sends any message to any window with any wParam and lParam we want. So instead of duplicating the code, we call SendMessage with the parent window handle, WM_COMMAND, IDM_GETTEXT, and 0. This has identical effect to selecting "Get Text" menu item from the menu. The window procedure doesn't perceive any difference between the two.
有兴趣的部分是在它确认了那个通知码是BN_CLICKED后。我们想从编辑框获取文本并将它显示在消息框中。我们能复制上面IDM_GETTEXT节区中的代码但是它并没有什么意义。如果我们能在任何时候发送一条wparam参数低字节包含IDM_GETTEXT值的WM_COMMAND消息给我们自己窗口过程。我们就能消除代码复制并使我们的程序简化。SendMessage函数就是答案。这个函数能发送任何包含你想要的wparam 参数值 和lParam参数值的任何消息给任何窗口。所以代替复制代码的方法就是,我们传递父窗口句柄,WM_COMMAND消息,IDM_GETTEXT, 和0给 SendMessage函数并调用它。这与从菜单中选择"Get Text"菜单项有着同样的效果。窗口过程感觉不出它们之间的任何变化。 (指用SendMessage函数发送消息和在菜单中选择菜单)
You should use this technique as much as possible to make your code more organized.
你应该尽可能用这种技巧来组织你的代码。
Last but not least, do not forget the TranslateMessage function in the message loop. Since you must type in some text into the edit box, your program must translate raw keyboard input into readable text. If you omit this function, you will not be able to type anything into your edit box.
最后,不要忘记在消息循环中用TranslateMessage函数。因为你必须输入一些文本在编辑框中,所以你的程序必须将未处理的键入翻译成可读的文本。如果你忽略了这个函数,你将不能在你的编辑框中输入任何东西。
________________________________________
This article come from Iczelion's asm page,
风向改变翻译于 2007 年12.16日
Tutorial 10: Dialog Box as Main Window
第十课:作为主窗口的对话框
________________________________________
Now comes the really interesting part about GUI, the dialog box. In this tutorial (and the next), we will learn how to use a dialog box as our main window.
现在,来到在图形用户界面中真正有趣味的部分--对话框。 在这一课(和下一课)中, 我们将学习如何利用对话框来作为我们的主窗口。
Download the first example here, the second example here.
Theory:
原理:
If you play with the examples in the previous tutorial long enough, you 'll find out that you cannot change input focus from one child window control to another with Tab key. The only way you can do that is by clicking the control you want it to gain input focus. This situation is rather cumbersome. Another thing you might notice is that I changed the background color of the parent window to gray instead of normal white as in previous examples.
This is done so that the color of the child window controls can blend seamlessly with the color of the client area of the parent window. There is a way to get around this problem but it's not easy. You have to subclass all child window controls in your parent window.
The reason why such inconvenience exists is that child window controls are originally designed to work with a dialog box, not a normal window. The default color of child window controls such as a button is gray because the client area of a dialog box is normally gray so they blend into each other without any sweat on the programmer's part.
Before we get deep into the detail, we should know first what a dialog box is. A dialog box is nothing more than a normal window which is designed to work with child window controls. Windows also provides internal "dialog box manager" which is responsible for most of the keyboard logic such as shifting input focus when the user presses Tab, pressing the default pushbutton if Enter key is pressed, etc so programmers can deal with higher level tasks.
Dialog boxes are primarily used as input/output devices.As such a dialog box can be considered as an input/output "black box" meaning that you don't have to know how a dialog box works internally in order to be able to use it, you only have to know how to interact with it. That's a principle of object oriented programming (OOP) called information hiding. If the black box is *perfectly* designed, the user can make use of it without any knowledge on how it operates. The catch is that the black box must be perfect, that's hard to achieve in the real world. Win32 API is also designed as a black box too.
对话框主要用来作为输入输出设备(接口).同样的,一个对话框也可以当作一个输入输出的”黑匣子”来考虑.”黑匣子”的意思是你并不知道对话框的内部是如何工作的,你仅知道的是 如何和它交互.这是面向对象的一个原理称作信息隐藏 .如果这个黑匣子的设计是完美的,那么用户就能使用它而不需要任何关于如何操作对话框的知识.我们应该抓住”黑匣子”必须是完美的这一点,并努力去实现它.Win32 API ( 应用程序接口) 也被设计成一黑匣子.
Well, it seems we stray from our path. Let's get back to our subject. Dialog boxes are designed to reduce workload of a programmer. Normally if you put child window controls on a normal window, you have to subclass them and write keyboard logic yourself. But if you put them on a dialog box, it will handle the logic for you. You only have to know how to get the user input from the dialog box or how to send commands to it.
A dialog box is defined as a resource much the same way as a menu. You write a dialog box template describing the characteristics of the dialog box and its controls and then compile the resource script with a resource editor.
一个对话框被作为资源定义,这和菜单的定义有很多相同之处.你写一个对话框模版用来描述这个对话框的特征面( 属性) 和它拥有的控件并用资源编辑器将这些资源脚本组合在一起.
Note that all resources are put together in the same resource script file. You can use any text editor to write a dialog box template but I don't recommend it. You should use a resource editor to do the job visually since arranging child window controls on a dialog box is hard to do manually. Several excellent resource editors are available. Most of the major compiler suites include their own resource editors. You can use them to create a resource script for your program and then cut out irrelevant lines such as those related to MFC.
注意,所有的资源都放在同一个资源脚本文件中.你能用任何文本编辑器来写一个对话框模版但是我并不推荐你这样做.你应该用可视化的资源编辑器做这个工作因为用写脚本的方式在对话框上安排子窗口控件是非常难的. 这里有几个极好的资源编辑器你可能用到.大多数主流的编译器都有它们自己的资源编辑器.你能用它们为你的应用程序创建一资源脚本文件然后裁减掉不相干的行例如:那些涉及到MFC的.
There are two main types of dialog box: modal and modeless. A modeless dialog box lets you change input focus to other window. The example is the Find dialog of MS Word. There are two subtypes of modal dialog box: application modal and system modal. An application modal dialog box doesn't let you change input focus to other window in the same application but you can change the input focus to the window of OTHER application. A system modal dialog box doesn't allow you to change input focus to any other window until you respond to it first.
这里有两种类型的对话框: 模式的和非模式的.非模式的对话框能让你切换输入焦点给其他窗口.如word中的查找对话框就是非模式的.
模式对话框有两种子类型: 应用程序模式和系统模式.一个应用程序模式对话框不让你切换输入焦点给属于同一应用程序的其他窗口了,但是你能切换输入焦点给其他的应用程序窗口.一个系统模式的对话框不允许你切换输入焦点给任何其它窗口,直到你响应它为止.
A modeless dialog box is created by calling CreateDialogParam API function. A modal dialog box is created by calling DialogBoxParam. The only distinction between an application modal dialog box and a system modal one is the DS_SYSMODAL style. If you include DS_SYSMODAL style in a dialog box template, that dialog box will be a system modal one.
You can communicate with any child window control on a dialog box by using SendDlgItemMessage function. Its syntax is like this:
非模式对话框是通过调用CreateDialogParam API 函数来创建的.模式对话框是通过调用DialogBoxParam函数创建。应用程序模式对话框和系统模式对话框唯一的区别就是系统模式对话框指定DS_SYSMODAL样式.如果在一个对话框模版中包含DS_SYSMODAL样式,那么这个对话框将是系统模式的对话框.
SendDlgItemMessage proto hwndDlg:DWORD,\
idControl:DWORD,\
uMsg:DWORD,\
wParam:DWORD,\
lParam:DWORD
This API call is immensely useful for interacting with a child window control. For example, if you want to get the text from an edit control, you can do this:
call SendDlgItemMessage, hDlg, ID_EDITBOX, WM_GETTEXT, 256, ADDR text_buffer
这个API函数调用是非常有用的,因为它和一个子窗口控件相结合.例如, 如果你想从一个编辑控件中获得文本,你可以这样做:
call SendDlgItemMessage, hDlg, ID_EDITBOX, WM_GETTEXT, 256, ADDR text_buffer
In order to know which message to send, you should consult your Win32 API reference.
为了知道是那一个消息被发送,你应该参考你的Win32 API手册.
Windows also provides several control-specific API functions to get and set data quickly, for example, GetDlgItemText, CheckDlgButton etc. These control-specific functions are provided for programmer's convenience so he doesn't have to look up the meanings of wParam and lParam for each message. Normally, you should use control-specific API calls when they're available since they make source code maintenance easier. Resort to SendDlgItemMessage only if no control-specific API calls are available.
The Windows dialog box manager sends some messages to a specialized callback function called a dialog box procedure which has the following format:
Windows为了快速的获取和设置数据提供了几个特殊的控件类API函数,例如,
GetDlgItemText,CheckDlgButton,等.使用这些特殊控件类函数给程序员带来了方便从而让程序员没有必要为每一个消息去查找wParam和lParam参数的含义.
通常地, 当用到子控件的时候你应该使用这些特殊控件类函数,因为它们构成的源代码容易维护.如果某一类子窗口控件没有特殊的控件类函数可以调用则用SendDlgItemMessage 函数.
Windows对话框管理器发送一些消息给指定的回叫函数调用对话框过程.这个过程格式如下:
DlgProc proto hDlg:DWORD ,\
iMsg:DWORD ,\
wParam:DWORD ,\
lParam:DWORD
The dialog box procedure is very similar to a window procedure except for the type of return value which is TRUE/FALSE instead of LRESULT. The internal dialog box manager inside Windows IS the true window procedure for the dialog box. It calls our dialog box procedure with some messages that it received. So the general rule of thumb is that: if our dialog box procedure processes a message,it MUST return TRUE in eax and if it does not process the message, it must return FALSE in eax. Note that a dialog box procedure doesn't pass the messages it does not process to the DefWindowProc call since it's not a real window procedure.
There are two distinct uses of a dialog box. You can use it as the main window of your application or use it as an input device. We 'll examine the first approach in this tutorial.
对话框过程和窗口过程是非常相似的除了返回值的类型对话框的是TRUE或FALSE替代了窗口过程的LRESULT.在windows系统内部的对话框管理器才是对话框真正的窗口过程.它用它接受到的一些消息调用我们外部的对话框过程.所以一般的规则是: 如果我们的对话框过程处理一个消息,它就在eax中返回TRUE,如果它没处理这条消息,它必须在eax中返回FALSE .注意:一个对话框过程不传递任何消息,它也不调用DefWindowProc这样的函数来处理其他未被处理的消息.因为对话框过程并不是真的窗口过程.
一个对话框有两种截然不同的用法. 你能用它作为你的应用程序的主窗口或者用它作为输入设备.在这一课中,我们将验证第一种用法:
"Using a dialog box as main window" can be interpreted in two different senses.
“ 用对话框作为主窗口”有两种不同的认识。
1. You can use the dialog box template as a class template which you register with RegisterClassEx call. In this case, the dialog box behaves like a "normal" window: it receives messages via a window procedure referred to by lpfnWndProc member of the window class, not via a dialog box procedure. The benefit of this approach is that you don't have to create child window controls yourself, Windows creates them for you when the dialog box is created. Also Windows handles the keyboard logic for you such as Tab order etc. Plus you can specify the cursor and icon of your window in the window class structure.
2. Your program just creates the dialog box without creating any parent window. This approach makes a message loop unnecessary since the messages are sent directly to the dialog box procedure. You don't even have to register a window class!
1.你能通过调用RegisterClassEx函数把对话框模版注册为类模版。既然这样,这个对话框的窗口行为就如同一个正常的窗口。它通过窗口类的lpfnWndProc成员指定的窗口过程来处理它所接收到的消息。而不通过对话框过程。这种方法的好处是你不必要创建你自己的子窗口控件,因为在对话框被创建时由windows来为你创建它们。同样 ,windows也为你处理诸如tab 或其他一系列的键盘逻辑。另外,你还可以在窗口类结构中为你的窗口指定光标和图标。
2.你的程序仅仅创建了一个没有任何父窗口的对话框。因为消息被发送给对话框过程,所以对用这种方法创建的对话框而言,消息循环是多余的。你甚至可以不注册窗口类。
This tutorial is going to be a long one. I'll present the first approach followed by the second.
这一课将是长时间的。首先,我将为你呈现第一种方法,随后是第二种。
Examples:
例子:
________________________________________
dialog.asm
________________________________________
MyMenu MENU
BEGIN
POPUP "Test Controls"
BEGIN
MENUITEM "Get Text", IDM_GETTEXT
MENUITEM "Clear Text", IDM_CLEAR
MENUITEM "", , 0x0800 /*MFT_SEPARATOR*/
MENUITEM "E&xit", IDM_EXIT
END
END
Analysis:
分析:
Let's analyze this first example.
This example shows how to register a dialog template as a window class and create a "window" from that class. It simplifies your program since you don't have to create the child window controls yourself.
Let's first analyze the dialog template.
让我们分析第一个例子。
这个例子为我们展示了如何把一个对话框模版注册为一窗口类并基于这个类创建一个“窗口”。它简化了你的程序因为你不用自己去创建子窗口控件了。
让我们分析这个对话框模版。
MyDialog DIALOG 10, 10, 205, 60
Declare the name of a dialog, in this case, "MyDialog" followed by the keyword "DIALOG". The following four numbers are: x, y , width, and height of the dialog box in dialog box units (not the same as pixels).
声明对话框的名字,在这个例子中, “MyDialog”后跟随着关键字“DIALOG”。后面那四个数是:对话框的 x坐标,y坐标,宽,和高。它们用对话框的单位。
(注意:是对话框的单位,并不一定是像素 )
STYLE 0x0004 | DS_CENTER | WS_CAPTION | WS_MINIMIZEBOX |
WS_SYSMENU | WS_VISIBLE | WS_OVERLAPPED | DS_MODALFRAME | DS_3DLOOK
Declare the styles of the dialog box.
声明对话框的样式。
CAPTION "Our First Dialog Box"
This is the text that will appear in the dialog box's title bar.
这个文本将出现在对话框的标题栏。
CLASS "DLGCLASS"
This line is crucial. It's this CLASS keyword that allows us to use the dialog box template as a window class. Following the keyword is the name of the "window class"
这一行是至关紧要的。CLASS关键字允许我们用对话框模版作为一个窗口类.跟随其后的是这个”窗口类 ” 的名字.
BEGIN
EDITTEXT IDC_EDIT, 15,17,111,13, ES_AUTOHSCROLL | ES_LEFT
DEFPUSHBUTTON "Say Hello", IDC_BUTTON, 141,10,52,13
PUSHBUTTON "E&xit", IDC_EXIT, 141,26,52,13
END
The above block defines the child window controls in the dialog box. They're defined between BEGIN and END keywords. Generally the syntax is as follows:
上面这块代码定义在对话框中的子窗口控件.它们必须写在BEGIN和END关键字之间.一般语法如下:
control-type "text" ,controlID, x, y, width, height [,styles]
control-types are resource compiler's constants so you have to consult the manual.
因为控件类型是资源编辑器的常数值,(宏定义)所以你必须参照手册来定义它们.
Now we go to the assembly source code. The interesting part is in the window class structure:
现在我们继续组合源代码. 有趣味的部分在窗口类结构中.
mov wc.cbWndExtra,DLGWINDOWEXTRA
mov wc.lpszClassName,OFFSET ClassName
Normally, this member is left NULL, but if we want to register a dialog box template as a window class, we must set this member to the value DLGWINDOWEXTRA. Note that the name of the class must be identical to the one following the CLASS keyword in the dialog box template. The remaining members are initialized as usual. After you fill the window class structure, register it with RegisterClassEx. Seems familiar? This is the same routine you have to do in order to register a normal window class.
invoke CreateDialogParam,hInstance,ADDR DlgName,NULL,NULL,NULL
After registering the "window class", we create our dialog box. In this example, I create it as a modeless dialog box with CreateDialogParam function. This function takes 5 parameters but you only have to fill in the first two: the instance handle and the pointer to the name of the dialog box template. Note that the 2nd parameter is not a pointer to the class name.
再注册了这个”窗口类"后,我们创建我们的对话框.在这个例子中,我用CreateDialogParam函数创建一个模式对话框.这个函数有5个参数,但是你只需要填写前两个:实例句柄和一个指向对话框模版名的指针.注意:第二个参数并不是指向类名的指针.
At this point, the dialog box and its child window controls are created by Windows. Your window procedure will receive WM_CREATE message as usual.
invoke GetDlgItem,hDlg,IDC_EDIT
invoke SetFocus,eax
After the dialog box is created, I want to set the input focus to the edit control. If I put these codes in WM_CREATE section, GetDlgItem call will fail since at that time, the child window controls are not created yet. The only way you can do this is to call it after the dialog box and all its child window controls are created. So I put these two lines after the UpdateWindow call. GetDlgItem function gets the control ID and returns the associated control's window handle. This is how you can get a window handle if you know its control ID.
当对话框被创建后,我想设置输入焦点给编辑框.如果我把这些代码放在WM_CREATE节区中,GetDlgItem将失效因为在那个时候,这个子窗口控件还没有被创建.你唯一的方式就是等对话框和这个对话框的所有子控件被创建后再这样做.所以我们把这两行代码放在UpdateWindow调用之后.GetDlgItem函数获取控件ID并返回与这个控件窗口相关联的窗口句柄.所以如果你知道一个控件的ID你就能得到和它相关联的窗口句柄.
invoke IsDialogMessage, hDlg, ADDR msg
.IF eax ==FALSE
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDIF
The program enters the message loop and before we translate and dispatch messages, we call IsDialogMessage function to let the dialog box manager handles the keyboard logic of our dialog box for us. If this function returns TRUE , it means the message is intended for the dialog box and is processed by the dialog box manager. Note another difference from the previous tutorial. When the window procedure wants to get the text from the edit control, it calls GetDlgItemText function instead of GetWindowText. GetDlgItemText accepts a control ID instead of a window handle. That makes the call easier in the case you use a dialog box.
这个窗口进入消息循环,在我们翻译和分派消息之前,我们调用IsDialogMessage函数来让对话框管理器为我们处理我们对话框的键盘逻辑.如果这个函数返回值是TRUE, 它意味着这个消息是打算分派给对话框使用的并且已经被对话框管理器处理.在这里注意和先前课程中的一个不同之处.当这个窗口过程想从编辑控件中得到文本时它调用GetDlgItemText函数而不是GetWindowText函数.GetDlgItemText接收一个控件的ID来替代窗口句柄。这样让你在对话框中调用这个函数变得更容易。
________________________________________
Now let's go to the second approach to using a dialog box as a main window. In the next example, I 'll create an application modal dialog box. You'll not find a message loop or a window procedure because they're not necessary!
现在让我们转到用对话框作为我们主窗口的第二种方法。在下一个例子中,我将创建一个应用程序模式对话框,你将找不到消息循环或者是窗口过程因为它们是不需要的。
________________________________________
dialog.asm (part 2)
________________________________________
.386
.model flat,stdcall
option casemap:none
DlgProc 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
.data
DlgName db "MyDialog",0
AppName db "Our Second Dialog Box",0
TestString db "Wow! I'm in an edit box now",0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
buffer db 512 dup(?)
.const
IDC_EDIT equ 3000
IDC_BUTTON equ 3001
IDC_EXIT equ 3002
IDM_GETTEXT equ 32000
IDM_CLEAR equ 32001
IDM_EXIT equ 32002
IDR_MENU1 MENU
BEGIN
POPUP "Test Controls"
BEGIN
MENUITEM "Get Text", IDM_GETTEXT
MENUITEM "Clear Text", IDM_CLEAR
MENUITEM "", , 0x0800 /*MFT_SEPARATOR*/
MENUITEM "E&xit", IDM_EXIT
END
END
________________________________________
The analysis follows:
分析如下:
DlgProc proto :DWORD,:DWORD,:DWORD,:DWORD
We declare the function prototype for DlgProc so we can refer to it with addr operator in the line below:
我们声明函数的类型为DlgProc 所以我们能在下面用addr操作引用它:
invoke DialogBoxParam, hInstance, ADDR DlgName,NULL, addr DlgProc, NULL
The above line calls DialogBoxParam function which takes 5 parameters: the instance handle, the name of the dialog box template, the parent window handle, the address of the dialog box procedure, and the dialog-specific data. DialogBoxParam creates a modal dialog box. It will not return until the dialog box is destroyed.
上面这行调用的DialogBoxParam函数有5个参数:实例句柄, 一个对话框模版的名字,父窗口句柄,对话框过程的地址,还有对话框特有的数据。DialogBoxParam 函数创建一个模式对话框。它直到这个对话框被销毁时才返回。
.IF uMsg==WM_INITDIALOG
invoke GetDlgItem, hWnd,IDC_EDIT
invoke SetFocus,eax
.ELSEIF uMsg==WM_CLOSE
invoke SendMessage,hWnd,WM_COMMAND,IDM_EXIT,0
The dialog box procedure looks like a window procedure except that it doesn't receive WM_CREATE message. The first message it receives is WM_INITDIALOG. Normally you can put the initialization code here. Note that you must return the value TRUE in eax if you process the message.
The internal dialog box manager doesn't send our dialog box procedure the WM_DESTROY message by default when WM_CLOSE is sent to our dialog box. So if we want to react when the user presses the close button on our dialog box, we must process WM_CLOSE message. In our example, we send WM_COMMAND message with the value IDM_EXIT in wParam. This has the same effect as when the user selects Exit menu item. EndDialog is called in response to IDM_EXIT.
这个对话框过程看起来像一个窗口过程除了了它不接受WM_CREATE消息外.第一个被他接收的消息是WM_INITDIALOG.通常你能在这放置一些初始化代码.注意, 如果你处理了这个消息,你必须在eax中返回TRUE. 当WM_CLOSE消息被发送给我们的对话框过程时,内部的对话框管理器并不发送WM_DESTROY这个消息给对话框过程.所以当用户按了对话框的关闭按钮的时候我们如果想让它起作用,我们就必须处理WM_CLOSE消息.在我们的例子中,我们发送WM_COMMAND消息并置wParam的值为IDM_EXIT.这样做和用户选择退出菜单项是一样的效果.EndDialog函数被调用来响应IDM_EXIT.
The processing of WM_COMMAND messages remains the same.
When you want to destroy the dialog box, the only way is to call EndDialog function. Do not try DestroyWindow! EndDialog doesn't destroy the dialog box immediately. It only sets a flag for the internal dialog box manager and continues to execute the next instructions.
这和处理WM_COMMAND消息是一样的.
当你想销毁一个对话框时, 唯一的方式就是调用EndDialog函数.不要尝试DestroyWindow! EndDialog并不立即销毁这个对话框.它仅为内部的对话框管理器设置一个标志字然后继续执行下一条指令.
Now let's examine the resource file. The notable change is that instead of using a text string as menu name we use a value, IDR_MENU1. This is necessary if you want to attach a menu to a dialog box created with DialogBoxParam. Note that in the dialog box template, you have to add the keyword MENU followed by the menu resource ID.
A difference between the two examples in this tutorial that you can readily observe is the lack of an icon in the latter example. However, you can set the icon by sending the message WM_SETICON to the dialog box during WM_INITDIALOG.
现在我们来分析一下资源文件. 最显著的改变是这里用一个值-IDR_MENU1代替作为菜单名的字符串.如果你想调用DialogBoxParam函数创建对话框和一个菜单相关联,这就是必须的.(指用ID值代替菜单名)注意在对话框模版中,你必须增加一个菜单资源ID在关键字MENU的后面.
你能很容易的发现这一课的两个例子有一个不同之处就是后一个例子缺少一个图标.然而,你同样可以设置这个图标.只需在对话框处理WM_INITDIALOG消息的时候发送一个WM_SETICON给对话框并添加相应的代码即可.
________________________________________
This article come from Iczelion's asm page
风向改变 翻译于 2007年12月20日
Tutorial 11: More about Dialog Box
第十一课:对话框的更多特性
________________________________________
We will learn more about dialog box in this tutorial. Specifically, we will explore the topic of how to use dialog boxs as our input-output devices. If you read the previous tutorial, this one will be a breeze since only a minor modification is all that's needed to be able to use dialog boxes as adjuncts to our main window. Also in this tutorial, we will learn how to use common dialog boxes.
在这一课中,我们学习关于对话框的更多知识。特别的,我们将探究如何使用对话框来作为我们的输入输出设备这个主题。如果你读过上一课,那么这一课将是轻而易举的事因为唯一的一点改动就是必须把对话框作为我们主窗口的附属品。在这一课中,我们还将学习如何使用通用对话框。
Download the dialog box examples here and here. Download Common Dialog Box example here.
Theory:
原理:
Very little is to be said about how to use dialog boxes as input-output devices of our program. Your program creates the main window as usual and when you want to display the dialog box, just call CreateDialogParam or DialogBoxParam. With DialogBoxParam call, you don't have to do anything more, just process the messages in the dialog box procedure. With CreateDialogParam, you must insert IsDialogMessage call in the message loop to let dialog box manager handle the keyboard navigation in your dialog box for you. Since the two cases are trivial, I'll not put the source code here. You can download the examples and examine them yourself, here and here.
Let's go on to the common dialog boxes. Windows has prepared predefined dialog boxes for use by your applications. These dialog boxes exist to provide standardized user interface. They consist of file, print, color, font, and search dialog boxes. You should use them as much as possible. The dialog boxes reside in comdlg32.dll. In order to use them, you have to link to comdlg32.lib. You create these dialog boxes by calling appropriate functions in the common dialog library. For open file dialog, it is GetOpenFileName, for save as dialog it is GetSaveFileName, for print dialog it is PrintDlg and so on. Each one of these functions takes a pointer to a structure as its parameter. You should look them up in Win32 API reference. In this tutorial, I'll demonstrate how to create and use an open file dialog.
让我们接近通用对话框. Windows为你准备了预定义的对话框,你可以在你的应用程序中使用它.这些对话框提供标准的( 定型的)用户界面.它们由文件,打印,颜色,字体,和查找对话框组成.你应该尽可能多的用它们.这些对话框驻留在comdlg32.dll中.为了使用它们,你必须连接comdlg32.lib这个库文件.你通过调用在这个通用对话框库文件中的相应函数来创建这些对话框.
对于”打开文件”对话框,相应的函数是GetOpenFileName .对于”另存为”对话框,相应的函数是GetSaveFileName,对于”打印 ”对话框,相应的函数是PrintDlg函数等等.每一个这样的函数都接收一个指向一个结构体的指针来作为它的参数.你可以在win32 api手册中找到它们.在这一课中.我将示范如何创建和使用打开文件对话框.
Below is the function prototype of GetOpenFileName function:
下面是GetOpenFileName的函数原型:
GetOpenFileName proto lpofn:DWORD
You can see that it receives only one parameter, a pointer to an OPENFILENAME structure. The return value TRUE means the user selected a file to open, it's FALSE otherwise. We will look at OPENFILENAME structure next.
你能看出它仅接收一个参数,一个指向OPENFILENAME结构体的指针.返回值是TRUE意味着一个文件被用户选择并打开.返回FALSE则为其它.接下来让我们来看看OPENFILENAME这个结构体.
OPENFILENAME STRUCT
lStructSize DWORD ?
hwndOwner HWND ?
hInstance HINSTANCE ?
lpstrFilter LPCSTR ?
lpstrCustomFilter LPSTR ?
nMaxCustFilter DWORD ?
nFilterIndex DWORD ?
lpstrFile LPSTR ?
nMaxFile DWORD ?
lpstrFileTitle LPSTR ?
nMaxFileTitle DWORD ?
lpstrInitialDir LPCSTR ?
lpstrTitle LPCSTR ?
Flags DWORD ?
nFileOffset WORD ?
nFileExtension WORD ?
lpstrDefExt LPCSTR ?
lCustData LPARAM ?
lpfnHook DWORD ?
lpTemplateName LPCSTR ?
OPENFILENAME ENDS
Let's see the meaning of the frequently used members.
让我们看看经常用到的成员有什么意义.
lStructSize The size of the OPENFILENAME structure , in bytes
OPENFILENAME结构的大小,以字节为单位.
hwndOwner The window handle of the open file dialog box.
打开文件对话框的窗口句柄.
hInstance Instance handle of the application that creates the open file dialog box
创建和打开文件对话框的应用程序实例句柄.
lpstrFilter The filter strings in the format of pairs of null terminated strings. The first string in each pair is the description. The second string is the filter pattern. for example:
一对以NULL结束的格式化字符串中的过滤字串.每一对的第一个字符串是描述部分.第二个字符串是过滤样式. 例如:
FilterString db "All Files (*.*)",0, "*.*",0
db "Text Files (*.txt)",0,"*.txt",0,0
Note that only the pattern in the second string in each pair is actually used by Windows to filter out the files. Also noted that you have to put an extra 0 at the end of the filter strings to denote the end of it.
注意:只有每一对中的第二个字符串样式是WINDOWS实际过滤出的文件,另外您必须在该部分后放置一个0,以示字符串的结束。
nFilterIndex Specify which pair of the filter strings will be initially used when the open file dialog is first displayed. The index is 1-based, that is the first pair is 1, the second pair is 2 and so on. So in the above example, if we specify nFilterIndex as 2, the second pattern, "*.txt" will be used.
指定当打开文件对话框首次显示时首次使用的那一对过滤模式串.这个索引值从1开始,就是第一对是1,第二对是2并且继续下去.所以在上面的例子中,如果我们指定nFilterIndex的值是2,第二个模式,” *.txt” 将被使用.
lpstrFile Pointer to the buffer that contains the filename used to initialize the filename edit control on the dialog box. The buffer should be at least 260 bytes long.
After the user selects a file to open, the filename with full path is stored in this buffer. You can extract the information from it later.
指向一个包含文件名的缓冲区指针,这个文件名被用来初始化对话框上编辑控件的文件名.这个缓冲区至少要260个字节长.
当用户将选定的文件打开后,这个文件和它的全部路径都被存储在这个缓冲区中.稍后,你可以从这抽取你所要的信息.
nMaxFile The size of the lpstrFile buffer.
Lpstrfile 参数的缓冲区大小.
lpstrTitle Pointer to the title of the open file dialog box
指向打开文件对话框标题的字符串指针
Flags Determine the styles and characteristics of the dialog box.
这个标志决定着对话框的样式和特征面( 属性)
nFileOffset After the user selects a file to open, this member contains the index to the first character of the actual filename. For example, if the full name with path is "c:\windows\system\lz32.dll", the this member will contain the value 18.
在用户将选定文件打开后,这个成员包含一索引值,值的大小是全路径文件名中第一个字符处.
例如:如果全路径文件名是
"c:\windows\system\lz32.dll",这个成员将包含的值是18.
(c:\windows\system\ 共18个字符)
nFileExtension After the user selects a file to open, this member contains the index to the first character of the file extension
在用户将选定文件打开后,这个成员包含一索引值,值的大小是文件扩展名的第一个字符处.
Example:
The following program displays an open file dialog box when the user selects File-> Open from the menu. When the user selects a file in the dialog box, the program displays a message box showing the full name, filename,and extension of the selected file.
当用户从菜单中选择 文件-〉打开 菜单项时 ,我们的程序将显示一个打开文件对话框。当用户在对话框中选择一个文件时,这个程序将弹出一的对话框并显示选定文件的文件名和扩展名。
.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
include \masm32\include\comdlg32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib
.const
IDM_OPEN equ 1
IDM_EXIT equ 2
MAXSIZE equ 260
OUTPUTSIZE equ 512
.data
ClassName db "SimpleWinClass",0
AppName db "Our Main Window",0
MenuName db "FirstMenu",0
ofn OPENFILENAME <>
FilterString db "All Files",0,"*.*",0
db "Text Files",0,"*.txt",0,0
buffer db MAXSIZE dup(0)
OurTitle db "-=Our First Open File Dialog Box=-: Choose the file to open",0
FullPathName db "The Full Filename with Path is: ",0
FullName db "The Filename is: ",0
ExtensionName db "The Extension is: ",0
OutputString db OUTPUTSIZE dup(0)
CrLf db 0Dh,0Ah,0
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd: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
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
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,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,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:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if ax==IDM_OPEN
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hwndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE
mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
mov ofn.lpstrTitle, OFFSET OurTitle
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke lstrcat,offset OutputString,OFFSET FullPathName
invoke lstrcat,offset OutputString,ofn.lpstrFile
invoke lstrcat,offset OutputString,offset CrLf
invoke lstrcat,offset OutputString,offset FullName
mov eax,ofn.lpstrFile
push ebx
xor ebx,ebx
mov bx,ofn.nFileOffset
add eax,ebx
pop ebx
invoke lstrcat,offset OutputString,eax
invoke lstrcat,offset OutputString,offset CrLf
invoke lstrcat,offset OutputString,offset ExtensionName
mov eax,ofn.lpstrFile
push ebx
xor ebx,ebx
mov bx,ofn.nFileExtension
add eax,ebx
pop ebx
invoke lstrcat,offset OutputString,eax
invoke MessageBox,hWnd,OFFSET OutputString,ADDR AppName,MB_OK
invoke RtlZeroMemory,offset OutputString,OUTPUTSIZE
.endif
.else
invoke DestroyWindow, hWnd
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
________________________________________
Analysis:
分析:
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hwndOwner
push hInstance
pop ofn.hInstance
We fill in the routine members of ofn structures.
在这里 我们为例子中的ofn结构成员变量赋值.
mov ofn.lpstrFilter, OFFSET FilterString
This FilterString is the filename filter that we specify as follows:
这个FilterString是我们在下面指定的文件名过滤字串.
FilterString db "All Files",0,"*.*",0
db "Text Files",0,"*.txt",0,0
Note that All four strings are zero terminated. The first string is the description of the following string. The actual pattern is the even number string, in this case, "*.*" and "*.txt". Actually we can specify any pattern we want here. We MUST put an extra zero after the last pattern string to denote the end of the filter string. Don't forget this else your dialog box will behave strangely.
注意:这四个字符串都必须以0结尾.第一个字串是描述下一个字串的.实际的模式是偶数字串.即这个例子中的“*.*” 和” *.txt* ”.我们可以在这里指定任何我们想要的模式.另外,我们必须放一个额外的0 在最后一个模式字串的后面用来作为过滤字串结束标志.不要忘了这一点,否则你的对话框行为将会非常奇怪.
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE
We specify where the dialog box will put the filename that the user selects. Note that we must specify its size in nMaxFile member. We can later extract the filename from this buffer.
为对话框上用户选定的文件名指定一个储存的地方.(内存缓冲区)注意我们必须在nMaxFile成员中指定它的大小.稍后,我们可以从这个缓冲区中取这个文件名.
mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
Flags specifies the characteristics of the dialog box.
OFN_FILEMUSTEXIST and OFN_PATHMUSTEXIST flags demand that the filename and path that the user types in the filename edit control MUST exist.
OFN_LONGNAMES flag tells the dialog box to show long filenames.
OFN_EXPLORER flag specifies that the appearance of the dialog box must be explorer-like. OFN_HIDEREADONLY flag hides the read-only checkbox on the dialog box.
There are many more flags that you can use. Consult your Win32 API reference.
指定对话框特证面的标志字. OFN_FILEMUSTEXIST 和 OFN_PATHMUSTEXIST标志要求用户在文件名编辑控件中输入的文件名和路径必须存在. OFN_LONGNAMES标志告诉我们对话框显示长文件名. OFN_EXPLORER标志规定对话框的外观必须像资源管理器. OFN_HIDEREADONLY标志在对话框上隐藏只读文件的检查框.
这里有很多标志你可以用,请参照你的win32 api手册来获取更多信息.
mov ofn.lpstrTitle, OFFSET OurTitle
Specify the title of the dialog box.
指定对话框标题.
invoke GetOpenFileName, ADDR ofn
Call the GetOpenFileName function. Passing the pointer to the ofn structure as its parameter.
调用GetOpenFileName函数.并传递一个OFN结构指针作为函数参数.
At this time, the open file dialog box is displayed on the screen. The function will not return until the user selects a file to open or presses the cancel button or closes the dialog box. It 'll return the value TRUE in eax if the user selects a file to open. It returns FALSE otherwise.
这时,打开文件对话框将显示在你的屏幕上.这个函数并不立即返回.直到用户选择打开一个文件或者是按下了取消按钮或者是关闭了这个对话框为止.如果用户选择打开一个文件,它将在eax中返回TRUE,否则,它返回FALSE.
.if eax==TRUE
invoke lstrcat,offset OutputString,OFFSET FullPathName
invoke lstrcat,offset OutputString,ofn.lpstrFile
invoke lstrcat,offset OutputString,offset CrLf
invoke lstrcat,offset OutputString,offset FullName
In case the user selects a file to open, we prepare an output string to be displayed in a message box. We allocate a block of memory in OutputString variable and then we use an API function, lstrcat, to concatenate the strings together. In order to put the strings into several lines, we must separate each line with a carriage return-line feed pair.
在这个例子中,用户选择打开一个文件,我们准备一个在消息框中显示的输出字符串.首先.我们为OutputString变量在内存中分配一存储块然后用一个API函数,lstrcat,来将这个字串连接在一起.为了将这些字串分行显示,我们必须在每个字符串后面放置一换行符.
mov eax,ofn.lpstrFile
push ebx
xor ebx,ebx
mov bx,ofn.nFileOffset
add eax,ebx
pop ebx
invoke lstrcat,offset OutputString,eax
The above lines require some explanation. nFileOffset contains the index into the ofn.lpstrFile. But you cannot add them together directly since nFileOffset is a WORD-sized variable and lpstrFile is a DWORD-sized one. So I have to put the value of nFileOffset into the low word of ebx and add it to the value of lpstrFile.
上面这些行需要一些说明.nFileOffset包含一个在ofn.lpstrFile成员中的索引值.但是你不能将它们直接的合在一起.因为nFileOffset是一个字长的变量而lpstrFile是一个双子长的变量.所以我们应该把nFileOffset的值放在ebx寄存器的低16位中并将这个值传给lpstrFile.
invoke MessageBox,hWnd,OFFSET OutputString,ADDR AppName,MB_OK
We display the string in a message box.
我们在消息框中显示一个字符.
invoke RtlZerolMemory,offset OutputString,OUTPUTSIZE
We must *clear* the OutputString before we can fill in another string. So we use RtlZeroMemory function to do the job.
在用另外一些字符来替换OutputString字串之前我们必须用RtlZeroMenory这个函数来清除OutputString变量中的值.
________________________________________
This article come from Iczelion's asm page
风向改变 翻译于 2007-12-22日
Tutorial 12: Memory Management and File I/O
第十二课:内存管理和文件输入输出
________________________________________
We will learn the rudimentary of memory management and file i/o operation in this tutorial. In addition we'll use common dialog boxes as input-output devices.
我们将在这一课中学习内存管理的基本原理和文件的I/O操作。另外我们将用通用对话框作为输入输出设备。
Download the example here.
Theory:
原理:
Memory management under Win32 from the application's point of view is quite simple and straightforward. Each process owns a 4 GB memory address space. The memory model used is called flat memory model. In this model, all segment registers (or selectors) point to the same starting address and the offset is 32-bit so an application can access memory at any point in its own address space without the need to change the value of selectors. This simplifies memory management a lot. There's no "near" or "far" pointer anymore.
从应用程序的角度来看WIN32中的内存管理是非常简单和直接的.每个程序都拥有4GB的地址空间.这种内存模式叫做平坦内存模式.在这种模式中,所有的段寄存器(或选择器)都指向同一个开始地址并且偏移量是32位的,所以一个应用程序能在它的地址空间中访问任何地址而不需要改变段选择器的值. 这种内存管理模式简单了很多.在平坦模式中再也没有了 near 和 far 指针.( 即段内和段间转移)
Under Win16, there are two main categories of memory API functions: Global and Local. Global-type API calls deal with memory allocated in other segments thus they're "far" memory functions. Local-type API calls deal with the local heap of the process so they're "near" memory functions. Under Win32, these two types are identical. Whether you call GlobalAlloc or LocalAlloc, you get the same result.
在win16中,有两种主要类型的内存API函数: 全局的和局部的.全局类型的API函数调用处理在其他段中内存分配(编译中的静态分配)从而它们是”far”内存函数. .局部类型的API调用处理一个过程的局部内存堆(编译中的动态分配)所以它们是”near”内存函数.在win32中,这两种类型是一样的.无论你调用的是GlobalAlloc(全局分配) 或是 LocalAlloc(局部分配),你得到的结果是一样的.
Steps in allocating and using memory are as follows:
分配和使用内存的步骤如下:
1. Allocate a block of memory by calling GlobalAlloc. This function returns a handle to the requested memory block.
通过调用GlobalAlloc分配一个内存块.这个函数返回一个请求到的(向操作系统请求) 内存块.
2. "Lock" the memory block by calling GlobalLock. This function accepts a handle to the memory block and returns a pointer to the memory block.
通过调用GlobalLock锁住这个内存块。这个函数接收一个内存块句柄并返回这个内存块指针。(锁住的内存块)
3. You can use the pointer to read or write memory.
你能用指针读或写内存。
4. "Unlock" the memory block by calling GlobalUnlock . This function invalidates the pointer to the memory block.
通过调用GlobalUnlock函数为锁住的内存块解锁。这个函数使指向那个内存块的指针无效。
5. Free the memory block by calling GlobalFree. This function accepts the handle to the memory block.
通过调用GlobalFree函数释放申请到的内存块。这个函数接收一个内存块句柄。
You can also substitute "Global" by "Local" such as LocalAlloc, LocalLock,etc.
你也能用“Local”代替“Global”例如LocalAlloc,LocalLock等。
The above method can be further simplified by using a flag in GlobalAlloc call, GMEM_FIXED. If you use this flag, the return value from Global/LocalAlloc will be the pointer to the allocated memory block, not the memory block handle. You don't have to call Global/LocalLock and you can pass the pointer to Global/LocalFree without calling Global/LocalUnlock first. But in this tutorial, I'll use the "traditional" approach since you may encounter it when reading the source code of other programs.
上面这些方法能通过在GlobalAlloc调用中使用一个GMEM_FIXED标志而得到进一步的简化。如果你用这个标志,从Global/localAlloc中返回的值将是一个指向已分配内存块的指针,而不是内存块句柄。你也没必要再调用Global/LocalLock这个函数来锁定内存块 并且 在释放内存时 你能传递这个指针给Global/LocalFree而不用首先调用Global/LocalUnlock函数来为这个内存块解锁。 但是在这一课中,我将使用“传统的” 方法因为当你阅读其他程序的源代码时你可能遭遇它 。
File I/O under Win32 bears remarkable semblance to that under DOS. The steps needed are the same. You only have to change interrupts to API calls and it's done. The required steps are the followings:
在win32中的文件i/o操作比起在DOS中的文件i/o操作具有显著的外表(伪装),但需要的步骤是相同的。你仅需要的是把DOS中的中断调用变成API调用。 下面的步骤是必须的:
1. Open or Create the file by calling CreateFile function. This function is very versatile: in addition to files, it can open communication ports, pipes, disk drives or console. On success, it returns a handle to file or device. You can then use this handle to perform operations on the file or device.
Move the file pointer to the desired location by calling SetFilePointer.
通过调用CreateFile函数来打开或创建文件。这个函数是非常有用的: 除文件外,它能打开通信端口,管道,磁盘驱动器或控制台。如果成功的话,它将返回一个文件或设备的句柄。然后,你能用这个句柄完成对文件和设备的操作。通过调用SetFilePointer函数来把文件指针移到想读写的地方。
2. Perform read or write operation by calling ReadFile or WriteFile. These functions transfer data from a block of memory to or from the file. So you have to allocate a block of memory large enough to hold the data.
通过调用ReadFile或WriteFile来完成读或写操作。这些函数在内存块和文件之间传递数据。所以你必须分配一个足够大的内存块来容纳数据。
3. Close the file by calling CloseHandle. This function accepts the file handle.
通过调用CloseHandle函数来关闭文件。这个函数接收一个文件句柄。
Content:
内容:
The program listed below displays an open file dialog box. It lets the user select a text file to open and shows the content of that file in an edit control in its client area. The user can modify the text in the edit control as he wishes, and can choose to save the content in a file.
下面的程序清单显示一个打开文件对话框。它让用户选择一个文本文件来打开并将文件的内容显示在一个编辑控件的客户区内。用户能在编辑框中按他希望的形式修改文本,并且能选择保存文件内容。
.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
include \masm32\include\comdlg32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\comdlg32.lib
.const
IDM_OPEN equ 1
IDM_SAVE equ 2
IDM_EXIT equ 3
MAXSIZE equ 260
MEMSIZE equ 65535
EditID equ 1 ; ID of the edit control
.data
ClassName db "Win32ASMEditClass",0
AppName db "Win32 ASM Edit",0
EditClass db "edit",0
MenuName db "FirstMenu",0
ofn OPENFILENAME <>
FilterString db "All Files",0,"*.*",0
db "Text Files",0,"*.txt",0,0
buffer db MAXSIZE dup(0)
.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?
hwndEdit HWND ? ; Handle to the edit control
hFile HANDLE ? ; File handle
hMemory HANDLE ? ;handle to the allocated memory block
pMemory DWORD ? ;pointer to the allocated memory block
SizeReadWrite DWORD ? ; number of bytes actually read or write
.code
start:
invoke GetModuleHandle, NULL
mov hInstance,eax
invoke GetCommandLine
mov CommandLine,eax
invoke WinMain, hInstance,NULL,CommandLine, SW_SHOWDEFAULT
invoke ExitProcess,eax
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:SDWORD
LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd: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
push hInst
pop wc.hInstance
mov wc.hbrBackground,COLOR_WINDOW+1
mov wc.lpszMenuName,OFFSET MenuName
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,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\
WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,\
CW_USEDEFAULT,300,200,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 uses ebx hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.IF uMsg==WM_CREATE
invoke CreateWindowEx,NULL,ADDR EditClass,NULL,\
WS_VISIBLE or WS_CHILD or ES_LEFT or ES_MULTILINE or\
ES_AUTOHSCROLL or ES_AUTOVSCROLL,0,\
0,0,0,hWnd,EditID,\
hInstance,NULL
mov hwndEdit,eax
invoke SetFocus,hwndEdit
;==============================================
; Initialize the members of OPENFILENAME structure
;==============================================
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hWndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE
.ELSEIF uMsg==WM_SIZE
mov eax,lParam
mov edx,eax
shr edx,16
and eax,0ffffh
invoke MoveWindow,hwndEdit,0,0,eax,edx,TRUE
.ELSEIF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSEIF uMsg==WM_COMMAND
mov eax,wParam
.if lParam==0
.if ax==IDM_OPEN
mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ or GENERIC_WRITE ,\
FILE_SHARE_READ or FILE_SHARE_WRITE,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFile,eax
invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE
mov hMemory,eax
invoke GlobalLock,hMemory
mov pMemory,eax
invoke ReadFile,hFile,pMemory,MEMSIZE-1,ADDR SizeReadWrite,NULL
invoke SendMessage,hwndEdit,WM_SETTEXT,NULL,pMemory
invoke CloseHandle,hFile
invoke GlobalUnlock,pMemory
invoke GlobalFree,hMemory
.endif
invoke SetFocus,hwndEdit
.elseif ax==IDM_SAVE
mov ofn.Flags,OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetSaveFileName, ADDR ofn
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ or GENERIC_WRITE ,\
FILE_SHARE_READ or FILE_SHARE_WRITE,\
NULL,CREATE_NEW,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFile,eax
invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE
mov hMemory,eax
invoke GlobalLock,hMemory
mov pMemory,eax
invoke SendMessage,hwndEdit,WM_GETTEXT,MEMSIZE-1,pMemory
invoke WriteFile,hFile,pMemory,eax,ADDR SizeReadWrite,NULL
invoke CloseHandle,hFile
invoke GlobalUnlock,pMemory
invoke GlobalFree,hMemory
.endif
invoke SetFocus,hwndEdit
.else
invoke DestroyWindow, hWnd
.endif
.endif
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
end start
________________________________________
Analysis:
分析:
invoke CreateWindowEx,NULL,ADDR EditClass,NULL,\
WS_VISIBLE or WS_CHILD or ES_LEFT or ES_MULTILINE or\
ES_AUTOHSCROLL or ES_AUTOVSCROLL,0,\
0,0,0,hWnd,EditID,\
hInstance,NULL
mov hwndEdit,eax
In WM_CREATE section, we create an edit control. Note that the parameters that specify x, y, width,height of the control are all zeroes since we will resize the control later to cover the whole client area of the parent window.
Note that in this case, we don't have to call ShowWindow to make the edit control appear on the screen because we include WS_VISIBLE style. You can use this trick in the parent window too.
在WM_CREATE节区中,我们创建一个编辑控件。注意指定控件的x,y,width,height参数为空是因为我们稍后将调整这个控件的大小来覆盖整个父窗口的客户区。注意在这个例子中,我们没有必要调用ShowWindow函数来让这个控件显示在屏幕上因为我们包含了WS_VISIBLE样式。你也在父窗口中用这个技巧。
;==============================================
; Initialize the members of OPENFILENAME structure
;==============================================
mov ofn.lStructSize,SIZEOF ofn
push hWnd
pop ofn.hWndOwner
push hInstance
pop ofn.hInstance
mov ofn.lpstrFilter, OFFSET FilterString
mov ofn.lpstrFile, OFFSET buffer
mov ofn.nMaxFile,MAXSIZE
After creating the edit control, we take this time to initialize the members of ofn. Because we want to reuse ofn in the save as dialog box too, we fill in only the *common* members that're used by both GetOpenFileName and GetSaveFileName.
在创建了编辑控件后,我们初始化ofn结构成员变量。因为在保存文件对话框中还要用到这个结构,所以我们只初始化它的公共成员变量,这些公共成员将被GetOpenFileName和GetSaveFileName函数使用。
WM_CREATE section is a great place to do once-only initialization.
WM_CRREATE 消息的是对变量仅初始化一次的好地方。
.ELSEIF uMsg==WM_SIZE
mov eax,lParam
mov edx,eax
shr edx,16
and eax,0ffffh
invoke MoveWindow,hwndEdit,0,0,eax,edx,TRUE
We receive WM_SIZE messages when the size of the client area of our main window changes. We also receive it when the window is first created. In order to be able to receive this message, the window class styles must include CS_VREDRAW and CS_HREDRAW styles. We use this opportunity to resize our edit control to the same size as the client area of the parent window. First we have to know the current width and height of the client area of the parent window. We get this info from lParam. The high word of lParam contains the height and the low word of lParam the width of the client area. We then use the information to resize the edit control by calling MoveWindow function which, in addition to changing the position of the window, can alter the size too.
当主窗口客户区大小被改变的时候我们收到WM_SIZE消息。在窗口第一次创建的时候我们同样能接收到这个消息。为了能接收到这个消息,窗口样式必须包含CS_VREDRAW和CS_HREDRAW样式。这个时候我们将编辑控件的大小调整得和父窗口客户区一样大。首先,我们必须知道当前父窗口客户区的宽和高。lParam的高子节包含客户区的高度,低子节包含客户区的宽度。然后我们通过调用MoveWindow函数来使用这些信息从而调整编辑控件的大小。该函数不仅能改变窗口位置而且还能改变窗口大小。
.if ax==IDM_OPEN
mov ofn.Flags, OFN_FILEMUSTEXIST or \
OFN_PATHMUSTEXIST or OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
invoke GetOpenFileName, ADDR ofn
When the user selects File/Open menu item, we fill in the Flags member of ofn structure and call GetOpenFileName function to display the open file dialog box.
当用户选定文件/打开菜单项时,我们填写ofn结构的Flags成员变量并调用GetOpenFileName函数来显示一个打开文件对话框。
.if eax==TRUE
invoke CreateFile,ADDR buffer,\
GENERIC_READ or GENERIC_WRITE ,\
FILE_SHARE_READ or FILE_SHARE_WRITE,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,\
NULL
mov hFile,eax
After the user selects a file to open, we call CreateFile to open the file. We specifies that the function should try to open the file for read and write. After the file is opened, the function returns the handle to the opened file which we store in a global variable for future use. This function has the following syntax:
当用户选择了他想打开的文件之后,我们调用CreateFile函数来打开这个文件。我们指定标志位来让打开的文件能够读和写。在文件被打开后,这个函数将返回被打开的文件句柄,为了方便以后使用我们将它存储在一个全局变量中。这个函数句法如下:
CreateFile proto lpFileName:DWORD,\
dwDesiredAccess:DWORD,\
dwShareMode:DWORD,\
lpSecurityAttributes:DWORD,\
dwCreationDistribution:DWORD\,
dwFlagsAndAttributes:DWORD\,
hTemplateFile:DWORD
dwDesiredAccess specifies which operation you want to perform on the file.
dwDesiredAccess指定你想在这个文件上完成的操作。
• 0 Open the file to query its attributes. You have to rights to read or write the data.
• 0 打开文件并查询它的属性。你必须有权限读写它的数据。
• GENERIC_READ Open the file for reading.
• 以只读方式打开文件。
• GENERIC_WRITE Open the file for writing.
• 以只写方式打开文件。
dwShareMode specifies which operation you want to allow other processes to perform on the file that 's being opened.
指定其他进程在你打开的文件上允许执行的操作。
• 0 Don't share the file with other processes.
• 0 不和其他进程共享这个文件
• FILE_SHARE_READ allow other processes to read the data from the file being opened
• 允许其它进程从这个打开的文件中读数据。
• FILE_SHARE_WRITE allow other processes to write data to the file being opened.
• 允许其它进程从这个打开的文件中写数据。
lpSecurityAttributes has no significance under Windows 95.
该属性在win95中无效。
dwCreationDistribution specifies the action CreateFile will perform when the file specified in lpFileName exists or when it doesn't exist.
指定 当文件指定在lpFileName中的成员存在或不存在的时候CreateFile函数将执行的动作
• CREATE_NEW Creates a new file. The function fails if the specified file already exists.
• CREATE_NEW创建一个新文件.如果指定文件已经存在,函数无效.
• CREATE_ALWAYS Creates a new file. The function overwrites the file if it exists.
• CREATE_ALWAYS创建一个新文件.如果指定的文件存在,函数重写这文件.
• OPEN_EXISTING Opens the file. The function fails if the file does not exist.
• OPEN_EXISTING 打开文件.如果文件不存在,函数无效.
• OPEN_ALWAYS Opens the file, if it exists. If the file does not exist, the function creates the file as if dwCreationDistribution were CREATE_NEW.
• OPEN_ALWAYS 打开文件,如果文件存在的话.如果文件不存在,函数创建这个文件如同dwCreationDistribution 参数的值为CREATE_NEW.
• TRUNCATE_EXISTING Opens the file. Once opened, the file is truncated so that its size is zero bytes. The calling process must open the file with at least GENERIC_WRITE access. The function fails if the file does not exist.
• TRUNCATE_EXISTING 打开文件.一旦打开,文件被截所以它的大小是零字节.调用进程必须打开文件并且至少有GENETIC_WRITE访问权.如果文件不存在函数失效.
dwFlagsAndAttributes specifies the file attributes
dwFlagsAndAttributes 指定文件属性.
• FILE_ATTRIBUTE_ARCHIVE The file is an archive file. Applications use this attribute to mark files for backup or removal.
• FILE_ATTRIBUTE_ARCHIVE 文件是一档案文件.应用程序可以用这个属性作为文件备份和删除的标志.
• FILE_ATTRIBUTE_COMPRESSED The file or directory is compressed. For a file, this means that all of the data in the file is compressed. For a directory, this means that compression is the default for newly created files and subdirectories.
• FILE_ATTRIBUTE_COMPRESSED 文件或目录是压缩的.对于文件,意味着所有在文件中的数据都被压缩.对一目录,意味着对于新生成的文件和子目录的压缩是默认的.
• FILE_ATTRIBUTE_NORMAL The file has no other attributes set. This attribute is valid only if used alone.
• FILE_ATTRIBUTE_NORMAL 文件没有其它的属性设置.仅当该属性单独使用时属性才是有效的.
• FILE_ATTRIBUTE_HIDDEN The file is hidden. It is not to be included in an ordinary directory listing.
• FILE_ATTRIBUTE_HIDDEN 文件是隐藏文件.它并不包含在普通的目录列表中.
• FILE_ATTRIBUTE_READONLY The file is read only. Applications can read the file but cannot write to it or delete it.
• FILE_ATTRIBUTE_READONLY 文件是只读文件.应用程序能读这个文件但是不能写这个文件或是删除它.
• FILE_ATTRIBUTE_SYSTEM The file is part of or is used exclusively by the operating system.
• FILE_ATTRIBUTE_SYSTEM 文件是操作系统专用的一部分.即系统文件.
invoke GlobalAlloc,GMEM_MOVEABLE or GMEM_ZEROINIT,MEMSIZE
mov hMemory,eax
invoke GlobalLock,hMemory
mov pMemory,eax
When the file is opened, we allocate a block of memory for use by ReadFile and WriteFile functions. We specify GMEM_MOVEABLE flag to let Windows move the memory block around to consolidate memory. GMEM_ZEROINIT flag tells GlobalAlloc to fill the newly allocated memory block with zeroes.
When GlobalAlloc returns successfully, eax contains the handle to the allocated memory block. We pass this handle to GlobalLock function which returns a pointer to the memory block.
当文件被打开时,我们分配一块内存供ReadFile和WriteFile函数使用.我们指定GMEM_MOVEABLE标志让Windows移动分配内存块周围的内存来整理内存.GMEM_ZERO INIT标志告诉GlobalAlloc函数用零将新分配的内存块填满.当GlobalAlloc返回成功,eax包含分配内存块的句柄.我们传递这个句柄给GlobalLock函数,这个函数返回内存块的指针.
invoke ReadFile,hFile,pMemory,MEMSIZE-1,ADDR SizeReadWrite,NULL
invoke SendMessage,hwndEdit,WM_SETTEXT,NULL,pMemory
When the memory block is ready for use, we call ReadFile function to read data from the file. When a file is first opened or created, the file pointer is at offset 0. So in this case, we start reading from the first byte in the file onwards. The first parameter of ReadFile is the handle of the file to read, the second is the pointer to the memory block to hold the data, next is the number of bytes to read from the file, the fourth param is the address of the variable of DWORD size that will be filled with the number of bytes actually read from the file.
After we fill the memory block with the data, we put the data into the edit control by sending WM_SETTEXT message to the edit control with lParam containing the pointer to the memory block. After this call, the edit control shows the data in its client area.
当内存块准备好被使用时,我们调用ReadFile函数从文件中读进数据.当文件是第一次被打开或创建时,文件指针在偏移量0的地方 .所以在这个例子中,我们从第一个字节向前的读这个文件.ReadFile的第一个参数是要读的那个文件句柄,第二个参数是存放数据的内存块指针,下一个成员是从文件中读取的字节数.第四个参数是DWORD长度的地址变量,这个成员存放从文件中实际读取的字节数.
在用数据填满这个内存块后,我们通过在WM_SETTEXT消息的lParam中包含内存块的指针并发送WM_SETTEXT消息给编辑控件来把数据放置在编辑控件中。在这个函数调用后,编辑控件将数据显示在它的客户区。
invoke CloseHandle,hFile
invoke GlobalUnlock,pMemory
invoke GlobalFree,hMemory
.endif
At this point, we have no need to keep the file open any longer since our purpose is to write the modified data from the edit control to another file, not the original file. So we close the file by calling CloseHandle with the file handle as its parameter. Next we unlock the memory block and free it. Actually you don't have to free the memory at this point, you can reuse the memory block during the save operation later. But for demonstration purpose , I choose to free it here.
这里需要指出的是,我们再也不需要让文件保持打开状态了,因为我们的目的是把在编辑框中修改了的数据写进其它的文件中而不是原来的文件。所以我们指定文件句柄作为CloseHandle的参数,然后调用这个函数来关闭文件。下一步我们解锁这块内存并释放它。实际上你没必要在这里释放这块内存,你可以在稍后的保存操作中重新使用这块内存。但是作为演示的用途,我在这里选择释放它。
invoke SetFocus,hwndEdit
When the open file dialog box is displayed on the screen, the input focus shifts to it. So after the open file dialog is closed, we must move the input focus back to the edit control.
This end the read operation on the file. At this point, the user can edit the content of the edit control.And when he wants to save the data to another file, he must select File/Save as menuitem which displays a save as dialog box. The creation of the save as dialog box is not much different from the open file dialog box. In fact, they differ in only the name of the functions, GetOpenFileName and GetSaveFileName. You can reuse most members of the ofn structure too except the Flags member.
当打开文件对话框被显示在屏幕上时,输入焦点切换给它。所以在对话框被关闭后,我们必须移动输入焦点到编辑控件上。
最后是在文件上的写操作。这里需要指出的是,用户能编辑这个编辑控件中的内容。并且当用户想把数据保存在另一个文件中时,他必须选定文件/保存菜单项,这个菜单项弹出一个保存文件对话框。产生的保存对话框和打开文件对话框有很多相似之处。事实上,它们的不同之处仅仅是函数的名字,GetOpenFileName 和GetSaveFileName。除了标志成员外你可以重新使用ofn结构的大多数成员。
mov ofn.Flags,OFN_LONGNAMES or\
OFN_EXPLORER or OFN_HIDEREADONLY
In our case, we want to create a new file, so OFN_FILEMUSTEXIST and OFN_PATHMUSTEXIST must be left out else the dialog box will not let us create a file that doesn't already exist.
The dwCreationDistribution parameter of the CreateFile function must be changed to CREATE_NEW since we want to create a new file.
The remaining code is identical to those in the open file section except the following:
在我们的例子中,我们想创建一个新的文件,所以OFN_FILEMUSTEXIST和OFN_PATHMUSTEXIST标志必须被遗弃,否则这个对话框将不能让我们创建一个文件,因为这个文件不是现有的。
CreateFile函数的dwCreationDistribution 参数必须变成CREATE_NEW 因为我们想创建一个新文件。
剩下的代码除了下面这几行,其它的和那些在打开文件节区中的一样:
invoke SendMessage,hwndEdit,WM_GETTEXT,MEMSIZE-1,pMemory
invoke WriteFile,hFile,pMemory,eax,ADDR SizeReadWrite,NULL
We send WM_GETTEXT message to the edit control to copy the data from it to the memory block we provide, the return value in eax is the length of the data inside the buffer. After the data is in the memory block, we write them to the new file.
我们发送WM_GETTEXT消息给编辑控件,复制数据到我们预先提供的内存块中。在eax中的返回值是缓冲区中的数据长度。当数据复制到内存块中后,我们把它们写到新文件中。
________________________________________
This article come from Iczelion's asm page,
风向改变翻译于 2007-12-29日
由于考试复习,放了一段时间。