2005年09月27日

        资源编译器用来把资源脚本文件(*.rc)编译成资源文件(*.res),MASM32 软件包中使用的是Visual C++附带的Rc.exe 程序,TASM软件包中使用的是BRC32.exe 或BRCC32.exe,两者的用法都比较简单,它们有相同的命令行语法:

Rc 或BRC32 [选项] 资源脚本文件名
        Rc 和BRC32 在使用中没有必需的选项,不像汇编编译器一样必须使用一些关键的选项。如果编译成功,就会产生以res 为扩展名的资源文件,两者生成的资源文件的格式是一样的。资源文件编写是PE 开发的标准步骤,不同的语言使用的资源编译器以及生成的 .res 文件格式都是一样的,没有汇编格式的资源文件和C 格式的资源文件之分,所以汇编开发包中的资源编译器实际上就是C 开发包中的资源编译器。由于C 语言的使用远比汇编广泛,所以资源脚本文件的语法是C 格式的,如等值定义语句使用#define 而不是汇编常用的equ,注释使用:“//”而不是“”,头文件习惯使用 .h 扩展名而不是 .inc,参数定义有“或”操作时使用“|”操作符而不是汇编的“or”操作符等,这些在使用中必须注意,否则会引起语法错误。两种资源编译器在使用中稍微有所区别,BRC32.exe 内部可以解释Windows 的一些预定义值,所以不用附带头文件,只有遇到最新的预定义时才需要头文件,而Rc 并没有这个功能,所以在脚本文件中必须把头文件Resource.h 包括进去。

         说明,我们要写一个test.exe 文件,生成最后的可执行文件有4 个步骤:
1. 汇编源文件x.asm,其中用到头文件common.inc,它们经Ml.exe 编译成x.obj;
2. 汇编源文件y.asm,用到头文件common.inc 和y.inc,它们经Ml.exe 编译成y.obj;
3. 资源脚本文件x.rc,经Rc.exe 编译成x.res;
4. 最后用Link 将x.obj,y.obj 和x.res 链接成test.exe。
         看出,当程序调试的时候,如果修改了x.asm,也就是说x.obj 的文件时间比x.asm要早,就需要重新进行步骤1 和4;如果修改了y.asm 或y.inc,那么需要重新执行步骤2 和4;如果修改的是x.rc,则步骤3 和4 必须重新执行;如果修改的是common.inc,因为x.asm 和y.asm 都和它有关,所以步骤1、2 和4 都要重新执行;如果同时修改了common.inc 和x.rc,那么必须重复全部步骤。在这个例子中,文件的依赖关系就是:
1. test.exe 依赖于 x.obj,y.obj 和 x.res;
2. x.res 依赖于 x.rc;
3. x.obj 依赖于 x.asm 和 common.inc;
4. y.obj 依赖于 y.asm,common.inc 和 y.inc。

         ke 可以根据文件的时间正确判断文件的新旧并执行相应的步骤。但make 又是如何知道文件之间的依赖关系呢?这需要用户用一个描述文件来指定。前面提到的makefile 就是这个描述文件,执行make 工具的时候,它会默认用makefile 做描述文件名来进行相应的工作,书写描述文件有规定的语法,虽然语法不是很简单,但写好以后就省事多了。

       Ms的make 工具文件名为nmake.exe,它并不是MASM 软件包的一部分,但可以在Visual C++的Bin 目录下找到。Borland 公司的make 工具文件名是make.exe,它已经包括在TASM 5.0 工具包中。两者默认的描述文件名都是makefile,描述文件的语法也大同小异,只是使用时命令行参数有些不同。
在命令行键入nmake /? 可以显示帮助信息,nmake 的语法为:
nmake [选项] [/f 描述文件名] [/x 输出信息文件名] [宏定义] [目标]
说明如下:
/f 参数棗如果描述文件名不使用默认的 makefile,可以用/f 参数指定。
/x 参数棗如果想把屏幕输出的信息存到一个文件中,可以用/x 参数指定(用 DOS下的管道操作符nmake > 文件名的方法无效)。

描述文件一般需要包含以下内容:
● 注释
● 宏定义
● 显式规则
● 隐含规则
在这里,首先为2.4.1 节中有关test.exe 的例子写出一个描述文件,再逐步介绍各部分的书写语法。为了方便使用,一般都把描述文件的文件名取为默认文件名:makefile。这个例子的makefile 文件如下(注意前面括号里的是行号,不是文件的真正内容):
(001) # nmake 工具的描述文件例子
(002) EXE = Test.exe #指定输出文件
(003) OBJS = x.obj \
(004) y.obj #需要的目标文件
(005) RES = x.res #需要的资源文件
(006)
(007) LINK_FLAG = /subsystem:windows #链接选项
(008) ML_FLAG = /c /coff #编译选项
(009)
(010) #定义依赖关系和执行命令
(011) $(EXE): $(OBJS) $(RES)
(012) Link $(LINK_FLAG) /out:$(EXE) $(OBJS) $(RES)
(013) $(OBJS): Common.inc
(014) y.obj: y.inc
(015)
(016) #定义汇编编译和资源编译的默认规则
(017) .asm.obj:
(018) ml $(ML_FLAG) $<
(019) .rc.res:
(020) rc $<
(021)
(022) #清除临时文件
(023) clean:
(024) del *.obj
(025) del *.res

1. 注释和换行
makefile 中的注释是以#号开头一直到行尾的字符,当nmake 工具处理到这些字符的时候,它会完全忽略#号及全部注释字符。
当一行的内容过长的时候,可以用换行符来继续,makefile 的换行符是\,如例子中的第三行和第四行可以合并为:
OBJS = x.obj y.obj #需要的目标文件
在使用换行符的时候要注意在“\”后面不能再加上其他字符,包括注释和空格,否则nmake 检测到“\”不在一行的最后,就不会把它当成换行符解释,就会出现错误。
2. 宏定义
makefile 中允许使用简单的宏定义指代源文件及其相关编译信息,可以把宏称为变量,在整个描述文件中,只要符合下面语法的行就是宏定义:
变量名=变量内容
如上面例子文件中的2 到8 就是宏定义,在引用宏时只需在变量前加$符号,但是要注意的是,如果变量名的长度超过一个字符,在引用时就必须加圆括号(),下面都是有效的宏引用:
$(LINK_FLAG)
$(EXE)
$A
$(A)

其中最后两个引用是完全一致的。

3. 显式规则
makefile 中包含有一些规则,这些规则定义了文件之间的依赖关系和产生命令,一个规
则的格式是这样的:
目标文件:依赖文件;命令 (方法1)

目标文件:依赖文件 (方法2)
命令
在规则定义和命令行中,不能包含注释,例子中的第11 和12 行把宏定义展开后就是:
test.exe:x.obj y.obj x.res
Link /subsystem:windows /out:test.exe x.obj y.obj x.res

        这里的目标文件就是test.exe,它依赖于3 个文件x.obj,y.obj 和x.res,如果有必要,产生目标文件的命令就是下面的Link 命令,整个规则可以用两种方法,用第二种方法的时候,命令可以从第二行开始,第一行的“;”省略,但是这时命令前面必须有一个Tab 字符,否则nmake 无法区分这究竟是命令还是别的定义。目标文件可以有多个,依赖文件也可以有多个,同时命令也可以由多个命令行组成,当然这时候就必须用第二种方法定义了。

        我们也可以用test.exe 生成的规则定义其他文件,如x.obj 或x.res 的生成方法,但nmake如何知道哪个是最终要make 的文件呢?实际上nmake 默认将整个描述文件的第一条规则中的目标文件认为是最终文件,如果我们把11,12 行放到第13 行后面,那么x.obj 和y.obj 的建立规则就成了第一条规则,nmake 建立了x.obj 和x.obj 之后就不理会test.exe 的建立了,所以我们必须把最终需要生成的文件放在第一条规则定义。当然,在nmake 的命令行参数中可以指定要make 的目标,如我们要生成x.res 文件,那么不必修改makefile 将x.res 的描述规则移动到最前面,而是直接在命令行键入以下命令即可:
nmake x.res

4. 隐含规则
        隐含规则可以为某一类的文件指出建立的命令,它具体定义了如何将带一个特定扩展名的文件转换成具有另一种扩展名的文件,定义的格式是:
.源扩展名.目标扩展名:;命令 (方法1)

.源扩展名.目标扩展名: (方法2)
命令

        隐含规则的语法和显式规则相似,也是用“:”隔开,在“;”下面书写命令,也可以不用“;”而将命令写在第二行,同理,这时命令之前要加一个Tab 字符。
       隐含规则不能有依赖文件,所以“:”下面没有内容,例子中的第17、18 行定义了从asm文件建立obj 文件的隐含规则,第19 和20 行定义了从rc 文件建立res 文件的隐含规则,隐含规则中无法指定确定的输入文件名,因为输入文件名是泛指的有相同扩展名的一整类文件,这时候就要用到几个特殊的内定宏来指定文件名,这些宏是$@,$*,$?和$<,它们的含义如下:
$@    全路径的目标文件。
$*      除去扩展名的全路径的目标文件。
$?     所有源文件名。
$<     源文件名(只能用在隐含规则中)。
所以第19、20 行中的rc $< 用于x.rc 的时候就是rc x.rc。

        读者可以注意到一些显式规则没有命令行,如第13 行的“$(OBJS): Common.inc”指出了所有的obj 文件全部依赖于Common.inc 文件,第14 行的“y.obj: y.inc”则指出了y.obj 同时也依赖于y.inc 和第13 行的规则合并,y.obj 依赖于Common.inc 也依赖于y.inc,但是这两条规则都没有指出产生这些obj 文件的命令,所以nmake 处理的时候会到隐含规则中去找命令行,最后会用第18 行的“ml $(ML_FLAG) $<命令去产生这些obj 文件。

        资源文件确实太麻烦了,不说那么多重复的代码,就说那些个x、y坐标,就令人十分的头痛了,仔细翻看一下书的第二章,推荐的是Resource WorkshopVisual C++编辑资源。比较了一下,还是用VC容易多了~,虽然生成的代码太多VC的东西,需要手工删除,但是他毕竟是所见即所得!

资源文件:

//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#include  <resource.h>
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#define ICO_MAIN  0×1000 //图标
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#define IDM_MAIN  0×2000 //菜单
#define IDA_MAIN  0×2000 //加速键
#define IDM_OPEN  0×4101
#define IDM_OPTION  0×4102
#define IDM_EXIT  0×4103  
#define IDM_SETFONT  0×4201
#define IDM_SETCOLOR 0×4202
#define IDM_INACT  0×4203
#define IDM_GRAY  0×4204
#define IDM_BIG   0×4205
#define IDM_SMALL  0×4206
#define IDM_LIST  0×4207
#define IDM_DETAIL  0×4208
#define IDM_TOOLBAR  0×4209
#define IDM_TOOLBARTEXT 0×4210
#define IDM_INPUTBAR 0×4211
#define IDM_STATUSBAR 0×4212
#define IDM_HELP  0×4301
#define IDM_ABOUT  0×4302
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN ICON  "Main.ico"
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
IDM_MAIN menu discardable
BEGIN
 popup "文件(&F)"
 BEGIN
  menuitem "打开文件(&O)…", IDM_OPEN
  menuitem "关闭文件(&C)…", IDM_OPTION
  menuitem separator
  menuitem "退出(&X)",  IDM_EXIT
 END
 popup "查看(&V)"
 BEGIN
  menuitem "字体(&F)…\tAlt+F",IDM_SETFONT
  menuitem "背景色(&B)…\tCtrl+Alt+B",IDM_SETCOLOR
  menuitem separator
  menuitem "被禁用的菜单项", IDM_INACT, INACTIVE
  menuitem "被灰化的菜单项", IDM_GRAY, GRAYED
  menuitem separator
  menuitem "大图标(&G)",  IDM_BIG
  menuitem "小图标(&M)",  IDM_SMALL
  menuitem "列表(&L)",   IDM_LIST
  menuitem "详细资料(&D)",  IDM_DETAIL
  menuitem separator
  popup  "工具栏(&T)"
  BEGIN
     menuitem "标准按钮(&S)",  IDM_TOOLBAR
     menuitem "文字标签(&C)",  IDM_TOOLBARTEXT
     menuitem "命令栏(&I)",  IDM_INPUTBAR
  END
  menuitem "状态栏(&U)",  IDM_STATUSBAR
 END
 popup "帮助(&H)" ,HELP
 BEGIN
  menuitem "帮助主题(&H)\tF1", IDM_HELP
  menuitem separator
  menuitem "关于本程序(&A)…",IDM_ABOUT
 END
END
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
IDA_MAIN accelerators
BEGIN
  VK_F1, IDM_HELP, VIRTKEY
  "B", IDM_SETCOLOR,VIRTKEY,CONTROL,ALT
  "F", IDM_SETFONT,VIRTKEY,ALT
END
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

; Menu.asm
; 菜单资源的使用例子
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 使用 nmake 或下列命令进行编译和链接:
; ml /c /coff Menu.asm
; rc Menu.rc
; Link /subsystem:windows Menu.obj Menu.res
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  .386
  .model flat, stdcall
  option casemap :none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include  windows.inc
include  user32.inc
includelib user32.lib
include  kernel32.inc
includelib kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Equ 等值定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN equ  1000h ;图标
IDM_MAIN equ  2000h ;菜单
IDA_MAIN equ  2000h ;加速键
IDM_OPEN equ  4101h
IDM_OPTION equ  4102h
IDM_EXIT equ  4103h
IDM_SETFONT equ  4201h
IDM_SETCOLOR equ  4202h
IDM_INACT equ  4203h
IDM_GRAY equ  4204h
IDM_BIG  equ  4205h
IDM_SMALL equ  4206h
IDM_LIST equ  4207h
IDM_DETAIL equ  4208h
IDM_TOOLBAR equ  4209h
IDM_TOOLBARTEXT equ  4210h
IDM_INPUTBAR equ  4211h
IDM_STATUSBAR equ  4212h
IDM_HELP equ  4301h
IDM_ABOUT equ  4302h
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  .data?
hInstance dd  ?
hWinMain dd  ?
hMenu  dd  ?
hSubMenu dd  ?
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  .const
szClassName db ’Menu Example’,0
szCaptionMain db ’Menu’,0
szMenuHelp db ’帮助主题(&H)’,0
szMenuAbout db ’关于本程序(&A)…’,0
szCaption db ’菜单选择’,0
szFormat db ’您选择了菜单命令:%08x’,0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  .code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_DisplayMenuItem proc _dwCommandID
   local @szBuffer[256]:byte

  pushad
  invoke wsprintf,addr @szBuffer,addr szFormat,_dwCommandID
  invoke MessageBox,hWinMain,addr @szBuffer,offset szCaption,MB_OK
  popad
  ret

_DisplayMenuItem endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Quit  proc

  invoke DestroyWindow,hWinMain
  invoke PostQuitMessage,NULL
  ret

_Quit  endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_ProcWinMain proc uses ebx edi esi hWnd,uMsg,wParam,lParam
  local @stPos:POINT
  local @hSysMenu

  mov eax,uMsg
  .if eax == WM_CREATE
   invoke GetSubMenu,hMenu,1
   mov hSubMenu,eax
;********************************************************************
; 在系统菜单中添加菜单项
;********************************************************************
   invoke GetSystemMenu,hWnd,FALSE
   mov @hSysMenu,eax
   invoke AppendMenu,@hSysMenu,MF_SEPARATOR,0,NULL
   invoke AppendMenu,@hSysMenu,0,IDM_HELP,offset szMenuHelp
   invoke AppendMenu,@hSysMenu,0,IDM_ABOUT,offset szMenuAbout
;********************************************************************
; 处理菜单及加速键消息
;********************************************************************
  .elseif eax == WM_COMMAND
   invoke _DisplayMenuItem,wParam
   mov eax,wParam
   movzx eax,ax
   .if eax == IDM_EXIT
    call _Quit
   .elseif eax >= IDM_TOOLBAR && eax <= IDM_STATUSBAR
    mov ebx,eax
    invoke GetMenuState,hMenu,ebx,MF_BYCOMMAND
    .if eax == MF_CHECKED
     mov eax,MF_UNCHECKED
    .else
     mov eax,MF_CHECKED
    .endif
    invoke CheckMenuItem,hMenu,ebx,eax
   .elseif eax >= IDM_BIG && eax <= IDM_DETAIL
    invoke CheckMenuRadioItem,hMenu,IDM_BIG,IDM_DETAIL,eax,MF_BYCOMMAND
   .endif
;********************************************************************
; 处理系统菜单消息
;********************************************************************
  .elseif eax == WM_SYSCOMMAND
   mov eax,wParam
   movzx eax,ax
   .if eax == IDM_HELP || eax == IDM_ABOUT
    invoke _DisplayMenuItem,wParam
   .else
    invoke DefWindowProc,hWnd,uMsg,wParam,lParam
    ret
   .endif
;********************************************************************
; 按下右键时弹出一个POPUP菜单
;********************************************************************
  .elseif eax == WM_RBUTTONDOWN
   invoke GetCursorPos,addr @stPos
   invoke TrackPopupMenu,hSubMenu,TPM_LEFTALIGN,@stPos.x,@stPos.y,NULL,hWnd,NULL
;********************************************************************
  .elseif eax == WM_CLOSE
   call _Quit
;********************************************************************
  .else
   invoke DefWindowProc,hWnd,uMsg,wParam,lParam
   ret
  .endif
;********************************************************************
  xor eax,eax
  ret

_ProcWinMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_WinMain proc
  local @stWndClass:WNDCLASSEX
  local @stMsg:MSG
  local @hAccelerator

  invoke GetModuleHandle,NULL
  mov hInstance,eax
  invoke LoadMenu,hInstance,IDM_MAIN
  mov hMenu,eax
  invoke LoadAccelerators,hInstance,IDA_MAIN
  mov @hAccelerator,eax
;********************************************************************
; 注册窗口类
;********************************************************************
  invoke RtlZeroMemory,addr @stWndClass,sizeof @stWndClass
  invoke LoadIcon,hInstance,ICO_MAIN
  mov @stWndClass.hIcon,eax
  mov @stWndClass.hIconSm,eax
  invoke LoadCursor,0,IDC_ARROW
  mov @stWndClass.hCursor,eax
  push hInstance
  pop @stWndClass.hInstance
  mov @stWndClass.cbSize,sizeof WNDCLASSEX
  mov @stWndClass.style,CS_HREDRAW or CS_VREDRAW
  mov @stWndClass.lpfnWndProc,offset _ProcWinMain
  mov @stWndClass.hbrBackground,COLOR_WINDOW + 1
  mov @stWndClass.lpszClassName,offset szClassName
  invoke RegisterClassEx,addr @stWndClass
;********************************************************************
; 建立并显示窗口
;********************************************************************
  invoke CreateWindowEx,WS_EX_CLIENTEDGE,\
   offset szClassName,offset szCaptionMain,\
   WS_OVERLAPPEDWINDOW,\
   100,100,400,300,\
   NULL,hMenu,hInstance,NULL
  mov hWinMain,eax
  invoke ShowWindow,hWinMain,SW_SHOWNORMAL
  invoke UpdateWindow,hWinMain
;********************************************************************
; 消息循环
;********************************************************************
  .while TRUE
   invoke GetMessage,addr @stMsg,NULL,0,0
   .break .if eax == 0
   invoke TranslateAccelerator,hWinMain,@hAccelerator,addr @stMsg
   .if eax == 0
    invoke TranslateMessage,addr @stMsg
    invoke DispatchMessage,addr @stMsg
   .endif
  .endw
  ret

_WinMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
  call _WinMain
  invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  end start

The AppendMenu function appends a new item to the end of the specified menu bar, drop-down menu, submenu, or shortcut menu. You can use this function to specify the content, appearance, and behavior of the menu item.

The AppendMenu function has been superseded by the InsertMenuItem function. You can still use AppendMenu, however, if you do not need any of the extended features of InsertMenuItem.

BOOL AppendMenu(
  HMENU hMenu,      // handle to menu to be changed
  UINT uFlags,      // menu-item flags
  UINT uIDNewItem,  // menu-item identifier or handle to drop-down menu or submenu
  LPCTSTR lpNewItem // menu-item content
);



The GetMenuState function retrieves the menu flags associated with the specified menu 

item. If the menu item opens a submenu, this function also returns the number of items in the submenu. 

The GetMenuState function has been superseded by the GetMenuItemInfo function. You can still use

 GetMenuState, however, if you do not need any of the extended features of GetMenuItemInfo.
UINT GetMenuState(
  HMENU hMenu, // handle to menu
  UINT uId,    // menu item to query
  UINT uFlags  // menu flags
);


The CheckMenuItem function sets the state of the specified menu item's check mark attribute 

to either checked or unchecked. 

The CheckMenuItem function has been superseded by the SetMenuItemInfo function. You can still use 

CheckMenuItem, however, if you do not need any of the extended features of SetMenuItemInfo.
DWORD CheckMenuItem(
  HMENU hmenu,        // handle to menu
  UINT uIDCheckItem,  // menu item to check or uncheck
  UINT uCheck         // menu item flags
);


The   CheckMenuRadioItem function checks a specified menu item and makes it a radio item. At the 

same time, the function unchecks all other menu items in the associated group and clears the 

radio-item type flag for those items.
BOOL CheckMenuRadioItem(
  HMENU hmenu,
  UINT idFirst,
  UINT idLast,
  UINT idCheck,
  UINT uFlags    
);


The   GetCursorPos function retrieves the cursor's position, in screen coordinates. 
BOOL GetCursorPos(
  LPPOINT lpPoint   // address of structure for cursor position
);


The   TrackPopupMenu function displays a shortcut menu at the specified location and tracks the 

selection of items on the menu. The shortcut menu can appear anywhere on the screen.
BOOL TrackPopupMenu(
  HMENU hMenu,         // handle to shortcut menu
  UINT uFlags,         // screen-position and mouse-button flags
  int x,               // horizontal position, in screen coordinates
  int y,               // vertical position, in screen coordinates
  int nReserved,       // reserved, must be zero
  HWND hWnd,           // handle to owner window
  CONST RECT *prcRect  // ignored
);


The   LoadMenu function loads the specified menu resource from the executable (.EXE) file 

associated with an application instance. 
HMENU LoadMenu(
  HINSTANCE hInstance,  // handle to application instance
  LPCTSTR lpMenuName    // menu name string or menu-resource
                        // identifier
);


The   LoadAccelerators function loads the specified accelerator table. 
HACCEL LoadAccelerators(
  HINSTANCE hInstance,  // handle to application instance
  LPCTSTR lpTableName   // address of table-name string
);

2005年09月23日

  .386
  .model flat,stdcall
  option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include  windows.inc
include  gdi32.inc
includelib gdi32.lib
include  user32.inc
includelib user32.lib
include  kernel32.inc
includelib kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  .data?

hInstance dd  ?
hWinMain dd  ?

  .const

szClassName db ’MyClass’,0
szCaptionMain db ’My first Window !’,0
szText  db ’Win32 Assembly, Simple and powerful !’,0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  .code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 窗口过程
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_ProcWinMain proc uses ebx edi esi,hWnd,uMsg,wParam,lParam
  local @stPs:PAINTSTRUCT
  local @stRect:RECT
  local @hDc

  mov eax,uMsg
;********************************************************************
  .if eax == WM_PAINT
   invoke BeginPaint,hWnd,addr @stPs
   mov @hDc,eax

   invoke GetClientRect,hWnd,addr @stRect
   invoke DrawText,@hDc,addr szText,-1,\
    addr @stRect,\
    DT_SINGLELINE or DT_CENTER or DT_VCENTER

   invoke EndPaint,hWnd,addr @stPs
;********************************************************************
  .elseif eax == WM_CLOSE
   invoke DestroyWindow,hWinMain
   invoke PostQuitMessage,NULL
;********************************************************************
  .else
   invoke DefWindowProc,hWnd,uMsg,wParam,lParam
   ret
  .endif
;********************************************************************
  xor eax,eax
  ret

_ProcWinMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_WinMain proc
  local @stWndClass:WNDCLASSEX
  local @stMsg:MSG

  invoke GetModuleHandle,NULL
  mov hInstance,eax
  invoke RtlZeroMemory,addr @stWndClass,sizeof @stWndClass
;********************************************************************
; 注册窗口类
;********************************************************************
  invoke LoadCursor,0,IDC_ARROW
  mov @stWndClass.hCursor,eax
  push hInstance
  pop @stWndClass.hInstance
  mov @stWndClass.cbSize,sizeof WNDCLASSEX
  mov @stWndClass.style,CS_HREDRAW or CS_VREDRAW
  mov @stWndClass.lpfnWndProc,offset _ProcWinMain
  mov @stWndClass.hbrBackground,COLOR_WINDOW + 1
  mov @stWndClass.lpszClassName,offset szClassName
  invoke RegisterClassEx,addr @stWndClass
;********************************************************************
; 建立并显示窗口
;********************************************************************
  invoke CreateWindowEx,WS_EX_CLIENTEDGE,offset szClassName,offset szCaptionMain,\
   WS_OVERLAPPEDWINDOW,\
   100,100,600,400,\
   NULL,NULL,hInstance,NULL
  mov hWinMain,eax
  invoke ShowWindow,hWinMain,SW_SHOWNORMAL
  invoke UpdateWindow,hWinMain
;********************************************************************
; 消息循环
;********************************************************************
  .while TRUE
   invoke GetMessage,addr @stMsg,NULL,0,0
   .break .if eax == 0
   invoke TranslateMessage,addr @stMsg
   invoke DispatchMessage,addr @stMsg
  .endw
  ret

_WinMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
  call _WinMain
  invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  end start

程序的流程也就是常见的windows下的编程,这里简单回顾一下~

GetModuleHandle → RtlZeroMemory → LoadCursor → RegisterClassEx→ CreateWindowEx → ShowWindow → UpdateWindow ;API调用顺序

GetMessage → TranslateMessage → DispatchMessage ;消息循环

(1)得到应用程序的句柄(GetModuleHandle)。
(2)注册窗口类(RegisterClassEx)。在注册之前,要先填写RegisterClassEx 的参数
WNDCLASSEX 结构。
(3)建立窗口(CreateWindowEx)。
(4)显示窗口(ShowWindows)。
(5)刷新窗口客户区(UpdateWindow)。
(6)进入无限的消息获取和处理的循环。首先获取消息(GetMessage),如果有消息到达,则将消息分派到回调函数处理(DispatchMessage),如果消息是WM_QUIT,则退出循环。
        程序的另一半_ProcWinMain 子程序是用来处理消息的,它就是窗口的回调函数(Callback),也叫做窗口过程,之所以是回调函数是因为它是由Windows 而不是我们自己调用的,我们调用DispatchMessage,而DispatchMessage 再回过来调用窗口过程。

        Windows 为每个程序(严格地说是每个线程)维护一个消息队列,Windows 检查系统消息队列里消息的发生位置,当位置位于某个应用程序的窗口范围内的时候,就把这个消息派送到应用程序的消息队列里,如图4.4 中的箭头c 所示。
        当应用程序还没有来取消息的时候,消息就暂时保留在消息队列里,当程序中的消息循环执行到GetMessage 的时候,控制权转移到GetMessage 所在的USER32.DLL 中(箭头1),USER32.DLL 从程序消息队列中取出一条消息(箭头2),然后把这条消息返回应用程序(箭头3)。
应用程序可以对这条消息进行预处理,如可以用TranslateMessage 把基于键盘扫描码的按键消息转换成基于ASCII 码的键盘消息,以后也会用到TranslateAccelerator 把键盘快捷键转换成命令消息,但这个步骤不是必需的。
        然后应用程序将处理这条消息,但方法不是自己直接调用窗口过程来完成,而是通过DispatchMessage 间接调用窗口过程,Dispatch 的英文含义是“分派”,之所以是“分派”,是因为一个程序可能建有不止一个窗口,不同的窗口消息必须分派给相应的窗口过程。当控制权转移到USER32.DLL 中的DispatchMessage 时,DispatchMessage 找出消息对应窗口的窗口过程,然后把消息的具体信息当做参数来调用它(箭头5),窗口过程根据消息找到对应的分支去处理,然后返回(箭头6),这时控制权回到DispatchMessage,最后DispatchMessage 函数返回应用程序(箭头7)。这样,一个循环就结束了,程序又开始新一轮的GetMessage

        应用程序之间也可以互发消息,PostMessage 是把一个消息放到其他程序的消息队列中,如图4.4 中箭头d 所示,目标程序收到了这条消息就把它放入该程序的消息队列去处理;而SendMessage 则越过消息队列直接调用目标程序的窗口过程(箭头I 所示),窗口过程返回以后才从SendMessage 返回(箭头II 所示)。
        窗口过程是由Windows 回调的,Windows 又是怎么知道往哪里回调呢?答案是我们在调用RegisterClassEx 函数的时候告诉了Windows

        hbrBackground  窗口客户区的背景色。前面的 hbr 表示它是一个刷子(Brush)的句柄,"刷子"一词形象地表示了填充一个区域的着色模式。Windows 预定义了一些刷子,如BLACK_BRUSHWHITE_BRUSH 等,可以用下列语句来得到它们的句柄:
invoke GetStockObject, WHITE_BRUSH
        但在这里也可以使用颜色值,Windows 已经预定义了一些颜色值,分别对应窗口各部分的颜色,如COLOR_BACKGROUNDCOLOR_HIGHLIGHTCOLOR_MENU COLOR_WINDOW等,
使用颜色值的时候,Windows 规定必须在颜色值上加1,所以程序中的指令是:

mov @stWndClass.hbrBackground,COLOR_WINDOW + 1

对于不同二进制位组合的计算,“加”和“或”的结果是一样的,在FirstWindow程序中用CS_HREDRAW or CS_VREDRAW 来代表两个组合,若用CS_HREDRAW+CS_VREDRAW 也并没有什么不同,但强烈建议使用or,因为如果不小心指定了两个同样的风格时:CS_HREDRAW or CS_VREDRAW or CS_VREDRAW和原来的数值是一样的,而CS_HREDRAW+CS_VREDRAW+ CS_VREDRAW就不对了,因为1 or 1=1,而1+1 就等于2 了。

窗口间的消息互发

invoke PostMessage,hWnd,Msg,wParam,lParam
invoke SendMessage,hWnd,Msg,wParam,lParam

对于不同的Msg,wParam 和lParam 的含义是不同的,如对于WM_SETTEXT 是:
wParam = 0; // 未定义,必须为0
lParam = (LPARAM)(LPCTSTR)lpsz; // 要设置的字符串地址

具体请看代码:
; Receive.asm
; 从一个程序向另一个窗口程序发送消息 之 消息接收程序
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 使用 nmake 或下列命令进行编译和链接:
; ml /c /coff Receive.asm
; Link /subsystem:windows Receive.obj
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  .386
  .model flat,stdcall
  option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include  windows.inc
include  gdi32.inc
includelib gdi32.lib
include  user32.inc
includelib user32.lib
include  kernel32.inc
includelib kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  .data?

hInstance dd ?
hWinMain dd ?
szBuffer db 512 dup (?)

  .const
szClassName db ’MyClass’,0
szCaptionMain db ’消息接收窗口’,0

szReceive1 db ’收到 WM_SETTEXT 消息!’,0dh,0ah,0dh,0ah
  db ’字符串地址: %08x’,0dh,0ah
  db ’字符串: %s’,0dh,0ah,0
szReceive2 db ’收到 WM_COPYDATA 消息!’,0dh,0ah,0dh,0ah
  db ’数据地址: %d’,0dh,0ah
  db ’数据: %s’,0dh,0ah,0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  .code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 窗口过程
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_ProcWinMain proc uses ebx edi esi,hWnd,uMsg,wParam,lParam

  mov eax,uMsg
;********************************************************************
  .if eax == WM_CLOSE
   invoke DestroyWindow,hWinMain
   invoke PostQuitMessage,NULL
;********************************************************************
; 收到 WM_SETTEXT 消息则将消息字符串和字符串地址显示出来
;********************************************************************
  .elseif eax == WM_SETTEXT
   invoke wsprintf,addr szBuffer,addr szReceive1,\
    lParam,lParam
   invoke MessageBox,hWnd,offset szBuffer,addr szCaptionMain,MB_OK
;********************************************************************
; 收到 WM_COPYDATA 消息将消息附带的数据长度和字符串数据显示出来
;********************************************************************
  .elseif eax == WM_COPYDATA
   mov eax,lParam
   assume eax:ptr COPYDATASTRUCT
   invoke wsprintf,addr szBuffer,addr szReceive2,\
    [eax].lpData,[eax].lpData
   invoke MessageBox,hWnd,offset szBuffer,addr szCaptionMain,MB_OK
   assume eax:nothing
;********************************************************************
  .else
   invoke DefWindowProc,hWnd,uMsg,wParam,lParam
   ret
  .endif
;********************************************************************
  xor eax,eax
  ret

_ProcWinMain endp

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_WinMain proc
  local @stWndClass:WNDCLASSEX
  local @stMsg:MSG

  invoke GetModuleHandle,NULL
  mov hInstance,eax
  invoke RtlZeroMemory,addr @stWndClass,sizeof @stWndClass
;********************************************************************
; 注册窗口类
;********************************************************************
  invoke LoadCursor,0,IDC_ARROW
  mov @stWndClass.hCursor,eax
  push hInstance
  pop @stWndClass.hInstance
  mov @stWndClass.cbSize,sizeof WNDCLASSEX
  mov @stWndClass.style,CS_HREDRAW or CS_VREDRAW
  mov @stWndClass.lpfnWndProc,offset _ProcWinMain
  mov @stWndClass.hbrBackground,COLOR_WINDOW + 1
  mov @stWndClass.lpszClassName,offset szClassName
  invoke RegisterClassEx,addr @stWndClass
;********************************************************************
; 建立并显示窗口
;********************************************************************
  invoke CreateWindowEx,WS_EX_CLIENTEDGE or WS_EX_TOPMOST,offset szClassName,offset szCaptionMain,\
   WS_OVERLAPPEDWINDOW,\
   50,50,200,150,\
   NULL,NULL,hInstance,NULL
  mov hWinMain,eax
  invoke ShowWindow,hWinMain,SW_SHOWNORMAL
  invoke UpdateWindow,hWinMain
;********************************************************************
; 消息循环
;********************************************************************
  .while TRUE
   invoke GetMessage,addr @stMsg,NULL,0,0
   .break .if eax == 0
   invoke TranslateMessage,addr @stMsg
   invoke DispatchMessage,addr @stMsg
  .endw
  ret

_WinMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
  call _WinMain
  invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  end start


; SendMessage.asm
; 从一个程序向另一个窗口程序发送消息 之 发送程序
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 使用 nmake 或下列命令进行编译和链接:
; ml /c /coff SendMessage.asm
; Link /subsystem:windows SendMessage.obj
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  .386
  .model flat,stdcall
  option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include  windows.inc
include  user32.inc
includelib user32.lib
include  kernel32.inc
includelib kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  .data
hWnd  dd ?
szBuffer db 512 dup (?)
stCopyData COPYDATASTRUCT <>

  .const
szCaption db ’发送窗口’,0
szDestClass db ’MyClass’,0 ;目标窗口的窗口类
szStart1 db ’按下“确定”用 SendMessage 发送 WM_SETTEXT 消息!’,0dh,0ah,0dh,0ah
  db ’字符串地址:%08x’,0dh,0ah
  db ’字符串:%s’,0
szStart2 db ’SendMessage 返回!’,0dh,0ah,0dh,0ah
  db ’接下来按“确定”用 SendMessage 发送 WM_COPYDATA 消息!’,0dh,0ah,0dh,0ah
  db ’数据地址:%d’,0dh,0ah
  db ’数据:%s’,0
szReturn2 db ’SendMessage 返回!’,0

szText  db ’这是发送的测试文本’,0
szNotFound db ’目标窗口未找到,请先运行Receive.exe!’,0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  .code
start:
  invoke FindWindow,addr szDestClass,NULL
  .if eax
   mov hWnd,eax ;找到目标窗口则发送消息
;********************************************************************
   invoke wsprintf,addr szBuffer,addr szStart1,addr szText,addr szText
   invoke MessageBox,NULL,offset szBuffer,offset szCaption,MB_OK
   invoke SendMessage,hWnd,WM_SETTEXT,0,addr szText
;********************************************************************
   invoke wsprintf,addr szBuffer,addr szStart2,addr szText,addr szText
   invoke MessageBox,NULL,offset szBuffer,offset szCaption,MB_OK
   mov stCopyData.cbData,sizeof szText
   mov stCopyData.lpData,offset szText
   invoke SendMessage,hWnd,WM_COPYDATA,0,addr stCopyData
   invoke MessageBox,NULL,offset szReturn2,offset szCaption,MB_OK
  .else
   invoke MessageBox,NULL,offset szNotFound,offset szCaption,MB_OK
  .endif
  invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  end start


WM_COPYDATA

The WM_COPYDATA message is sent when an application passes data to another application.

WM_COPYDATA
wParam = (WPARAM) (HWND) hwnd;            // handle of sending window
lParam = (LPARAM) (PCOPYDATASTRUCT) pcds; // pointer to structure with data 

WM_SETTEXT

An application sends a WM_SETTEXT message to set the text of a window.
WM_SETTEXT
wParam = 0;                     // not used; must be zero
lParam = (LPARAM)(LPCTSTR)lpsz; // address of window-text string 

COPYDATASTRUCT

The COPYDATASTRUCT structure contains data to be passed to another application by the WM_COPYDATA message.
typedef struct tagCOPYDATASTRUCT {  // cds
    DWORD dwData;
    DWORD cbData;
    PVOID lpData;
} COPYDATASTRUCT;

FindWindow

The FindWindow function retrieves a handle to the top-level window whose class name and window name match the specified strings. This function does not search child windows. This function does not perform a case-sensitive search.
HWND FindWindow(
  LPCTSTR lpClassName,  // pointer to class name
  LPCTSTR lpWindowName  // pointer to window name
);


; MsgWindow.asm
; 将窗口的消息流程显示到 Notepad 进程的编辑窗口中
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 使用 nmake 或下列命令进行编译和链接:
; ml /c /coff MsgWindow.asm
; Link /subsystem:windows MsgWindow.obj
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  .386
  .model flat,stdcall
  option casemap:none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include  windows.inc
include  gdi32.inc
includelib gdi32.lib
include  user32.inc
includelib user32.lib
include  kernel32.inc
includelib kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  .data?

hInstance dd ?
hWinMain dd ?
  .const
szClassName db 'MyClass',0
szCaptionMain db 'Message Tester',0
;********************************************************************
; 消息ID列表
;********************************************************************
dwMsgTable dd WM_NULL
  dd WM_CREATE
  dd WM_DESTROY
  dd WM_MOVE
  dd WM_SIZE
  dd WM_ACTIVATE
  dd WM_SETFOCUS
  dd WM_KILLFOCUS
  dd WM_ENABLE
  dd WM_SETREDRAW
  dd WM_SETTEXT
  dd WM_GETTEXT
  dd WM_GETTEXTLENGTH
  dd WM_PAINT
  dd WM_CLOSE
  dd WM_QUERYENDSESSION
  dd WM_QUIT
  dd WM_QUERYOPEN
  dd WM_ERASEBKGND
  dd WM_SYSCOLORCHANGE
  dd WM_ENDSESSION
  dd WM_SHOWWINDOW
  dd WM_WININICHANGE
  dd WM_DEVMODECHANGE
  dd WM_ACTIVATEAPP
  dd WM_FONTCHANGE
  dd WM_TIMECHANGE
  dd WM_CANCELMODE
  dd WM_SETCURSOR
  dd WM_MOUSEACTIVATE
  dd WM_CHILDACTIVATE
  dd WM_QUEUESYNC
  dd WM_GETMINMAXINFO
  dd WM_PAINTICON
  dd WM_ICONERASEBKGND
  dd WM_NEXTDLGCTL
  dd WM_SPOOLERSTATUS
  dd WM_DRAWITEM
  dd WM_MEASUREITEM
  dd WM_DELETEITEM
  dd WM_VKEYTOITEM
  dd WM_CHARTOITEM
  dd WM_SETFONT
  dd WM_GETFONT
  dd WM_SETHOTKEY
  dd WM_GETHOTKEY
  dd WM_QUERYDRAGICON
  dd WM_COMPAREITEM
  dd WM_GETOBJECT
  dd WM_COMPACTING
  dd WM_OTHERWINDOWCREATED
  dd WM_OTHERWINDOWDESTROYED
  dd WM_COMMNOTIFY
  dd WM_WINDOWPOSCHANGING
  dd WM_WINDOWPOSCHANGED
  dd WM_POWER
  dd WM_COPYDATA
  dd WM_CANCELJOURNAL
  dd WM_NOTIFY
  dd WM_INPUTLANGCHANGEREQUEST
  dd WM_INPUTLANGCHANGE
  dd WM_TCARD
  dd WM_HELP
  dd WM_USERCHANGED
  dd WM_NOTIFYFORMAT
  dd WM_CONTEXTMENU
  dd WM_STYLECHANGING
  dd WM_STYLECHANGED
  dd WM_DISPLAYCHANGE
  dd WM_GETICON
  dd WM_SETICON
  dd WM_NCCREATE
  dd WM_NCDESTROY
  dd WM_NCCALCSIZE
  dd WM_NCHITTEST
  dd WM_NCPAINT
  dd WM_NCACTIVATE
  dd WM_GETDLGCODE
  dd WM_SYNCPAINT
  dd WM_NCMOUSEMOVE
  dd WM_NCLBUTTONDOWN
  dd WM_NCLBUTTONUP
  dd WM_NCLBUTTONDBLCLK
  dd WM_NCRBUTTONDOWN
  dd WM_NCRBUTTONUP
  dd WM_NCRBUTTONDBLCLK
  dd WM_NCMBUTTONDOWN
  dd WM_NCMBUTTONUP
  dd WM_NCMBUTTONDBLCLK
  dd WM_KEYDOWN
  dd WM_KEYUP
  dd WM_CHAR
  dd WM_DEADCHAR
  dd WM_SYSKEYDOWN
  dd WM_SYSKEYUP
  dd WM_SYSCHAR
  dd WM_SYSDEADCHAR
  dd WM_KEYLAST
  dd WM_INITDIALOG
  dd WM_COMMAND
  dd WM_SYSCOMMAND
  dd WM_TIMER
  dd WM_HSCROLL
  dd WM_VSCROLL
  dd WM_INITMENU
  dd WM_INITMENUPOPUP
  dd WM_MENUSELECT
  dd WM_MENUCHAR
  dd WM_ENTERIDLE
  dd WM_CTLCOLORMSGBOX
  dd WM_CTLCOLOREDIT
  dd WM_CTLCOLORLISTBOX
  dd WM_CTLCOLORBTN
  dd WM_CTLCOLORDLG
  dd WM_CTLCOLORSCROLLBAR
  dd WM_CTLCOLORSTATIC
  dd WM_MOUSEMOVE
  dd WM_LBUTTONDOWN
  dd WM_LBUTTONUP
  dd WM_LBUTTONDBLCLK
  dd WM_RBUTTONDOWN
  dd WM_RBUTTONUP
  dd WM_RBUTTONDBLCLK
  dd WM_MBUTTONDOWN
  dd WM_MBUTTONUP
  dd WM_MBUTTONDBLCLK
  dd WM_MOUSELAST
  dd WM_PARENTNOTIFY
  dd WM_ENTERMENULOOP
  dd WM_EXITMENULOOP
  dd WM_MDICREATE
  dd WM_MDIDESTROY
  dd WM_MDIACTIVATE
  dd WM_MDIRESTORE
  dd WM_MDINEXT
  dd WM_MDIMAXIMIZE
  dd WM_MDITILE
  dd WM_MDICASCADE
  dd WM_MDIICONARRANGE
  dd WM_MDIGETACTIVE
  dd WM_MDISETMENU
  dd WM_DROPFILES
  dd WM_MDIREFRESHMENU
  dd WM_CUT
  dd WM_COPY
  dd WM_PASTE
  dd WM_CLEAR
  dd WM_UNDO
  dd WM_RENDERFORMAT
  dd WM_RENDERALLFORMATS
  dd WM_DESTROYCLIPBOARD
  dd WM_DRAWCLIPBOARD
  dd WM_PAINTCLIPBOARD
  dd WM_VSCROLLCLIPBOARD
  dd WM_SIZECLIPBOARD
  dd WM_ASKCBFORMATNAME
  dd WM_CHANGECBCHAIN
  dd WM_HSCROLLCLIPBOARD
  dd WM_QUERYNEWPALETTE
  dd WM_PALETTEISCHANGING
  dd WM_PALETTECHANGED
  dd WM_HOTKEY
  dd WM_PRINT
  dd WM_PRINTCLIENT
  dd WM_PENWINFIRST
  dd WM_PENWINLAST
  dd WM_MENURBUTTONUP
  dd WM_MENUDRAG
  dd WM_MENUGETOBJECT
  dd WM_UNINITMENUPOPUP
  dd WM_MENUCOMMAND
  dd WM_NEXTMENU
  dd WM_SIZING
  dd WM_CAPTURECHANGED
  dd WM_MOVING
  dd WM_POWERBROADCAST
  dd WM_DEVICECHANGE
  dd WM_ENTERSIZEMOVE
  dd WM_EXITSIZEMOVE
MSG_TABLE_LEN equ ($ - dwMsgTable)/sizeof dword
;********************************************************************
; 消息名称字符串列表
;********************************************************************
MSG_STRING_LEN equ sizeof szStringTable
szStringTable db 'WM_NULL                  ',0
  db 'WM_CREATE                ',0
  db 'WM_DESTROY               ',0
  db 'WM_MOVE                  ',0
  db 'WM_SIZE                  ',0
  db 'WM_ACTIVATE              ',0
  db 'WM_SETFOCUS              ',0
  db 'WM_KILLFOCUS             ',0
  db 'WM_ENABLE                ',0
  db 'WM_SETREDRAW             ',0
  db 'WM_SETTEXT               ',0
  db 'WM_GETTEXT               ',0
  db 'WM_GETTEXTLENGTH         ',0
  db 'WM_PAINT                 ',0
  db 'WM_CLOSE                 ',0
  db 'WM_QUERYENDSESSION       ',0
  db 'WM_QUIT                  ',0
  db 'WM_QUERYOPEN             ',0
  db 'WM_ERASEBKGND            ',0
  db 'WM_SYSCOLORCHANGE        ',0
  db 'WM_ENDSESSION            ',0
  db 'WM_SHOWWINDOW            ',0
  db 'WM_WININICHANGE          ',0
  db 'WM_DEVMODECHANGE         ',0
  db 'WM_ACTIVATEAPP           ',0
  db 'WM_FONTCHANGE            ',0
  db 'WM_TIMECHANGE            ',0
  db 'WM_CANCELMODE            ',0
  db 'WM_SETCURSOR             ',0
  db 'WM_MOUSEACTIVATE         ',0
  db 'WM_CHILDACTIVATE         ',0
  db 'WM_QUEUESYNC             ',0
  db 'WM_GETMINMAXINFO         ',0
  db 'WM_PAINTICON             ',0
  db 'WM_ICONERASEBKGND        ',0
  db 'WM_NEXTDLGCTL            ',0
  db 'WM_SPOOLERSTATUS         ',0
  db 'WM_DRAWITEM              ',0
  db 'WM_MEASUREITEM           ',0
  db 'WM_DELETEITEM            ',0
  db 'WM_VKEYTOITEM            ',0
  db 'WM_CHARTOITEM            ',0
  db 'WM_SETFONT               ',0
  db 'WM_GETFONT               ',0
  db 'WM_SETHOTKEY             ',0
  db 'WM_GETHOTKEY             ',0
  db 'WM_QUERYDRAGICON         ',0
  db 'WM_COMPAREITEM           ',0
  db 'WM_GETOBJECT             ',0
  db 'WM_COMPACTING            ',0
  db 'WM_OTHERWINDOWCREATED    ',0
  db 'WM_OTHERWINDOWDESTROYED  ',0
  db 'WM_COMMNOTIFY            ',0
  db 'WM_WINDOWPOSCHANGING     ',0
  db 'WM_WINDOWPOSCHANGED      ',0
  db 'WM_POWER                 ',0
  db 'WM_COPYDATA              ',0
  db 'WM_CANCELJOURNAL         ',0
  db 'WM_NOTIFY                ',0
  db 'WM_INPUTLANGCHANGEREQUEST',0
  db 'WM_INPUTLANGCHANGE       ',0
  db 'WM_TCARD                 ',0
  db 'WM_HELP                  ',0
  db 'WM_USERCHANGED           ',0
  db 'WM_NOTIFYFORMAT          ',0
  db 'WM_CONTEXTMENU           ',0
  db 'WM_STYLECHANGING         ',0
  db 'WM_STYLECHANGED          ',0
  db 'WM_DISPLAYCHANGE         ',0
  db 'WM_GETICON               ',0
  db 'WM_SETICON               ',0
  db 'WM_NCCREATE              ',0
  db 'WM_NCDESTROY             ',0
  db 'WM_NCCALCSIZE            ',0
  db 'WM_NCHITTEST             ',0
  db 'WM_NCPAINT               ',0
  db 'WM_NCACTIVATE            ',0
  db 'WM_GETDLGCODE            ',0
  db 'WM_SYNCPAINT             ',0
  db 'WM_NCMOUSEMOVE           ',0
  db 'WM_NCLBUTTONDOWN         ',0
  db 'WM_NCLBUTTONUP           ',0
  db 'WM_NCLBUTTONDBLCLK       ',0
  db 'WM_NCRBUTTONDOWN         ',0
  db 'WM_NCRBUTTONUP           ',0
  db 'WM_NCRBUTTONDBLCLK       ',0
  db 'WM_NCMBUTTONDOWN         ',0
  db 'WM_NCMBUTTONUP           ',0
  db 'WM_NCMBUTTONDBLCLK       ',0
  db 'WM_KEYDOWN               ',0
  db 'WM_KEYUP                 ',0
  db 'WM_CHAR                  ',0
  db 'WM_DEADCHAR              ',0
  db 'WM_SYSKEYDOWN            ',0
  db 'WM_SYSKEYUP              ',0
  db 'WM_SYSCHAR               ',0
  db 'WM_SYSDEADCHAR           ',0
  db 'WM_KEYLAST               ',0
  db 'WM_INITDIALOG            ',0
  db 'WM_COMMAND               ',0
  db 'WM_SYSCOMMAND            ',0
  db 'WM_TIMER                 ',0
  db 'WM_HSCROLL               ',0
  db 'WM_VSCROLL               ',0
  db 'WM_INITMENU              ',0
  db 'WM_INITMENUPOPUP         ',0
  db 'WM_MENUSELECT            ',0
  db 'WM_MENUCHAR              ',0
  db 'WM_ENTERIDLE             ',0
  db 'WM_CTLCOLORMSGBOX        ',0
  db 'WM_CTLCOLOREDIT          ',0
  db 'WM_CTLCOLORLISTBOX       ',0
  db 'WM_CTLCOLORBTN           ',0
  db 'WM_CTLCOLORDLG           ',0
  db 'WM_CTLCOLORSCROLLBAR     ',0
  db 'WM_CTLCOLORSTATIC        ',0
  db 'WM_MOUSEMOVE             ',0
  db 'WM_LBUTTONDOWN           ',0
  db 'WM_LBUTTONUP             ',0
  db 'WM_LBUTTONDBLCLK         ',0
  db 'WM_RBUTTONDOWN           ',0
  db 'WM_RBUTTONUP             ',0
  db 'WM_RBUTTONDBLCLK         ',0
  db 'WM_MBUTTONDOWN           ',0
  db 'WM_MBUTTONUP             ',0
  db 'WM_MBUTTONDBLCLK         ',0
  db 'WM_MOUSELAST             ',0
  db 'WM_PARENTNOTIFY          ',0
  db 'WM_ENTERMENULOOP         ',0
  db 'WM_EXITMENULOOP          ',0
  db 'WM_MDICREATE             ',0
  db 'WM_MDIDESTROY            ',0
  db 'WM_MDIACTIVATE           ',0
  db 'WM_MDIRESTORE            ',0
  db 'WM_MDINEXT               ',0
  db 'WM_MDIMAXIMIZE           ',0
  db 'WM_MDITILE               ',0
  db 'WM_MDICASCADE            ',0
  db 'WM_MDIICONARRANGE        ',0
  db 'WM_MDIGETACTIVE          ',0
  db 'WM_MDISETMENU            ',0
  db 'WM_DROPFILES             ',0
  db 'WM_MDIREFRESHMENU        ',0
  db 'WM_CUT                   ',0
  db 'WM_COPY                  ',0
  db 'WM_PASTE                 ',0
  db 'WM_CLEAR                 ',0
  db 'WM_UNDO                  ',0
  db 'WM_RENDERFORMAT          ',0
  db 'WM_RENDERALLFORMATS      ',0
  db 'WM_DESTROYCLIPBOARD      ',0
  db 'WM_DRAWCLIPBOARD         ',0
  db 'WM_PAINTCLIPBOARD        ',0
  db 'WM_VSCROLLCLIPBOARD      ',0
  db 'WM_SIZECLIPBOARD         ',0
  db 'WM_ASKCBFORMATNAME       ',0
  db 'WM_CHANGECBCHAIN         ',0
  db 'WM_HSCROLLCLIPBOARD      ',0
  db 'WM_QUERYNEWPALETTE       ',0
  db 'WM_PALETTEISCHANGING     ',0
  db 'WM_PALETTECHANGED        ',0
  db 'WM_HOTKEY                ',0
  db 'WM_PRINT                 ',0
  db 'WM_PRINTCLIENT           ',0
  db 'WM_PENWINFIRST           ',0
  db 'WM_PENWINLAST            ',0
  db 'WM_MENURBUTTONUP         ',0
  db 'WM_MENUDRAG              ',0
  db 'WM_MENUGETOBJECT         ',0
  db 'WM_UNINITMENUPOPUP       ',0
  db 'WM_MENUCOMMAND           ',0
  db 'WM_NEXTMENU              ',0
  db 'WM_SIZING                ',0
  db 'WM_CAPTURECHANGED        ',0
  db 'WM_MOVING                ',0
  db 'WM_POWERBROADCAST        ',0
  db 'WM_DEVICECHANGE          ',0
  db 'WM_ENTERSIZEMOVE         ',0
  db 'WM_EXITSIZEMOVE          ',0
;********************************************************************
szDestClass db 'Notepad',0
szFormat db 'WndProc: [%04x]%s %08x %08x',0dh,0
szCreateWindow1 db 'Creating Window...',0dh,0
szCreateWindow2 db 'CreateWindow end',0dh,0
szShowWindow1 db 'Showing Window...',0dh,0
szShowWindow2 db 'ShowWindow end',0dh,0
szUpdateWindow1 db 'Updating Window...',0dh,0
szUpdateWindow2 db 'UpdateWindow end',0dh,0
szGetMsg1 db 'Getting Message...',0dh,0
szGetMsg2 db '[%04x]Message gotten',0dh,0
szDispatchMsg1 db 'Dispatching Message...',0dh,0
szDispatchMsg2 db 'DispatchMessage end',0dh,0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  .code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_SendtoNotepad proc _lpsz
  local @hWinNotepad
  pushad
  invoke FindWindow,addr szDestClass,NULL
  .if eax
   mov ecx,eax
   invoke ChildWindowFromPoint,ecx,20,20
  .endif
  .if eax
   mov @hWinNotepad,eax
   mov esi,_lpsz
   @@:
   lodsb
   or al,al
   jz @F
   movzx eax,al
   invoke PostMessage,@hWinNotepad,WM_CHAR,eax,1
   jmp @B
   @@:
  .endif
  popad
  ret
_SendtoNotepad endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_ShowMessage proc _uMsg,_wParam,_lParam
  local @szBuffer[128]:byte
  pushad
;********************************************************************
; 查找消息的说明字符串
;********************************************************************
  mov eax,_uMsg
  mov edi,offset dwMsgTable
  mov ecx,MSG_TABLE_LEN
  cld
  repnz scasd
  .if ZERO?
   sub edi,offset dwMsgTable + sizeof dword
   shr edi,2
   mov eax,edi
   mov ecx,MSG_STRING_LEN
   mul ecx
   add eax,offset szStringTable
;********************************************************************
; 翻译格式并发送到 Notepad 窗口
;********************************************************************
   invoke wsprintf,addr @szBuffer,addr szFormat,\
    _uMsg,eax,_wParam,_lParam
   invoke _SendtoNotepad,addr @szBuffer
  .endif
  popad
  ret
_ShowMessage endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 窗口过程
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_ProcWinMain proc uses ebx edi esi,hWnd,uMsg,wParam,lParam
  invoke _ShowMessage,uMsg,wParam,lParam
  mov eax,uMsg
;********************************************************************
  .if eax == WM_CLOSE
   invoke DestroyWindow,hWinMain
   invoke PostQuitMessage,NULL
;********************************************************************
  .else
   invoke DefWindowProc,hWnd,uMsg,wParam,lParam
   ret
  .endif
;********************************************************************
  xor eax,eax
  ret
_ProcWinMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_WinMain proc
  local @szBuffer[128]:byte
  local @stWndClass:WNDCLASSEX
  local @stMsg:MSG
  invoke GetModuleHandle,NULL
  mov hInstance,eax
  invoke RtlZeroMemory,addr @stWndClass,sizeof @stWndClass
;********************************************************************
; 注册窗口类
;********************************************************************
  invoke LoadCursor,0,IDC_ARROW
  mov @stWndClass.hCursor,eax
  push hInstance
  pop @stWndClass.hInstance
  mov @stWndClass.cbSize,sizeof WNDCLASSEX
  mov @stWndClass.style,CS_HREDRAW or CS_VREDRAW
  mov @stWndClass.lpfnWndProc,offset _ProcWinMain
  mov @stWndClass.hbrBackground,COLOR_WINDOW + 1
  mov @stWndClass.lpszClassName,offset szClassName
  invoke RegisterClassEx,addr @stWndClass
;********************************************************************
; 建立并显示窗口
;********************************************************************
  invoke _SendtoNotepad,addr szCreateWindow1
  invoke CreateWindowEx,WS_EX_CLIENTEDGE ,offset szClassName,offset szCaptionMain,\
   WS_OVERLAPPEDWINDOW,\
   50,50,100,100,\
   NULL,NULL,hInstance,NULL
  mov hWinMain,eax
  invoke _SendtoNotepad,addr szCreateWindow2
  invoke _SendtoNotepad,addr szShowWindow1
  invoke ShowWindow,hWinMain,SW_SHOWNORMAL
  invoke _SendtoNotepad,addr szShowWindow2
  invoke _SendtoNotepad,addr szUpdateWindow1
  invoke UpdateWindow,hWinMain
  invoke _SendtoNotepad,addr szUpdateWindow2
;********************************************************************
; 消息循环
;********************************************************************
  .while TRUE
   invoke _SendtoNotepad,addr szGetMsg1
   invoke GetMessage,addr @stMsg,NULL,0,0
   push eax
   invoke wsprintf,addr @szBuffer,addr szGetMsg2,@stMsg.message
   invoke _SendtoNotepad,addr @szBuffer
   pop eax
   .break .if eax == 0
   invoke TranslateMessage,addr @stMsg
   invoke _SendtoNotepad,addr szDispatchMsg1
   invoke DispatchMessage,addr @stMsg
   invoke _SendtoNotepad,addr szDispatchMsg2
  .endw
  ret
_WinMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
  call _WinMain
  invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  end start

2005年09月22日

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Hello.asm
; 使用 Win32ASM 写的 Hello, world 程序
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 使用 nmake 或下列命令进行编译和链接:
; ml /c /coff Hello.asm
; Link /subsystem:windows Hello.obj
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

.386
.model flat,stdcall
option casemap:none

include  windows.inc
include  user32.inc
includelib user32.lib
include  kernel32.inc
includelib kernel32.lib

  .data

szCaption db ‘A MessageBox !’,0
szText  db ‘Hello, World !’,0

  .code
start:
  invoke MessageBox,NULL,offset szText,offset szCaption,MB_YESNO
  invoke ExitProcess,NULL
end start

  .386  ;指定指令集,若是带有.386p的话,则表示可使用特权指令 

 .model flat,stdcall ;定义程序的工作模式,对于Win32程序来说,只有一种内存模式,即flat模式。如果定义了 .model flat,MASM 自动为各种段寄存器做了如下定义(Assume cs:FlAT,ds:Flat,ss:FALT,es:FLAT,fs:ERROR,gs:ERROR)。也就是说,CS,DS,ES 和SS 段全部使用平坦模式,FS 和GS 寄存器默认不使用,这时若在源程序中使用FS 或GS,在编译时会报错。如果有必要使用它们,只需在使用前用下面的语句声明一下就可以了(Assume fs:nothing,gs:nothing或者是Assume fs:flat,gs:flat)。而stdcall指出了调用子程序或Win32 API 时参数传递的次序和堆栈平衡的方法,相对于stdcall,不同的语言类型还有C,SysCall,BASIC,FORTRAN 和PASCAL,虽然各种高级语言在调用子程序时都是使用堆栈来传递参数,但它们的处理方法各有不同。
  option casemap:none;用option 语句定义的选项有很多,如option language 定义和option segment 定义等,在Win32 汇编程序中,需要的只是定义option casemap:none,这个语句定义了程序中的变量和子程序名是否对大小写敏感,由于Win32 API 中的API 名称是区分大小写的,所以必须指定这个选项,否则在调用API 的时候会有问题。
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include  windows.inc;inc文件包含一些函数声明
include  user32.inc
includelib user32.lib ;告诉链接程序使用了哪些函数库
include  kernel32.inc
includelib kernel32.lib

;为了使程序更有移植性,在源程序中一般不直接指明使用Unicode 还是ANSI 版本,而是使用宏汇编中的条件汇编功能来统一替换,如在源程序中使用Messagebox,但在头文件中定义:
if UNICODE
MessageBox equ <MessageBoxW>
else
MessageBox equ <MessageBoxA>
endif
所有涉及版本问题的API 都可以按此方法定义,然后在源程序的头部指定UNICODE=1 或UNICODE=0,重新编译后就能产生不同的版本。

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;Win32 中实际上只有代码和数据之分,.data.data?.const 是数据段.code 是代码段,和DOS 汇编不同,Win32 汇编不必考虑堆栈,系统会为程序分配一个向下扩展的、足够大的段作为堆栈段,所以 .stack 段定义常常被忽略。

;程序中的数据定义一般可以归纳为3 类:

第一类是可读可写的已定义变量。这些数据在源程序中已经被定义了初始值,而且在程序的执行中有可能被更改,如一些标志等,这些数据必须定义在 .data 段中,.data 段是已初始化数据段,其中定义的数据是可读可写的,在程序装入完成的时候,这些值就已经在内存中了.data 段存放在可执行文件的_DATA 节区内。

第二类是可读可写的未定义变量。这些变量一般是当做缓冲区或者在程序执行后才开始使用的,这些数据可以定义在 .data 段中,也可以定义在 .data?段中,但一般把它放到 .data?段中。虽然定义在这两种段中都可以正常使用,但定义在 .data?段中不会增大 .exe 文件的大小。

第三类数据是一些常量。如一些要显示的字符串信息,它们在程序装入的时候也已经有效,但在整个执行过程中不需要修改,这些数据可以放在 .const 段中.const 段是常量段,它是可读不可写的。。一般为了方便起见,在小程序中常常把常量一起定义到 .data 段中,而不另外定义一 .const 段。在程序中如果不小心写了对 .const 段中的数据做写操作的指令,会引起保护错误,Windows 会显示一个如图3.2 所示的提示框并结束程序。

;如果不怕程序可读性不佳的话,把 .const 段中定义的东西混到 .code 段中去也可以正常
使用,因为 .code 段也是可以读的。

  .data

szCaption db ’A MessageBox !’,0
szText  db ’Hello, World !’,0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;.code 段是代码段,所有的指令都必须写在代码段中,在可执行文件中,代码段是放在_TEXT 节区中的。Win32 环境中的数据段是不可执行的,只有代码段有可执行的属性。对于工作在特权级3 的应用程序来说.code 段是不可写的。
  .code
start: ;相当于程序入口

;一个源程序不必非要指定入口标号,这时候可以把开始地址忽略不写,这种情况发生在编写多模块程序的单个模块的时候。当分开写多个程序模块时,每个模块的源程序中也可以包括 .data.data?.const .code 段,结构就和上面的Win32 Hello World 一样,只是其他模块最后的end 语句必须不带开始地址。当最后把多个模块链接在一起的时候,只能有一个主模块指定入口地址,在多个模块中指定入口地址或者没有一个模块指定了入口地址,链接程序都会报错。
  invoke MessageBox,NULL,offset szText,offset szCaption,MB_YESNO
  invoke ExitProcess,NULL ;利用invoke伪指令,实现调用API
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
  end start ;所有的代码都必须在end 之前完成

   

变量的类型
 字节  byte  db  1
   word  dw  2
 双字  dword  dd  4
 三字  fword  df  6
 四字   qword  dq  8
 十字节BCD 码   tbyte  dt  10
 有符号字节  signbyte  sbyte  1
 有符号字  signword  sword  2
 有符号双字  signdword  sdword  4
 单精度浮点数   real4  real4  4
 双精度浮点数   real8  real8  8
 10 字节浮点数  real10  real10  10

         全局变量的作用域是整个程序,Win32 汇编的全局变量定义在 .data.data?段内,可以
同时定义变量的类型和长度,格式是:
变量名 类型 初始值1,初始值2,……
变量名 类型 重复数量 dup (初始值1,初始值2,……)

         所有使用到变量类型的情况中,只有定义全局变量的时候类型才可以用缩写

         全局变量在定义中既可以指定初值,也可以只用问号预留空间,在 .data?段中,只能用
问号预留空间,因为 .data?不能指定初始值,这里就有一个问题:既然可以用问号预留空间,
那么在实际运行的时候,这个未初始化的值是随机的还是确定的呢?在全局变量中,这个值
就是0,所以用问号指定的全局变量如果要以0 为初始值的话,在程序中可以不必为它赋值。

         局部变量的作用域是单个子程序,在进入子程序的时候,通过修改堆栈指针esp 来预留
出需要的空间,在用ret 指令返回主程序之前,同样通过恢复esp 丢弃这些空间,这些变量就
随之无效了。它的缺点就是因为空间是临时分配的,所以无法定义含有初始化值的变量,对
局部变量的初始化一般在子程序中由指令完成。

         local 变量名1[[重复数量]][:类型],变量名2[[重复数量]][:类型]……

local loc1[1024]:byte ;例1,定义了一个 1 024 字节长的局部变量 loc1。
local loc2 ;例2,
定义了一个名为 loc2 的局部变量,类型是默认值 dword。
local loc3:WNDCLASS ;例3,
定义了一个 WNDCLASS 数据结构,名为 loc3。

         需要注意的是,既然局部变量的空间临时分配的,那么必然需要在堆栈上进行操作,以便完成变量的赋值与释放。下面这两段代码展示了局部变量是如何在堆栈中操作的:

TestProc proc  ;由call调用,这时call指令会把返回地址堆入栈(返回地址存放在ebp+4),之后,原来的ebp值也会入栈,地址为ebp
local @loc1:dword,@loc2:word
local @loc3:byte
mov eax,@loc1;第一个局部变量,入栈地址为ebp-4
mov ax,@loc2;第二个局部变量,入栈地址为ebp-6
mov al,@loc3;第三个局部变量,入栈地址为ebp-7
ret
TestProc endp

:00401000 55 push ebp ;保护ebp值,入栈
:00401001 8BEC mov ebp, esp ;由于进入子程序后,esp值经常会被改变,故程序使用ebp作指针
:00401003 83C4F8 add esp, FFFFFFF8
:00401006 8B45FC mov eax, dword ptr [ebp-04]
:00401009 668B45FA mov ax, word ptr [ebp-06]
:0040100D 8A45F9 mov al, byte ptr [ebp-07]
:00401010 C9 leave ;伪指令,完成mov esp,ebp,pop ebp操作
:00401011 C3 ret

         程序退出的时候,必须把正确的esp 设置回去,否则,ret 指令会从堆栈中取出错误的
地址返回,看程序可以发现,ebp 就是正确的esp 值,因为子程序开始的时候已经有一句mov
ebp,esp
,所以要返回的时候只要先mov esp,ebp,然后再pop ebp,堆栈就是正确的了。
         在80386 指令集中有一条指令可以在一句中实现这个功能,就是leave 指令,所以,编译器在ret 指令之前只使用了一句leave 指令。明白了局部变量使用的原理,就很容易理解使用时的注意点:ebp 寄存器是关键,它起到保存原始esp 的作用,并随时用做存取局部变量的指针基址,所以在任何时刻,不要尝试把ebp 用于别的用途,否则会带来意想不到的后果。
         Win32 汇编中局部变量的使用方法可以解释一个很有趣的现象:在DOS 汇编的时候,如果在子程序中的push 指令和pop 指令不配对,那么返回的时候ret 指令从堆栈里得到的肯定是错误的返回地址,程序也就死掉了。但在Win32 汇编中,push 指令和pop指令不配对可能在逻辑上产生错误,却不会影响子程序正常返回,原因就是在返回的时候esp 不是靠相同数量的pushpop 指令来保持一致的,而是靠leave 指令从保存在ebp中的原始值中取回来的,也就是说,即使把esp 改得一塌糊涂也不会影响到子程序的返回,当然,"窍门"就在ebp,把ebp 改掉,程序就玩完了!

         局部变量是无法在定义的时候指定初始化值的,因为local 伪指令只是简单地把空间给留出来,那么开始使用时它里面是什么值呢?和全局变量不一样,局部变量的起始值是随机的,是其他子程序执行后在堆栈里留下的垃圾,所以,对局部变量的值一定要初始化,特别是定义为结构后当参数传递给API 函数的时候。

sizeof 伪指令可以取得变量数据类型数据结构以字节为单位的长度,lengthof 可以取得变量中数据的项数;注意二者不同的使用范围!

如果为了把Hello 和World 分两行定义,szHello 是这样定义的:
szHello db ‘Hello’,0dh,0ah
db ‘World’,0

那么sizeof szHello 是多少呢?注意!是7 而不是13,MASM 中的变量定义只认一行,后一行db ‘World’,0 实际上是另一个没有名称的数据定义,编译器认为sizeof szHello 是第一行字符的数量。虽然把szHello 的地址当参数传给MessageBox 等函数显示时会把两行都显示出来,但严格地说这是越界使用变量。虽然在实际的应用中这样定义长字符串的用法很普遍,因为如果要显示一屏幕帮助,一行是不够的,但要注意的是:要用到这种字符串的长度时,千万不要用sizeof 去表示,最好是在程序中用lstrlen 函数去计算。

获取变量地址

         对于全局变量,它的地址在编译的时候已经由编译器确定了,它的用法大家都不陌生:
mov 寄存器,offset 变量名
         其中offset 是取变量地址的伪操作符,和sizeof 伪操作符一样,它仅把变量的地址代到指令中去,这个操作是在编译时而不是在运行时完成的。

         但是对于局部变量,它的地址空间是在程序执行时动态给态给定的,所以不可能由offset来取地址!而使用:

lea eax,[ebp-4]
该指令可以在运行时按照ebp 的值实际计算出地址放到eax 中。

如果要在invoke 伪指令的参数中用到一个局部变量的地址,该怎么办呢?参数中是不可
能写入lea 指令的,用offset 又是不对的。MASM 对此有一个专用的伪操作符addr,其格式
为:
addr 局部变量名和全局变量名
addr 后跟全局变量名的时候,用法和offset 是相同的;当addr 后面跟局部变量名的时候,编译器会自动用lea 指令先把地址取到eax 中,然后用eax 来代替变量地址使用。注意addr 伪操作符只能在invoke 的参数中使用,不能用在类似于下列的场合:
mov eax,addr 局部变量名 ;注意:错误用法

假设在一个子程序中有如下invoke 指令:
invoke Test,eax,addr szHello
其中Test 是一个需要两个参数的子程序,szHello 是一个局部变量,会发生什么结果呢?
编译器会把invoke 伪指令和addr 翻译成下面这个模样:
lea eax,[ebp-4]
push eax ;参数2:addr szHello
push eax ;参数1:eax
call Test
发现了什么?到push 第一个参数eax 之前,eax 的值已经被lea eax,[ebp-4]指令覆盖
了!也就是说,要用到的eax 的值不再有效,所以,当在invoke 中使用addr 伪操作符时,
注意在它的前面不能用eax,否则eax 的值会被覆盖掉,当然eax 在addr 的后面的参数中
用是可以的。幸亏MASM 编译器对这种情况有如下错误提示:
error A2133: register value overwritten by INVOKE
否则,不知道又会引出多少莫名其妙的错误!

1、条件测试语句

&&、||、!等各种在C中存在的逻辑符号都可以使用。

除了这些和高级语言类似的条件测试伪操作,汇编语言还有特殊的要求,就是程序中常常要根据系统标志寄存器中的各种标志位来做条件跳转,这些在高级语言中是用不到的,所以又增加了以下一些标志位的状态指示,它们本身相当于一个表达式:
CARRY? 表示Carry 位是否置位
OVERFLOW? 表示Overflow 位是否置位
PARITY? 表示Parity 位是否置位
SIGN? 表示Sign 位是否置位
ZERO? 表示Zero 位是否置位
要测试eax 等于ebx 同时Zero 位置位,条件表达式可以写为:
(eax==ebx) && ZERO?
要测试eax 等于ebx 同时Zero 位清零,条件表达式可以写为:
(eax==ebx) && ! ZERO?

.if 条件表达式1
表达式1 为“真”时执行的指令
[.elseif 条件表达式2]
表达式2 为“真”时执行的指令
[.elseif 条件表达式3]
表达式3 为“真”时执行的指令

[.else]
所有表达式为“否”时执行的指令
.endif

注意:关键字if/elseif/else/endif 的前面有个小数点,如果不加小数点,就变成宏汇编中
的条件汇编伪操作了,结果可是天差地别。

.while 条件测试表达式
指令
[.break [.if 退出条件]]
[.continue]
.endw


.repeat
指令
[.break [.if 退出条件]]
[.continue]
.until 条件测试表达式 (或.untilcxz [条件测试表达式])

        .while/.endw 循环首先判断条件测试表达式,如果结果是"真",则执行循环体内的指令,结束后再回到 .while 处判断表达式,如此往复,一直到表达式结果为"假"为止.while/.endw指令有可能一遍也不会执行到循环体内的指令,因为如果第一次判断表达式时就遇到结果为"假"的情况,那么就直接退出循环。
        .repeat/.until 循环首先执行一遍循环体内的指令,然后再判断条件测试表达式,如果结果为"真"的话,就退出循环,如果为"假",则返回 .repeat 处继续循环,可以看出.repeat/.until不管表达式的值如何,至少会执行一遍循环体内的指令。
        也可以把条件表达式直接设置为固定值,这样就可以构建一个无限循环,对于.while/.end直接使用TRUE,对 .repeat/.until 直接使用FALSE 来当表达式就是如此,这种情况下,可以使用 .break 伪指令强制退出循环,如果 .break 伪指令后面跟一个 .if 测试伪指令的话,那么当退出条件为"真"时才执行 .break 伪指令。
        在循环体中也可以用 .continue 伪指令忽略以后的指令,遇到 .continue 伪指令时,不管下面还有没有其他循环体中的指令,都会直接回到循环头部开始执行。

2005年09月01日

windows日志的保护与伪造

原创:netone(cexo)

windows日志的保护与伪造
日志对于系统安全的作用是显而易见的,无论是网络管理员还是黑客都非常重视日志,一个有经验的管理员往往能够迅速通过日志了解到系统的安全性能,而一个聪明的黑客往往会在入侵成功后迅速清除掉对自己不利的日志。下面我们就来讨论一下日志的安全和创建问题。
一:概述:Windows2000的系统日志文件有应用程序日志,安全日志、系统日志、DNS服务器日志等等,应用程序日志、安全日志、系统日志、DNS日志默认位置:%systemroot%\system32\config,默认文件大小512KB。
安全日志文件:%systemroot%\system32\config\SecEvent.EVT
系统日志文件:%systemroot%\system32\config\SysEvent.EVT
应用程序日志文件:%systemroot%\system32\config\AppEvent.EVT
这些LOG文件在注册表中的:
HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Eventlog
有的管理员很可能将这些日志重定位。其中EVENTLOG下面有很多的子表,里面可查到以上日志的定位目录。

 

二:作为网络管理员:
1。日志的安全配置:
默认的条件下,日志的大小为512KB大小,如果超出则会报错,并且不会再记录任何日志。所以首要任务是更改默认大小,具体方法:注册表中HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services\Eventlog对应的每个日志如系统,安全,应用程序等均有一个maxsize子键,修改即可。
下面给出一个来自微软站点的一个脚本,利用VMI来设定日志最大25MB,并允许日志自行覆盖14天前的日志:
该脚本利用的是WMI对象, WMI(Windows Management Instrumentation)技术是微软提供的Windows下的系统管理工具。通过该工具可以在本地或者管理客户端系统中几乎一切的信息。很多专业的网络管理工具都是基于WMI开发的。该工具在Win2000以及WinNT下是标准工具,在Win9X下是扩展安装选项。所以以下的代码在2000以上均可运行成功。

strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate,(Security)}!\\" & _
strComputer & "\root\cimv2") ‘获得VMI对象
Set colLogFiles = objWMIService.ExecQuery _
("Select * from Win32_NTEventLogFile")
For each objLogfile in colLogFiles
strLogFileName = objLogfile.Name
Set wmiSWbemObject = GetObject _
("winmgmts:{impersonationLevel=Impersonate}!\\.\root\cimv2:" _
& "Win32_NTEventlogFile.Name=’" & strLogFileName & "’")
wmiSWbemObject.MaxFileSize = 2500000000
wmiSWbemObject.OverwriteOutdated = 14
wmiSWbemObject.Put_
Next
将上述脚本用记事本存盘为vbs为后缀的即可使用。
另外需要说明的是代码中的strComputer="."在windows脚本中的含义相当于localhost,如果要在远程主机上执行代码,只需要把"."改动为主机名,当然首先得拥有对方主机的管理员权限并建立IPC连接.本文中的代码所出现的strComputer均可作如此改动.
2。日志的查询与备份:
一个优秀的管理员是应该养成备份日志的习惯,如果有条件的话还应该把日志转存到备份机器上或直接转储到打印机上,笔者还有一篇文章《利用脚本编程格式化输出系统日志》,详细的讲述了利用windows脚本把日志转储并输出成html页已便于查询,有兴趣的可以查看,在这里推荐微软的resourceKit工具箱中的dumpel.exe,他的常用方法:
dumpel -f filename -s \\server -l log
-f filename 输出日志的位置和文件名
-s \\server 输出远程计算机日志
-l log log 可选的为system,security,application,可能还有别的如DNS等
如要把目标服务器server上的系统日志转存为backupsystem.log可以用以下格式:
dumpel \\server -l system -f backupsystem.log
再利用计划任务可以实现定期备份系统日志。
另外利用脚本编程的VMI对象也可以轻而易举的实现日志备份:
下面给出备份application日志的代码:
backuplog.vbs
strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate,(Backup)}!\\" & _
strComputer & "\root\cimv2") ‘获得 VMI对象
Set colLogFiles = objWMIService.ExecQuery _
("Select * from Win32_NTEventLogFile where LogFileName=’Application’") ‘获取日志对象中的应用程序日志
For Each objLogfile in colLogFiles
errBackupLog = objLogFile.BackupEventLog("f:\application.evt") ‘将日志备份为f:\application.evt
If errBackupLog <> 0 Then
Wscript.Echo "The Application event log could not be backed up."
else Wscript.Echo "success backup log"
End If
Next
程序说明:如果备份成功将窗口提示:"success backup log" 否则提示:"The Application event log could not be backed up",此处备份的日志为application 备份位置为f:\application.evt,可以自行修改,此处备份的格式为evt的原始格式,用记事本打开则为乱码,这一点他不如dumpel用得方便。


三:作为黑客
1。日至清除
一个入侵系统成功后的黑客第一件事便是清除日志,如果以图形界面远程控制对方机器或是从终端登陆进入,删除日志不是一件困难的事,由于日志虽然也是作为一种服务运行,但不同于http,ftp这样的服务,可以在命令行下先停止,再删除,在m命令行下用net stop eventlog是不能停止的,所以有人认为在命令行下删除日志是很困难的,实际上不是这样,下面介绍几种方法:
1.借助第三方工具:如小榕的elsave.exe远程清除system,applicaton,security的软件,使用方法很简单,首先利用获得的管理员账号与对方建立ipc会话,net use \\ip pass /user: user
然后命令行下:elsave -s \\ip -l application -C,这样就删除了安全日志。
其实利用这个软件还可以进行备份日志,只要加一个参数 -f filename就可以了,在此不再详述。
2.利用脚本编程中的VMI,也可以实现删除日志,首先获得object对象,然后利用其clearEventLog() 方法删除日志。源代码:
cleanevent.vbs
strComputer = "."
Set objWMIService = GetObject("winmgmts:" _
& "{impersonationLevel=impersonate,(Backup)}!\\" & _
strComputer & "\root\cimv2")
dim mylogs(3)
mylogs(1)="application"
mylogs(2)="system"
mylogs(3)="security"
for Each logs in mylogs
Set colLogFiles = objWMIService.ExecQuery _
("Select * from Win32_NTEventLogFile where LogFileName=’"&logs&"’")
For Each objLogfile in colLogFiles
objLogFile.ClearEventLog()
Next
next
在上面的代码中,建立一个数组,为application,security,system如果还有其他日志也可以加入数组。
然后用一个for 循环,删除数组中的每一个元素,即各个日志.
2。创建日志:
删除日志后,任何一个有头脑的管理员面对空空的日志,马上就会反应过来被入侵了,所以一个聪明的黑客的学会如何
伪造日志:
1。利用脚本编程中的eventlog方法是创造日志变得非常简单;下面看一个代码
createlog.vbs
set ws=wscript.createobject("Wscript.shell")
ws.logevent 0 ,"write log success" ‘创建一个成功执行日志
这个代码很容易阅读,首先获得wscript的一个shell对象,然后利用shell对象的logevent方法
logevent的用法:logevent eventtype,"description" [,remote system]
eventtype 为日志类型,可以使用的如下:0 代表成功执行,1 执行出错 ,2 警告 , 4,信息 ,8 成功审计 16 故障审计
所以上面代码中,把0改为1,2,4,8,16均可,引号下的为日志描述。
这种方法写的日志有一个缺点,只能写到应用程序日志,而且日至来源只能为wsh,即windows scripting host,所以不能起太多的隐蔽作用。
2,微软为了方便系统管理员和程序员,在xp下有个新的命令行工具,eventcreate.exe,利用它,创建日志更加简单。
eventcreate -s server -l logname -u username -p password -so source -t eventtype -id id -d description
含义:-s 为远程主机创建日志: -u 远程主机的用户名 -p 远程主机的用户密码
-l 日志;可以创建system和application 不能创建security日志,
-so 日志来源,可以是任何日志 -t 日志类型 如information信息,error错误,warning 警告,
-d 日志描述,可以是任意语句 -id 自主日志为1-1000之内
例如,我们要本地创建一个系统日志,日至来源为admin,日志类型是警告,描述为"this is a test",事件ID为500
可以用如下参数
eventcreate -l system -so administrator -t warning -d "this is a test" -id 500
这个工具不能创建安全日志。至于如何创建安全日志,希望大家能够找到一个好方法!