Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1760241
  • 博文数量: 290
  • 博客积分: 10653
  • 博客等级: 上将
  • 技术积分: 3178
  • 用 户 组: 普通用户
  • 注册时间: 2007-10-24 23:08
文章存档

2013年(6)

2012年(15)

2011年(25)

2010年(86)

2009年(52)

2008年(66)

2007年(40)

分类:

2007-12-03 20:02:13

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.

 

对程序员而言,这经过微软测试的GUI源代码已经在那里,并且为了你的使用而准备着。当然 ,它的不利方面是为程序设计员增加了相关复杂性。为了创建或生成任何GUI对象,例如窗口,菜单,或者图标,程序员必须遵循一些严格的规则。但是它能够通过使用模块化设计或者是面向对象的编程方法而被征服。

 

I'll outline the steps required to create a window on the desktop below:

在下面,我将略述 在桌面上 创建一个窗口 所需要的步骤:

  1. Get the instance handle of your program (required)

  获取你的程序的实例句柄 (必须的)

  1. Get the command line (not required unless your program wants to process a command line)

获取命令行(不是必须的,除非你的程序想处理一个命令行)

  1. Register window class (required ,unless you use predefined window types, eg. MessageBox or a dialog box)

注册窗口类( 必须的 ,除非你使用预定义的窗口类型 : 如: MessageBox

或者是一个对话框 )

  1. Create the window (required)

创建一个窗口(必须的)

  1. Show the window on the desktop (required unless you don't want to show the window immediately)

在桌面上显示你的窗口(必须的除非你不想立即看到你的窗口)

  1. Refresh the client area of the window

刷新窗口的客户区 

  1. Enter an infinite loop, checking for messages from Windows

进入一个死循环,检查来自windows 的消息(即消息循环)

  1. If messages arrive, they are processed by a specialized function that is responsible for the window

如果有消息到达,它们被由为这个窗口负责的一个专门的函数处理

  1. 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.

 

开始这三行是必须的,.386告诉MASM我们在程序中用80386的指令集,.model falt ,stdcall 告诉MASM我们将使用平坦内存地址模式. 我们将使用Stdcall

作为在我们程序中默认的参数约定.下一行是WinMain函数原型说明.稍后我们将调用WinMain函数.在我们用INVOKE调用它之前,首先我们必须定义它的函数原型.


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.

 

我们必须在源代码的开始包含windows.inc 它包含我们程序中使用的重要结构和常量.这个windows.inc仅仅是一个文本文件.你能用任何文本编辑文件打开它,请注意这个windows.inc并不包含所有的结构和常量.hutch和我仍然在继续扩充它.你可以为它增加新的内容,如果这些内容并不包含在这个文件中.

 

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.

 

我们的程序调用的API函数,它们驻留在user32.dll(例如CreateWindowEx,RegisterWindowClassEX,)和kernel.dll,所以,我们必须将我们的程序和这两个导入库连接.接下来的问题是:我们怎么知道那一个导入库应该连接到我们的程序.答案是:你必须清楚被你的程序调用的API函数驻留在那个导入库中.例如,如果你调用在gdi32.dll动态连接库中的一个API函数,你就必须连接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.

 

.data 我们说明两个以NULL结尾的空串(ASCIIZ 字串):

类名是我们窗口类的的名字.appName是我们窗口的名字.注意这两个变量是已经被初始化的。


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类型的一个新名字。你能在windowsInc中看到它们。 注意在.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 : and end . 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.

 

.code中包含你所有的指令.你的源码必须驻留在<开始标号>: end<开始标号>之间.至于标号用什么名字并不重要.你能用任何你喜欢的名称来命名它只要她是唯一的并且不违反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,esiebp这几个寄存器. ecxedx被当做临时寄存器,这两个寄存器从win32函数中返回的值是永远不可知的,有偶然性.

 Note: Don't expect the values of eax, ecx, edx to be preserved across API function calls.

注意: Windows API 函数中返回后,eaxecxedx 中的值和调用前不一定相同
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 :. 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的时候,存储芯片价格高得惊人 从而大多数计算机内存只有1Mwindows必须有效的利用稀有的内存空间。有一点要指出:如果你定义你自己的窗口,在你创建窗口之前,你必须在一个WNDCLASSWNDCLASSEX结构中填入你想得到的窗口的特征面(属性)并且调用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这是一个新的参数。您可以为windows95NT设置新的窗口风格。您可以在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日

 

阅读(2796) | 评论(4) | 转发(0) |
给主人留下些什么吧!~~

chinaunix网友2008-04-23 15:53:38

不错啊,这两天一直在关注!

kanghtta2008-04-15 11:45:20

有关C 编程的,过一段时间写一些出来! 嘿嘿... 谢谢你的支持!

moisev2008-04-12 16:09:14

无意中发现了楼主的blog,如获至宝.不仅学了编程语言同时还学习英语,一举两得啊.所以专门注册了一个号上来感谢楼主.希望楼主能多翻译一些类似(像C之类的)的文篇造福于网民,我知道这会花费你很多的精力.