Author: Jacky Chou

引用:
理论:
  Windows 程序中,在写图形用户界面时需要调用大量的标准 Windows Gui 函数。其实这对用户和程序员来说都有好处,对于用户,面对的是

同一套标准的窗口,对这些窗口的操作都是一样的,所以使用不同的应用程序时无须重新学习操作。对程序员来说,这些 Gui 源代码都是经过

了微软的严格测试,随时拿来就可以用的。当然至于具体地写程序对于程序员来说还是有难度的。为了创建基于窗口的应用程序,必须严格遵

守规范。作到这一点并不难,只要用模块化或面向对象的编程方法即可。

下面我就列出在桌面显示一个窗口的几个步骤:
1.  得到您应用程序的句柄(必需); 
2.  得到命令行参数(如果您想从命令行得到参数,可选); 
3.  注册窗口类(必需,除非您使用 Windows 预定义的窗口类,如 MessageBox 或 dialog box; 
4.  产生窗口(必需); 
5.  在桌面显示窗口(必需,除非您不想立即显示它); 
6.  刷新窗口客户区; 
7.  进入无限的获取窗口消息的循环; 
8.  如果有消息到达,由负责该窗口的窗口回调函数处理; 
9.  如果用户关闭窗口,进行退出处理。 

相对于单用户的 DOS 下的编程来说,Windows 下的程序框架结构是相当复杂的。但是 Windows 和 DOS 在系统架构上是截然不同的。Windows 

是一个多任务的操作系统,故系统中同时有多个应用程序彼此协同运行。这就要求 Windows 程序员必须严格遵守编程规范,并养成良好的编程

风格。
下面列出程序源代码:

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
以下代码 经RadASM编译通过,测试成功! (程序只有4K) 我用VC++ Win32 Application 做一个完全一样功能的程序,要168K,果然汇编牛啊
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;  Programmed by Jacky Chou
;
;     Win32 ASM is Masm
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

代码:
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;WinMain_Asm.Inc
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

.386                         ;
.model flat,stdcall          ;       
option casemap:none       ;这3条不用说了

include windows.inc       ;需要包含的头文件和库文件
include kernel32.inc
includelib kernel32.lib
include user32.inc
includelib user32.lib
include gdi32.inc
includelib gdi32.lib
include comctl32.inc
includelib comctl32.lib

代码:
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;WinMain_Asm.Asm
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

include WinMain_Asm.inc

WinMain proto :DWORD,:DWORD,:DWORD,:DWORD   ;WinMain函数的申明

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;Data Segment --- initialized data
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

.data
WndClassName db "MyFirstWin",0          ;类名
WndAppName   db "WinMainTest",0    ;窗口名
MsgText      db "hello JackyChou",0  ;消息内容
MsgCaption   db "Hello",0    ;消息标题
btnkeyinfo   db "You Have Entered Key ",?,0   
char         db "%c",0
LButtonDown  db "Left Button clicked!",0
RButtonDown  db "Right Buttond clicked!",0

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;Data Segment --- Uninitialized data 
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
hInstance  HINSTANCE ?  ;句柄 在Win32下,应用程序的句柄和模块的句柄是一样的。您可以把实例句柄看成是您的应用程序的 ID 号。
CommandLine LPSTR ?


;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;Code Segment
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
start:
invoke GetModuleHandle,NULL  ;获取我们应用程序的句柄
mov hInstance,eax
invoke GetCommandLine  ;您的应用程序不处理命令行那么就无须调用 GetCommandLine,这里只是告诉您如果要调用应该怎么做。
mov CommandLine,eax
invoke WinMain,hInstance,NULL,CommandLine,SW_SHOWNORMAL    ;调用WinMain函数
invoke ExitProcess,eax            ;退出我们的程序,退出代码是从WinMain函数返回的EAX

WinMain proc hInStance:HINSTANCE,hPreInstance:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD

  LOCAL hWnd:HWND      ;创建局部变量
  LOCAL msg:MSG
  LOCAL myWndClass:WNDCLASS
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;窗口类是一个结构题,我们只要像做填空题一样,把结构体中需要填写的内容全部进行相应赋值就可以了。
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  mov myWndClass.style,CS_HREDRAW or CS_VREDRAW
  mov myWndClass.lpfnWndProc,offset myWndProc
  mov myWndClass.cbClsExtra,NULL
  mov myWndClass.cbWndExtra,NULL
  push hInStance
  pop myWndClass.hInstance
  invoke LoadIcon,NULL,IDI_WINLOGO    ;装载图标
  mov myWndClass.hIcon,eax
  invoke LoadCursor,NULL,IDC_HAND     ;装载光标
  mov myWndClass.hCursor,eax
  invoke GetStockObject,WHITE_BRUSH
  mov myWndClass.hbrBackground,eax       ;背景
  mov myWndClass.lpszMenuName,NULL
  mov myWndClass.lpszClassName,offset WndClassName    
  
  invoke RegisterClass,addr myWndClass             ;注册窗口
  invoke CreateWindowEx,NULL,\      ;创建窗口
                        addr WndClassName,\
                        addr WndAppName,\
                        WS_OVERLAPPEDWINDOW, \           ;and (not WS_MAXIMIZEBOX),\  ;如何去除一个其中一个选项
                        CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,\
                        NULL,\
                        NULL,\
                        hInStance,\
                        NULL 
  mov hWnd,eax
  invoke ShowWindow,hWnd,SW_SHOWNORMAL    ;显示窗口
  invoke UpdateWindow,hWnd
  
  .while TRUE          ;循环处理消息
    invoke GetMessage,addr msg,NULL,0,0
    invoke TranslateMessage,addr msg
    invoke DispatchMessage,addr msg
  .endw
  mov eax,msg.wParam
  ret

WinMain endp

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;消息处理部分
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
myWndProc proc hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
  
  LOCAL ps:PAINTSTRUCT
  LOCAL hDc:HDC  
  .if uMsg == WM_DESTROY                  ;窗口销毁消息          
    invoke PostQuitMessage,0
  .elseif uMsg == WM_PAINT                  ;重画消息
      invoke BeginPaint,hWnd,addr ps
      invoke GetDC,hWnd
      mov hDc,eax
      invoke TextOut,hDc,0,0,addr MsgText,(sizeof MsgText)-1
      invoke ReleaseDC,hWnd,hDc
      invoke EndPaint,hWnd,addr ps
  .elseif uMsg == WM_KEYDOWN                 ;键盘键按下消息
    .if wParam == VK_F8       ;F8关闭程序,相当于给程序留一个关闭的后门
      invoke ExitProcess,0
    .endif
    invoke wsprintf,addr btnkeyinfo+21,addr char,wParam      ;参考wsprintf的用法
    invoke MessageBox,hWnd,addr btnkeyinfo,addr MsgCaption,MB_OK
        .elseif uMsg == WM_LBUTTONDOWN                ;鼠标左键按下消息
          invoke MessageBox,hWnd,addr LButtonDown,addr MsgCaption,MB_OK
  .elseif uMsg == WM_RBUTTONDOWN                ;鼠标右键按键消息
    invoke MessageBox,hWnd,addr RButtonDown,addr MsgCaption,MB_OK
  .elseif uMsg == WM_CLOSE                     ;关闭消息
          invoke ExitProcess,0
  .else 
          invoke DefWindowProc,hWnd,uMsg,wParam,lParam              ;默认处理
          ret
  .endif
  xor eax,eax
  ret

myWndProc endp
end start

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
以上代码 经RadASM编译通过,测试成功! (程序只有4K) 我用VC++ Win32 Application 做一个完全一样功能的程序,要168K,果然汇编牛啊
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

引用:
程序心得与总结:
    这个小程序的代码其实很容易理解,如果我已经掌握一门语言,看这个代码其实比较容易的,自己也是比较容易亲自动手写的,其实我一

开始不是很会汇编,特别在16位汇编学习时,觉得挺吃力,当我再次学习Win32汇编时,程序的设计过程和其他高级语言也是有很大相似的,就

拿这个程序来说,我是先会在VC++下如何进行建立一个简单的窗口已经循环处理消息,在VC++中的建立过程和MASM32中其实是一摸一样的顺序

。窗口类的赋值也是一样的,其实语言的表达稍微有点不同而已。如想知道在VC++中如何建立上述的程序,如不知道的话,感兴趣的朋友可以

与我联系,我就不再贴代码了,代码的方法几乎一样。

    下面谈谈这个程序的关键部分或者说需要再次说明的部分。

    数据段,我们有2个段,.data 和.data? 区别就是前者中的数据已经初始化,后者没有。也就是说在 .DATA? 中的变量都是未经初始化的

,这也就是说在程序刚启动时它们的值是什么无关紧要,只不过占有了一块内存,以后可以再利用而已。
    特别注意:WIN32下的实例句柄实际上是您应用程序在内存中的线性地址。

    WNDCLASSEX 中最重要的成员莫过于lpfnWndProc了。前缀 lpfn 表示该成员是一个指向函数的长指针。在 Win32中由于内存模式是 FLAT 

型,所以没有 near 或 far 的区别。每一个窗口类必须有一个窗口过程,当 Windows 把属于特定窗口的消息发送给该窗口时,该窗口的窗口

类负责处理所有的消息,如键盘消息或鼠标消息。由于窗口过程差不多智能地处理了所有的窗口消息循环,所以您只要在其中加入消息处理过

程即可。

    在消息循环中,一定要定义退出消息,否则当你销毁窗口后,程序并没有退出,在任务管理器中进程仍然在的。
    invoke DefWindowProc,hWnd,uMsg,wParam,lParam 这个默认的处理是必须要的,否则你创建的窗口你是看不到的。
     
    最后我们看看 offset 和 addr 之间的区别:

    1,addr不可以处理向前引用,offset则能。所谓向前引用是指:标号的定义是在invoke 语句之后,譬如在如下的例子:
invoke MessageBox,NULL, addr MsgBoxText,addr MsgBoxCaption,MB_OK
...... 
    如果您是用 addr 而不是 offset 的话,那 MASM 就会报错。

    addr 操作符用来把标号的地址传递给被调用的函数,它只能用在 invoke 语句中,譬如您不能用它来把标号的地址赋给寄存器或变量,

如果想这样做则要用 offset 操作符。

    2,addr可以处理局部变量而 offset 则不能。局部变量只是在运行时在堆栈中分配内存空间。而 offset 则是在编译时由编译器解释,这

显然不能用offset 在运行时来分配内存空间。编译器对 addr 的处理是先检查处理的是全局还是局部变量,若是全局变量则把其地址放到目标

文件中,这一点和 offset 相同,若是局部变量,就在执行 invoke 语句前产生如下指令序列: 
lea eax, LocalVar
push eax
因为lea指令能够在运行时决定标号的有效地址,所以有了上述指令序列,就可以保证 invoke 的正确执行了。

    就谈这些吧,让我们共同学习,共同进步!
引用:
源代码下载
Download-Link: 
http://www.live-share.com/files/153022/WinMain_Asm.rar.html