分享
 
 
 

Win32汇编教程 下

王朝system·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

8. 图形界面的操作

有关GDI和位图

GDI 即图形设备界面,是 Windows 最重要的部分之一,它大部分由 GDI32.DLL 库中的 API 来处理,GDI 的主要目的之一是支持与设备无关的图形编程,对于 Dos 下的图形编程,很多人可能“心有余悸”,因为PC 中有太多种类的显示卡,而几乎每个显示卡的处理都是不同的,即使后来有了 Vesa 编程,我们还是不能全部撇开具体的硬件,Windows GDI 使我们对图形的编程变得相对简单了很多,由于GDI 是 Windows 最庞大的部分,并不是几句话能讲清楚的,本节要讲的是 Windows 下GDI 的基本处理步骤和简单的位图处理,并没有涉及到 Directx 一类的编程。只希望能对朋友们有所启发。

Windows 并不允许程序员访问显示硬件,它的所有对屏幕的操作是通过环境设备(DC)来处理的,屏幕上的每一个窗口对应一个DC,你可以把一个DC 想象成这个窗口的视频缓冲区,你对DC的操作结果会反映到屏幕上,在窗口的DC之外,你也可以自己建立DC,这相当于建立一个内存中的缓冲区,你对这个DC的操作结果保存在内存中。你也可以用 API 在不同的DC之间拷贝数据,比如说你可以在内存DC 中先建立好数据,然后拷贝到窗口的DC中,就相当于完成了屏幕的刷新。

与DC的取得、建立取消有关的API有以下几种:

GetDC(hWnd) - 取得某个窗口的DC,API 返回对应的 DC 句柄

ReleaseDC(hWnd,hDC) - 释放用 GetDC 取得的 DC 句柄

CreateCompatibleDC(hDC) - 从一个已知的 DC 句柄中建立一个内存 DC,各种参数、属性参考已知的 DC

DeleteDC(hDC) - 删除用CreateCompatibleDC 建立的 DC

上面的4个API,必须成对出现,用 GetDC 取得的DC 必须用 ReleaseDC 释放,而用 CreateCompatibleDC 建立的 DC 必须用 DeleteDC 删除,不能混淆。DC 的作用范围:用 GetDC 取得的窗口 DC 必须尽快释放,你不应该在 Windows 的不同消息之间保存 DC 句柄,而用 CreateCompatibleDC 建立的 DC 可以长期保存,举例说明,如果你在 WM_PAINT 和 WM_SIZE 消息中都要对窗口的 DC 进行操作,你不能在 WM_INIT 时先 GetDC,然后保存句柄,最后在 WM_CLOSE 消息时 ReleaseDC,而是必须在 WM_PAINT 和 WM_SIZE 开始的地方 GetDC,在消息结束的地方就 ReleaseDC,而用 CreateCompatibleDC 建立的则相反,你可以在 WM_INIT 时建立,在 WM_CLOSE 时删除。

如果想把一个位图画到 DC 中,你只需简单的用 invoke SelectObject,hDc,hBitmap 就行了,是不是很简单?但图形操作并不是单单把位图放入屏幕就行了,还要涉及到位的操作,如把前景位图的边缘去掉贴入背景位图等。 Windows 的 GDI 提供了下面一些 DC 间的拷贝 API,中间就包括了拷贝的模式:

BitBlt hDcDest,XDest,YDest,Width,Height,hDcSource,XSrc,YSrc,dwRop

这个 API 把 hDcSource 的 XSrc,YSrc 坐标处的内容拷贝到 hDcDest 的 XDest,YDest 处,拷贝大小为 Width,Height。

PatBlt hDc,X,Y,Width,Height,dwRop 是用预定义的刷子等 Object 填充 DC

StretchBlt,hDcDest,XDest,YDest,Width,Height,hDcSource,XSrc,YSrc,WidthSrc,HeightSrc,dwRop 是拷贝并自动缩放大小,你可以注意到它和 BitBlt 相比多了两个参数 WidthSrc 和 HeightSrc,别的都是一样的。

以上API 中的 dwRop 参数是最关键的,它的值有 SRCCOPY,SRCPAINT,SRCAND,DSTINVERT 等,表示源DC 拷贝到目标DC后象素的计算方法,SRCCOPY 表示用源DC覆盖目标DC,SRCPAINT是执行 OR 操作,SRCAND 是执行 AND 操作,DSTINVERT 是取反,举例说明,如果源DC中的某一点是黑色,目标DC对应的点是红色,那么用 SRCCOPY后,目标DC的点变成黑色,用SRCPAINT 后还是红色,因为黑 (000000) or 红(0000ff) =红(0000ff)。

对应一般对屏幕或窗口进行图形操作的步骤如下。

用GetDC 取得目标窗口的 DC

用 CreateCompatibleDC 建立一个内存中的 DC用作缓冲区

用 SelectObject 填充内存DC 或别的办法对内存DC进行操作,一句话,先把要显示的东西处理好

用 BitBlt 把内存DC 拷贝到窗口 DC中,完成屏幕刷新。

本节的例子程序是一个屏幕放大镜,它把鼠标移动到的地方的屏幕内容放大一倍显示到自己的窗口中。

源程序 - 汇编源文件

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;Programmed by 罗云彬, bigluo@telekbird.com.cn

;Website: http://asm.yeah.net

;LuoYunBin's Win32 ASM page (罗云彬的编程乐园)

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;版本信息

;汇编教程附带源程序 - 屏幕放大器

; V1.0 ------2000年7月1日

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

.386

.model flat, stdcall

option casemap :none ; case sensitive

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;Include 数据

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

includewindows.inc

includeuser32.inc

includekernel32.inc

includecomctl32.inc

includecomdlg32.inc

includegdi32.inc

includelibuser32.lib

includelibkernel32.lib

includelibcomctl32.lib

includelibcomdlg32.lib

includelibgdi32.lib

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;Equ 数据

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

DLG_MAINequ1000

ID_BITMAPequ1001

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;数据段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

.data?

hWinPicdd?

hDcMemdd?

hBitmapdd?

hWinDesktopdd?

hInstancedd?

szBufferdb256 dup(?)

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;子程序声明

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

_ProcDlgMainPROTO:DWORD,:DWORD,:DWORD,:DWORD

.data

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;代码段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

.code

includeWin.asm

;********************************************************************

_ProcDlgMainprocuses ebx edi esi, hWnd:DWORD,wMsg:DWORD,wParam:DWORD,lParam:DWORD

local@stPoint:POINT

local@hDcDesktop,@hDcPic

moveax,wMsg

.ifeax == WM_CLOSE

invokeEndDialog,hWnd,NULL

invokeKillTimer,hWnd,1

invokeDeleteDC,hDcMem

invokeDeleteObject,hBitmap

; *******************************************************************

.elseifeax == WM_INITDIALOG

invokeGetDlgItem,hWnd,ID_BITMAP

movhWinPic,eax

invokeGetDesktopWindow

movhWinDesktop,eax

invokeSetWindowPos,hWnd,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE or SWP_NOSIZE

; *******************************************************************

invokeGetDC,hWinDesktop

mov@hDcDesktop,eax

invokeCreateCompatibleDC,@hDcDesktop

movhDcMem,eax

invokeCreateCompatibleBitmap,@hDcDesktop,80,80

movhBitmap,eax

invokeSelectObject,hDcMem,hBitmap

invokeReleaseDC,hWinDesktop,@hDcDesktop

invokeSetTimer,hWnd,1,100,NULL

; *******************************************************************

.elseifeax == WM_TIMER

invokeGetCursorPos,addr @stPoint

sub@stPoint.x,20

sub@stPoint.y,20

.if@stPoint.x < 0

mov@stPoint.x,0

.endif

.if@stPoint.y < 0

mov@stPoint.y,0

.endif

invokeGetDC,hWinDesktop

mov@hDcDesktop,eax

invokeGetDC,hWinPic

mov@hDcPic,eax

invokePatBlt,hDcMem,0,0,80,80,BLACKNESS

invokeStretchBlt,hDcMem,0,0,80,80,@hDcDesktop,@stPoint.x,@stPoint.y,40,40,SRCCOPY

invokeBitBlt,@hDcPic,0,0,80,80,hDcMem,0,0,SRCCOPY

invokeReleaseDC,hWinDesktop,@hDcDesktop

invokeReleaseDC,hWinPic,@hDcPic

.else

;********************************************************************

;注意:对话框的消息处理后,要返回 TRUE,对没有处理的消息

;要返回 FALSE

;********************************************************************

moveax,FALSE

ret

.endif

moveax,TRUE

ret

_ProcDlgMainendp

;********************************************************************

start:

invokeGetModuleHandle,NULL

movhInstance,eax

invokeDialogBoxParam,hInstance,DLG_MAIN,NULL,offset _ProcDlgMain,0

invokeExitProcess,NULL

endstart

程序的分析和要点

在程序的初始化中,我们用GetDc 取的桌面的屏幕的 DC,再用 CreateCompatibleDC 建立一个内存DC做缓冲区,建立一个位图再用 SelectObject 把 hDcMem 设置为这个位图是为了是 hDcMem 的大小变为 80x80。

invokeGetDC,hWinDesktop

mov@hDcDesktop,eax

invokeCreateCompatibleDC,@hDcDesktop

movhDcMem,eax

invokeCreateCompatibleBitmap,@hDcDesktop,80,80

movhBitmap,eax

invokeSelectObject,hDcMem,hBitmap

invokeReleaseDC,hWinDesktop,@hDcDesktop

然后在程序的每 0.1 秒一次的 WM_TIMER 定时器消息中,我们先用 GetDC 取得桌面和对话框中文本框的句柄,然后用 PatBlt 把内存DC清除为黑色,再用 StretchBlt 从桌面DC中拷贝 40x40的区域到内存 DC 中,新的大小是 80x80(放大功能就是这样实现的),拷贝的位置是用 GetCursorPos 取得的,也就是鼠标的当前位置,最后用 BitBlt 把内存DC 拷贝到对话框中。如果直接把桌面DC 拷贝到对话框中也可以,但是当鼠标移动到屏幕边缘上时,由于屏幕外的点是无效的,所以对话框中的一部分会花屏,大家可以改动程序试试。

invokeGetCursorPos,addr @stPoint

invokeGetDC,hWinDesktop

mov@hDcDesktop,eax

invokeGetDC,hWinPic

mov@hDcPic,eax

invokePatBlt,hDcMem,0,0,80,80,BLACKNESS

invokeStretchBlt,hDcMem,0,0,80,80,@hDcDesktop,@stPoint.x,@stPoint.y,40,40,SRCCOPY

invokeBitBlt,@hDcPic,0,0,80,80,hDcMem,0,0,SRCCOPY

invokeReleaseDC,hWinDesktop,@hDcDesktop

invokeReleaseDC,hWinPic,@hDcPic

9.综合篇(一)复杂形状的窗口

概述

在前面八篇的 Win32asm 教程中,已经初步讲述了消息框、对话框、菜单、资源、GDI 等内容,基本上已经设计到了 Windows 界面的大部分内容,

在继续新的 Windows 其他部分的内容如多线程、文件操作、内存操作之前,我先综合前面的内容并加上一些新内容,写上一篇综合篇。

本篇的例子程序是一个复杂形状的窗口,窗口的形状是根据位图自动计算得到的,这也就是在我编写的小闹钟中使用的技术(大家可以到我的软件发布中下载一个看看),

由于以前在网上看到的有关特殊形状窗口的例子最多就是画一个圆形,或者几个方块和椭圆结合的形状,

没有一篇文章指出如何画出如“唐老鸭”这样一个造型的窗口。本文使用的算法可以自动根据位图的形状计算窗口形状。

在源程序中,很多代码都是前面教程提到的,主要有以下部分: 首先建立一个标准的窗口。(参考窗口一节) 设置窗口为特殊形状。(见下面的程序分析) 在窗口的 WM_PAINT 消息中更新窗口的图片。(参考图形界面一节) 由于窗口没有标题栏,所以在右击窗口时弹出一个菜单。(参考菜单一节) 菜单中有个“关于本程序”项,里面有超联结文本。(参考窗口子类化一节) Windows 里有专门的 API 来实现特殊形状的窗口,步骤是首先建立区域(Region),Region 可以合并,

这样一来就可以用几个简单的区域合并出一个复杂的区域,建立、合并区域和设置窗口的 API 主要有以下几条: CreateRectRgn(Left,Top,Right,Bottom) - 建立矩型区域 CreateEllipticRgn(Left,Top,Right,Bottom) - 建立椭圆区域 CreatePolygonRgn(lpPoints,NumberOfPoints,Mode) - 建立多边形区域,这些API返回区域句柄 CombineRgn(hDest,hSource1,hSource2,CombineMode) - 合并区域 SetWindowRgn(hWnd,hRgn,bRedraw) - 根据区域设置窗口形状 本程序的方法是扫描位图的点,按行设置区域,然后合并到总的区域中。

源程序 - 汇编源文件

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;是否包括调试代码

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

DEBUG=0

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;Programmed by 罗云彬, bigluo@telekbird.com.cn

;Website: http://asm.yeah.net

;LuoYunBin's Win32 ASM page (罗云彬的编程乐园)

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;版本信息

;特殊形状窗口的演示程序 Ver 1.0

;可以根据位图自动设置窗口的形状。

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

.386

.model flat, stdcall

option casemap :none ; case sensitive

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;Include 数据

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

includewindows.inc

includeuser32.inc

includekernel32.inc

includecomctl32.inc

includecomdlg32.inc

includeshell32.inc

includegdi32.inc

includelibuser32.lib

includelibkernel32.lib

includelibcomctl32.lib

includelibcomdlg32.lib

includelibshell32.lib

includelibgdi32.lib

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;Equ 数据

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;**************Equ 数据 **********************************

IDI_MAINequ1;icon

IDC_HANDLEequ2;Cursor

;**************Equ 数据 **********************************

DLG_ABOUTequ1200;dialog - about

ID_ABOUT_OKequ1201

ID_EMAILequ1202

ID_HOMEPAGEequ1203

;**************Equ 数据 **********************************

IDM_MAINequ2000

IDM_ABOUTequ2001

IDM_EXITequ2002

;**************Equ 数据 **********************************

IDB_0equ 3000;bitmap

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;数据段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

.data?

hInstancedd?

hWinMaindd?

hIcondd?

hCursordd?

hMenudd?

hBmpBackdd?;background bitmap

hDcBackdd?

;**************数据段 ************************************

.data

szClassNamedb'ShapeWindow',0

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;代码段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

.code

ifDEBUG

includeDebug.asm

endif

;********************************************************************

;设置窗口形状为BMP图形形状

;参数:窗口句柄,BMP图形句柄

;输入BMP图形要求:0,0处颜色为背景色

;********************************************************************

_SetWindowShapeprochWnd:DWORD,hBitMap:DWORD

local@hDC:DWORD,@hBmpDC:DWORD

local@stPs:PAINTSTRUCT

local@stRect:RECT

local@stBmp:BITMAP

local@dwX:DWORD,@dwY:DWORD,@dwStartX:DWORD

local@hRgn:DWORD,@hRgnTemp:DWORD

local@rgbBack:DWORD

invokeGetObject,hBitMap,sizeof BITMAP,addr @stBmp

invokeGetWindowRect,hWnd,addr @stRect

invokeShowWindow,hWnd,SW_HIDE

invokeMoveWindow,hWnd,@stRect.left,@stRect.top,@stBmp.bmWidth,@stBmp.bmHeight,FALSE

invoke GetDC,hWnd

mov@hDC,eax

invokeCreateCompatibleDC,@hDC

mov@hBmpDC,eax

invokeSelectObject,@hBmpDC,hBitMap

;*************** 计算窗口形状 ***************************************

invokeGetPixel,@hBmpDC,0,0

mov@rgbBack,eax

invokeCreateRectRgn,0,0,0,0

mov@hRgn,eax

mov@dwY,0

.whileTRUE

mov@dwX,0

mov@dwStartX,-1

.whileTRUE

invokeGetPixel,@hBmpDC,@dwX,@dwY

.if@dwStartX == -1

.ifeax != @rgbBack

moveax,@dwX

mov@dwStartX,eax

.endif

.else

.ifeax == @rgbBack

movecx,@dwY

incecx

invokeCreateRectRgn,@dwStartX,@dwY,@dwX,ecx

invokeCombineRgn,@hRgn,@hRgn,eax,RGN_OR

mov@dwStartX,-1

.else

moveax,@dwX

.ifeax == @stBmp.bmWidth

inceax

movecx,@dwY

incecx

invokeCreateRectRgn,@dwStartX,@dwY,eax,ecx

invokeCombineRgn,@hRgn,@hRgn,eax,RGN_OR

mov@dwStartX,-1

.endif

.endif

.endif

inc@dwX

moveax,@dwX

.break.if eax > @stBmp.bmWidth

.endw

inc@dwY

moveax,@dwY

.break.if eax > @stBmp.bmHeight

.endw

invokeSetWindowRgn,hWnd,@hRgn,TRUE

;********************************************************************

invokeBitBlt,@hDC,0,0,@stBmp.bmWidth,@stBmp.bmHeight,@hBmpDC,0,0,SRCCOPY

invokeDeleteDC,@hBmpDC

invokeReleaseDC,hWnd,@hDC

invokeInvalidateRect,hWnd,NULL,-1

ret

_SetWindowShapeendp

;********************************************************************

;将窗口移动到屏幕中间

;参数:窗口句柄

;********************************************************************

_CenterWindowprochWnd:DWORD

local@stRectDeskTop:RECT,@stRectWin:RECT

local@dwWidth:DWORD,@dwHeight:DWORD

invokeGetWindowRect,hWnd,addr @stRectWin

invokeGetDesktopWindow

movebx,eax

invokeGetWindowRect,ebx,addr @stRectDeskTop

moveax,@stRectWin.bottom

subeax,@stRectWin.top

mov@dwHeight,eax

moveax,@stRectWin.right

subeax,@stRectWin.left

mov@dwWidth,eax

movebx,@stRectDeskTop.bottom

subebx,@dwHeight

shrebx,1

movecx,@stRectDeskTop.right

subecx,@dwWidth

shrecx,1

invokeMoveWindow,hWnd,ecx,ebx,@dwWidth,@dwHeight,FALSE

ret

_CenterWindowendp

;********************************************************************

includeAbout.asm

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;程序开始

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

start:

call_WinMain

invokeExitProcess,NULL

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;主窗口程序

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

_WinMainproc

local@stWcMain:WNDCLASSEX

local@stMsg:MSG

invokeInitCommonControls

invokeGetModuleHandle,NULL

movhInstance,eax

invokeLoadIcon,hInstance,IDI_MAIN

movhIcon,eax

invokeLoadMenu,hInstance,IDM_MAIN

invokeGetSubMenu,eax,0;PopUp 菜单要用到子菜单

movhMenu,eax

;*************** 注册窗口类 *****************************************

invokeLoadCursor,0,IDC_ARROW

mov@stWcMain.hCursor,eax

mov@stWcMain.cbSize,sizeof WNDCLASSEX

mov@stWcMain.hIconSm,0

mov@stWcMain.style,CS_HREDRAW or CS_VREDRAW

mov@stWcMain.lpfnWndProc,offset WndMainProc

mov@stWcMain.cbClsExtra,0

mov@stWcMain.cbWndExtra,0

moveax,hInstance

mov@stWcMain.hInstance,eax

mov@stWcMain.hIcon,0

mov@stWcMain.hbrBackground,COLOR_WINDOW + 1

mov@stWcMain.lpszClassName,offset szClassName

mov@stWcMain.lpszMenuName,0

invokeRegisterClassEx,addr @stWcMain

;***************** 建立输出窗口*****************************************

;属性:没有标题栏,不显示在任务栏

;********************************************************************

invokeCreateWindowEx,WS_EX_TOOLWINDOW,offset szClassName,NULL,WS_POPUP or WS_SYSMENU,0,0,1,1,NULL,NULL,hInstance,NULL

invokeShowWindow,hWinMain,SW_SHOWNORMAL

invokeUpdateWindow,hWinMain

;*************** 消息循环 *******************************************

.whileTRUE

invokeGetMessage,addr @stMsg,NULL,0,0

.break.if eax== 0

invokeTranslateMessage,addr @stMsg

invokeDispatchMessage,addr @stMsg

.endw

ret

_WinMainendp

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

WndMainProcprocuses ebx edi esi, hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD

local@stPos:POINT

local@stPs:PAINTSTRUCT,@hDC:DWORD

moveax,uMsg

.ifeax ==WM_CREATE

moveax,hWnd

movhWinMain,eax

call_Init

;********************************************************************

.elseifeax == WM_PAINT

invokeBeginPaint,hWnd,addr @stPs

mov@hDC,eax

moveax,@stPs.rcPaint.right

subeax,@stPs.rcPaint.left

movecx,@stPs.rcPaint.bottom

subecx,@stPs.rcPaint.top

invokeBitBlt,@hDC,@stPs.rcPaint.left,@stPs.rcPaint.top,eax,ecx,hDcBack,@stPs.rcPaint.left,@stPs.rcPaint.top,SRCCOPY

invokeEndPaint,hWnd,addr @stPs

;********************************************************************

;由于没有菜单,下面代码用于按下右键时弹出POPUP菜单

;********************************************************************

.elseif eax == WM_RBUTTONDOWN

.if wParam == MK_RBUTTON

invokeGetCursorPos,addr @stPos

invokeTrackPopupMenu,hMenu,TPM_LEFTALIGN,@stPos.x,@stPos.y,NULL,hWnd,NULL

.endif

;********************************************************************

;由于没有标题栏,下面代码用于按下左键时移动窗口

;********************************************************************

.elseif eax == WM_LBUTTONDOWN

invokeUpdateWindow,hWnd;即时刷新

invokeReleaseCapture

invokeSendMessage,hWnd,WM_NCLBUTTONDOWN,HTCAPTION,0

;********************************************************************

.elseifeax ==WM_COMMAND

.iflParam == 0

moveax,wParam

.ifax == IDM_EXIT

call_Quit

.elseifax == IDM_ABOUT

invokeDialogBoxParam,hInstance,DLG_ABOUT,hWnd,offset AboutDialogProc,DLG_ABOUT

.endif

.endif

;********************************************************************

.elseifeax ==WM_CLOSE

call_Quit

;********************************************************************

.else

invokeDefWindowProc,hWnd,uMsg,wParam,lParam

ret

.endif

;********************************************************************

;注意:WndProc 处理 Windows 消息后,必须在 Eax 中返回 0

;但是由 DefWindowProc 处理后的返回值不能改变,否则窗口

;将无法显示!

;********************************************************************

xoreax,eax

ret

WndMainProcendp

;********************************************************************

_Initproc

local@hDC

invokeSendMessage,hWinMain,WM_SETTEXT,0,offset szClassName

invokeSendMessage,hWinMain,WM_SETICON,ICON_SMALL,hIcon

invokeLoadBitmap,hInstance,IDB_0;装入背景图片

movhBmpBack,eax

invoke_SetWindowShape,hWinMain,hBmpBack;设置窗口形状为背景图片

invoke GetDC,hWinMain

mov@hDC,eax

invokeCreateCompatibleDC,@hDC;建立背景及数字 DC

movhDcBack,eax

invokeReleaseDC,hWinMain,@hDC

invokeSelectObject,hDcBack,hBmpBack

invoke_CenterWindow,hWinMain

ret

_Initendp

;********************************************************************

_Quitproc

local@stWindow:RECT

invokeDestroyMenu,hMenu

invokeDeleteDC,hDcBack

invokeDeleteObject,hBmpBack

invokeDestroyWindow,hWinMain

invokePostQuitMessage,NULL

ret

_Quitendp

;********************************************************************

endstart

程序的分析和要点

创建窗口的时候,窗口风格为 WS_POPUP,所以创建的窗口没有标题栏,这样的窗口适合于设置成特殊形状的窗口

invokeCreateWindowEx,WS_EX_TOOLWINDOW,offset szClassName,NULL,WS_POPUP or WS_SYSMENU,0,0,1,1,NULL,NULL,hInstance,NULL

但是当窗口没有标题栏后,我们就无法用拖动标题栏的办法来移动窗口,如果让窗口一动不动呆在屏幕中间显然是不行的,这里有一个替代办法,

我们可以响应按下鼠标左键的消息,在 WM_LBUTTONDOWN 消息中想窗口发送 WM_NCLBUTTONDOWN (非客户区鼠标按下消息) 位置在 HTCAPTION 来模拟鼠标按在标题栏中来实现移动的功能。

.elseif eax == WM_LBUTTONDOWN

invokeUpdateWindow,hWnd;即时刷新

invokeReleaseCapture

invokeSendMessage,hWnd,WM_NCLBUTTONDOWN,HTCAPTION,0

10. 定时器的应用

概述

Windows 的定时器是一种输入设备,它周期性地在指定的间隔时间通知应用程序。它可以用向指定窗口发送 WM_TIMER 消息或者调用指定的过程来执行用户的程序。定时器的应用主要包括下面一些地方: 时钟程序 - 显然,这是定时器最直接的应用。 多任务 - 如果程序有大量的数据处理,除了用多线程的办法,还可以用定时器,在每一个定时器消息中处理一小块内容。 定时显示程序的状况 - 定时器就相当于 Dos 编程中的自己挂接在 int 1ch 上面的要定时处理的程序,它可以定时显示程序运行的情况,如发送了多少内容,接收了多到内容等等。 在游戏程序中使用定时器可以消除在不同处理器下用延时来保持速度一致所造成的误差。 用于数据流处理 - 在音频、视频的播放中,需要隔一段时间处理一段数据。 总的来说,在 Dos 下实现精确定时的唯一方法是在 int 1ch 时钟中断中处理程序,但你使用起来必须遵守很多的规范,而在 Windows 的定时器中,你可以用 SetTimer 函数分配不止一个的定时器,比如说,在你的文本编辑程序中,你可以使用一个间隔1秒的定时器来在状态栏中显示时钟,

同时分配一个10分钟的定时器来实现定时存盘的功能。定时器实际上是 Windows 对时钟中断的一种扩展,

它的本质还是基于时钟中断的,所以你实际上无法把定时器的间隔设置到55毫秒以下,另外,定时器的精度也是以55毫秒为倍数的,比如说,你设置了一个1秒的定时器,它实际上是在每989毫秒的时候发生的。和在 Dos 下使用时钟中断,windows 的定时器还有下面一些要点: 在 Dos 中,你的程序随时可能被 int 1ch 打断,而在Windows 中,Windows 通过 WM_TIMER 消息把定时器消息放入正常的消息队列中,所以你不必担心你的程序在别的处理中被定时器打断。 不可能有同时两条以上的 WM_TIMER 消息,如果在一个还在消息队列中,窗口再得到一条 WM_TIMER 消息,两条消息会被合并为一条,所以在程序比较忙的时候可能会丢失 WM_TIMER 消息。 WM_TIMER 消息的级别是很低的,程序只有在消息队列中没有其他消息的情况下,才会接收 WM_TIMER 消息,你可以通过下马方法验证:在一个设置了定时器的窗口上按住标题栏移动窗口,你会发现定时器停止了工作,当你松开鼠标后,在这个过程中丢失的 WM_TIMER 消息并没有被补上,所以如果你设计一个时钟程序,你不能使用定时器消息来计数,而必须在消息中每次获取正确的系统时间。 讲了这么多定时器的特点,下面是定时器相关的API,你会发现除了在使用中要注意的这些特性,定时器的API真是又少又简单: 建立定时器

SetTimer(

HWND hWnd, // handle of window for timer messages

UINT nIDEvent, // timer identifier

UINT uElapse, // time-out value

TIMERPROC lpTimerFunc // address of timer procedure

);

hWnd 是 windows 发送 WM_TIMER 的窗口,nIDEvent 是定时器的编号,在 WM_TIMER 中出现在 wParam 参数中,用来区分在多个定时器的情况下,这条消息是由哪个定时器产生的。uElapse 是定时器间隔的毫秒数,如果你要设置一个1秒的定时器,这个值就是1000,lpTimerFunc 是处理定时器消息的过程,如果这个参数不是 NULL,windows 在到时间后会调用lpTimerFunc 指定的过程,调用的参数是 CALLBACK TimerProc(hwnd,WM_TIMER,iTimerID,dwTime),iTimerID 是定时器 ID,dwTime 是系统时间;如果 lpTimerFunc 参数是 NULL,Windows 会把 WM_TIMER 消息放入消息循环中,消息的 hWnd 是第一个参数中指定的 hWnd,也就是说向这个窗口发送了 WM_TIMER 消息。

另外,如果你的程序没有窗口,你也可以用这种办法建立定时器:invoke SetTimer,NULL,NULL,uElapse,TimerProc,函数会返回一个系统定义的 TimerID供你在 KillTimer 中使用。

取消定时器

KillTimer(

HWND hWnd, // handle of window that installed timer

UINT uIDEvent // timer identifier

);

取消定时器只需对应 SetTimer 时的 hWnd 和 uIDEvent 调用 KillTimer 函数就行了。 在本节的例子程序中,我在对话框中的 WM_INIT 消息中用 SetTimer 建立两个定时器,时间分别是500ms 和 200ms,

然后在间隔0.5秒的定时器消息中更换按钮上的图片,在间隔 0.2 秒的定时器消息中更换标题栏上的小图标,你就可以看到动画的效果了。

源程序 - 汇编源文件

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;Programmed by 罗云彬, bigluo@telekbird.com.cn

;Website: http://asm.yeah.net

;LuoYunBin's Win32 ASM page (罗云彬的编程乐园)

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;版本信息

;汇编教程附带例子程序 - 定时器的使用

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

.386

.model flat, stdcall

option casemap :none ; case sensitive

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;Include 数据

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

includewindows.inc

includeuser32.inc

includekernel32.inc

includecomctl32.inc

includecomdlg32.inc

includelibuser32.lib

includelibkernel32.lib

includelibcomctl32.lib

includelibcomdlg32.lib

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;Equ 数据

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

IDI_MAINequ1

IDI_MOON1equ2

IDI_MOON2equ3

IDI_MOON3equ4

IDI_MOON4equ5

IDI_MOON5equ6

IDI_MOON6equ7

IDI_MOON7equ8

IDI_MOON8equ9

DLG_MAINequ1000

ID_MOONequ1001

ID_TIMER1equ1

ID_TIMER2equ2

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;数据段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

.data?

hInstancedd?

dwCounter1dd?

dwCounter2dd?

hIcon1dd?

hIcon2dd?

hIcon3dd?

hIcon4dd?

hIcon5dd?

hIcon6dd?

hIcon7dd?

hIcon8dd?

szBufferdb256 dup(?)

.data

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;代码段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

.code

;********************************************************************

_ProcDlgMainprocuses ebx edi esi, hWnd:DWORD,wMsg:DWORD,wParam:DWORD,lParam:DWORD

moveax,wMsg

;********************************************************************

.ifeax ==WM_CLOSE

invokeKillTimer,hWnd,ID_TIMER1

invokeKillTimer,hWnd,ID_TIMER2

invokeEndDialog,hWnd,NULL

;********************************************************************

.elseifeax ==WM_INITDIALOG

movedi,offset hIcon1

movebx,IDI_MOON1

movecx,8

@@:

pushecx

invokeLoadIcon,hInstance,ebx

cld

stosd

incebx

popecx

loop@B

invokeSetTimer,hWnd,ID_TIMER1,500,NULL

invokeSetTimer,hWnd,ID_TIMER2,200,NULL

invokeSendMessage,hWnd,WM_SETICON,ICON_SMALL,hIcon1

;********************************************************************

.elseifeax ==WM_TIMER

.ifwParam == ID_TIMER1

incdwCounter1

.ifdwCounter1 == 8

movdwCounter1,0

.endif

moveax,dwCounter1

shleax,2

addeax,offset hIcon1

moveax,[eax]

invokeSendMessage,hWnd,WM_SETICON,ICON_SMALL,eax

.else

incdwCounter2

.ifdwCounter2 == 8

movdwCounter2,0

.endif

moveax,dwCounter2

shleax,2

addeax,offset hIcon1

moveax,[eax]

invokeSendDlgItemMessage,hWnd,ID_MOON,BM_SETIMAGE,IMAGE_ICON,eax

.endif

;********************************************************************

.else

moveax,FALSE

ret

.endif

moveax,TRUE

ret

_ProcDlgMainendp

;********************************************************************

start:

invokeGetModuleHandle,NULL

movhInstance,eax

invokeDialogBoxParam,hInstance,DLG_MAIN,NULL,offset _ProcDlgMain,0

invokeExitProcess,NULL

;********************************************************************

endstart

程序的分析和要点

有了上面的介绍,这个程序是很容易看懂的,在 WM_TIMER 消息中,通过 wParam 中的 TimerID 可以区分是哪个定时器产生的消息。在 WM_CLOSE 消息中,通过 KillTimer 来取消定时器。本程序中的的图标定义在资源文件中,

在对话框建立的时候,先用 LoadIcon 装入,然后为两个定时器分别保存一个图片编号 dwCounter1 和 dwCounter2,在定时器消息中分别用 WM_SETICON 和 BM_SETIMAGE 消息来对窗口标题的图标和按钮的图标进行设置。

11. 进程控制

概述

进程控制简单的说相当于在一个程序中执行另一个程序,你可以把它想象成在 Dos 下用 int 21h/4bh 功能来执行另外一个程序,如果单从执行另一个程序的目的来讲,在 Windows 中有不少方法,如使用 ShellExecute 等,

但这些 Api 仅仅是“执行”而已,进程控制的意义在于可以创建一个进程,并可以通过进程句柄结束进程,同样你也可以通过进程句柄来跟踪程序,还可以用 ReadProcessMemory 和 WriteProcessMemory 来读写子进程的内存空间。

进程控制要使用的相关 API 有下面这些:

创建进程的函数为CreateProcess,该函数比较复杂,共有十个参数,但有个好消息是使用时大部分可以用 NULL。

BOOL CreateProcess(

LPCTSTR lpApplicationName, // 执行程序文件名

LPTSTR lpCommandLine, // 参数行

LPSECURITY_ATTRIBUTES lpProcessAttributes, // 进程安全参数

LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程安全参数

BOOL bInheritHandles, // 继承标记

DWORD dwCreationFlags, // 创建标记

LPVOID lpEnvironment, // 环境变量

LPCTSTR lpCurrentDirectory, // 运行该子进程的初始目录

LPSTARTUPINFO lpStartupInfo, // 创建该子进程的相关参数

LPPROCESS_INFORMATION lpProcessInformation // 创建后用于被创建子进程的信息

);

各个参数的说明如下: lpApplicationName:为执行程序的文件名,你也可以把执行文件名包括在下一个参数 lpCommandLine 中,然后把该参数置为NULL。 lpCommandLine:为参数行,如果无参数可以为NULL,在有参数传递给进程时可以如下设置:lpApplicationName=文件名;lpCommandLine=参数,或者 lpApplicationName=NULL;lpCommandLine=文件名 + 参数。 lpProcessAttributes,lpThreadAttributes:分别描述了创建的进程和线程安全属性,如果使用NULL表示使用默认的安全描述。 bInheritHandles:表示当前进程中的打开的句柄是否能够被创建的子进程所继承。 dwCreationFlags:表示创建标记,通过该标记可以设置进程的创建状态和优先级别。常用的有下面的标记:

CREATE_NEW_CONSOLE:为子进程创建一个新的控制台。

CREATE_SUSPENDED:子进程在创建时为挂起状态。如果指定了这个参数,那么执行 CreateProcess 后进程只是被装入内存,但不是马上开始执行,而是必须等主程序调用 ResumeThread 后才继续执行。

HIGH_PRIORITY_CLASS/NORMAL_PRIORITY_CLASS:高/普通优先级别。 lpEnvironment:表示子进程所使用的环境变量,如果为NULL,则表示与当前进程使用相同的环境变量。 lpCurrentDirectory:表示子进程运行的初始目录。 lpStartupInfo:STARTUPINFO 结构,用于在创建子进程时设置各种属性。 lpProcessInformation:PROCESS_INFORMATION 结构,用来在进程创建后接收相关信息,该结构由系统填写。 调用 CreateProcess 函数有三个参数是必需的,一在 lpApplicationName 或 lpCommandLine 指定文件名,二是 lpStartupInfo 结构,三是 PROCESS_INFORMATION 结构,

因为 PROCESS_INFORMATION 结构返回了进程建立后的句柄,以后的一切操作将要用到这些返回的句柄,它是由系统填写的,结构说明如下:

typedef struct _PROCESS_INFORMATION {

HANDLE hProcess; //进程句柄

HANDLE hThread; //进程的主线程句柄

DWORD dwProcessId; //进程ID

DWORD dwThreadId; //进程的主线程ID

} PROCESS_INFORMATION;

另外还有一个关键的结构 STARTUPINFO,该结构定义如下:

typedef struct STARTUPINFO {

DWORD cb; //结构长度

LPTSTR lpReserved; //保留

LPTSTR lpDesktop; //保留

LPTSTR lpTitle; //如果为控制台进程则为显示的标题

DWORD dwX; //窗口位置

DWORD dwY; //窗口位置

DWORD dwXSize; //窗口大小

DWORD dwYSize; //窗口大小

DWORD dwXCountChars; //控制台窗口字符号宽度

DWORD dwYCountChars; //控制台窗口字符号高度

DWORD dwFillAttribute; //控制台窗口填充模式

DWORD dwFlags; //创建标记

WORD wShowWindow; //窗口显示标记,如同ShowWindow中的标记

WORD cbReserved2; //

LPBYTE lpReserved2; //

HANDLE hStdInput; //标准输入句柄

HANDLE hStdOutput; //标准输出句柄

HANDLE hStdError; //标准错误句柄

} STARTUPINFO, *LPSTARTUPINFO;

结构中 dwFlags 指定了其它的一些字段是否有效,如:dwFlags包含 STARTF_USESIZE 表示dwXSize和dwYSize有效,

包含STARTF_USEPOSITION表示dwX和dwY有效,等等。如果不是有特殊的要求,我们不用自己去填写这个结构,

只需用 GetStartupInfo 让 Windows 为你填写好了,这样,建立一个进程的语句就是:

...

stStartUp STARTUPINFO stProcInfo PROCESS_INFORMATION <?>

stProcInfo PROCESS_INFORMATION <?>

...

invoke GetStartupInfo,addr stStartUp

invoke CreateProcess,NULL,addr szFileName,NULL,NULL,NULL,NORMAL_PRIORITY_CLASS,NULL,NULL,offset stStartUp,offset stProcInfo

...

如果成功的话,eax 将返回非零值,注意返回在 PROCESS_INFORMATION 结构中的 hProcess,以后很多的操作都要用到它。

强制结束一个进程的 API 为 TerminateProcess

BOOL TerminateProcess(

HANDLE hProcess, // 进程句柄

UINT uExitCode // 退出代码

);

你可以使用语句 invoke TerminateProcess,structProcInfo.hProcess,0 来结束进程,要注意的是如果可能的话,尽量不要在程序中强制结束别的进程,因为使用 TerminateProcess 结束的进程,它装载的 dll 不能被正确卸载。

这样可能会引起系统资源的无效占用。最好的办法在进程中自己使用 ExitProcess 退出。

查询一个进程状态的 API 为 GetExitCodeProcess。

BOOL GetExitCodeProcess(

HANDLE hProcess, // handle to the process

LPDWORD lpExitCode // address to receive termination status

);

如果进程尚未退出,函数将会返回STILL_ACTIVE。这个 API 是马上返回的。

等待进程执行可以用 WaitForSingleObject

这个 API 并不是单用于进程的等待,其它还可以用在线程等操作,但我们一般用它来等待进程的执行,它的申明是:

DWORD WaitForSingleObject(

HANDLE hHandle, // handle of object to wait for

DWORD dwMilliseconds // time-out interval in milliseconds

);

如果我们要等待进程执行 1 秒钟,可以 invoke WaitForSingleObject,stProcInfo.hProcess,1000 如果要等到进程结束,

可以用 WaitForSingleObject,stProcInfo.hProcess,INFINITE ,参数 2 中的 INFINITE 在 Windows.inc 中有定义,意思是无穷等待。

最后,当不再使用进程句柄的时候,不要忘了使用 CloseHandle 关闭 hProcess 和 hThread,否则会浪费系统句柄的资源。

源程序 - 汇编源文件

.386

.model flat, stdcall

option casemap :none ; case sensitive

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;Include 数据

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

includewindows.inc

includeuser32.inc

includekernel32.inc

includecomctl32.inc

includecomdlg32.inc

includegdi32.inc

includelibuser32.lib

includelibkernel32.lib

includelibcomctl32.lib

includelibcomdlg32.lib

includelibgdi32.lib

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;Equ 数据

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

DLG_MAINequ3000

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

ID_BROWSEequ3001

ID_RUNequ3002

ID_EXITequ3003

ID_TEXTequ3004

F_RUNNINGequ0001h;进程在运行中

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;数据段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

.data?

stStartUpSTARTUPINFO<?>

stProcInfoPROCESS_INFORMATION<?>

stOpenFileNameOPENFILENAME<?>

hRunThreaddd?

hInstancedd?

hWinMaindd?

hIcondd?

szBufferdb512 dup(?)

dwFlagdd?

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

.data

szExcutedb'执行(&E)',0;按钮文字

szKilldb'终止(&E)',0

szExcuteErrordb'启动应用程序错误!',0

szTitleOpendb"Open executable file...",0

szExtdb'*.exe',0

szFilterdb'Excutable Files',0,'*.exe;*.com',0

db0

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;代码段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

.code

ifDEBUG

includeDebug.asm

endif

includeWin.asm

;********************************************************************

; 执行程序用的线程

; 1. 用 CreateProcess 建立进程

; 2. 用 WaitForSingleOject 等待进程结束

;********************************************************************

_RunThreadprocuses ebx ecx edx esi edi,dwParam:DWORD

ordwFlag,F_RUNNING

;********************************************************************

; 取消“退出”按钮并把“执行”按钮改为“中止”

;********************************************************************

invokeGetDlgItem,hWinMain,ID_EXIT

invokeEnableWindow,eax,FALSE

invokeSendDlgItemMessage,hWinMain,ID_RUN,WM_SETTEXT,0,offset szKill

;********************************************************************

; 执行文件,如果成功则等待程序结束

;********************************************************************

invokeGetStartupInfo,addr stStartUp

invokeCreateProcess,NULL,addr szBuffer,NULL,NULL,NULL,NORMAL_PRIORITY_CLASS,NULL,NULL,offset stStartUp,offset stProcInfo

.ifeax !=0

invokeWaitForSingleObject,stProcInfo.hProcess,INFINITE

invokeCloseHandle,stProcInfo.hProcess

invokeCloseHandle,stProcInfo.hThread

.else

invokeMessageBox,hWinMain,addr szExcuteError,NULL,MB_OK or MB_ICONERROR

.endif

;********************************************************************

; Enable “退出”按钮并把“中止”按钮改为“执行”

;********************************************************************

invokeGetDlgItem,hWinMain,ID_EXIT

invokeEnableWindow,eax,TRUE

invokeSendDlgItemMessage,hWinMain,ID_RUN,WM_SETTEXT,0,offset szExcute

anddwFlag,not F_RUNNING

ret

_RunThreadendp

;********************************************************************

;窗口程序

;********************************************************************

DialogMainProcprocuses ebx edi esi, hWnd:DWORD,wMsg:DWORD,wParam:DWORD,lParam:DWORD

moveax,wMsg

;********************************************************************

.ifeax ==WM_INITDIALOG

moveax,hWnd

movhWinMain,eax

call_Init

;********************************************************************

.elseifeax ==WM_CLOSE

invokeEndDialog,hWinMain,NULL

;********************************************************************

.elseifeax ==WM_COMMAND

moveax,wParam

.ifax ==ID_BROWSE

call_BrowseFile

call_CheckText

.elseifax ==ID_TEXT

invokeGetDlgItemText,hWinMain,ID_TEXT,addr szBuffer,512

call_CheckText

.elseifax ==ID_RUN

;********************************************************************

; 如果没有在执行中(dwFlag 没有置位) 则建立线程,在线程中执行程序

; 如果已经在执行中,则用 TerminateProcess 终止执行

;********************************************************************

testdwFlag,F_RUNNING

.ifZERO?

invokeCreateThread,NULL,NULL,offset _RunThread,NULL,NULL,offset hRunThread

.else

invokeTerminateProcess,stProcInfo.hProcess,-1

.endif

.elseifax ==ID_EXIT

invokeEndDialog,hWinMain,NULL

.endif

.else

;********************************************************************

;注意:对话框的消息处理后,要返回 TRUE,对没有处理的消息

;要返回 FALSE

;********************************************************************

moveax,FALSE

ret

.endif

moveax,TRUE

ret

DialogMainProcendp

;********************************************************************

; 程序入口

;********************************************************************

start:

invokeInitCommonControls

invokeGetModuleHandle,NULL

movhInstance,eax

invokeDialogBoxParam,hInstance,DLG_MAIN,NULL,offset DialogMainProc,0

invokeExitProcess,NULL

;********************************************************************

_Initproc

invoke_CenterWindow,hWinMain

invokeSendDlgItemMessage,hWinMain,ID_TEXT,EM_LIMITTEXT,512,NULL

invokeGetDlgItem,hWinMain,ID_RUN

invokeEnableWindow,eax,FALSE

ret

_Initendp

;********************************************************************

; 根据 text control 中有无字符决定是否将“执行”按钮 Disable 掉

;********************************************************************

_CheckTextproc

invokeGetDlgItemText,hWinMain,ID_TEXT,addr szBuffer,512

invokelstrlen,addr szBuffer

.ifeax != 0 || (dwFlag & F_RUNNING)

invokeGetDlgItem,hWinMain,ID_RUN

invokeEnableWindow,eax,TRUE

.else

invokeGetDlgItem,hWinMain,ID_RUN

invokeEnableWindow,eax,FALSE

.endif

ret

_CheckTextendp

;********************************************************************

_BrowseFileproc

movstOpenFileName.Flags,OFN_PATHMUSTEXIST or OFN_FILEMUSTEXIST

movstOpenFileName.lStructSize,SIZEOF stOpenFileName

moveax,hWinMain

movstOpenFileName.hwndOwner,eax

movstOpenFileName.lpstrFilter,offset szFilter;扩展名

movstOpenFileName.lpstrFile,offset szBuffer;文件名缓冲

movstOpenFileName.nMaxFile,512;文件名缓冲长度

movstOpenFileName.lpstrInitialDir,0

movstOpenFileName.lpstrTitle,offset szTitleOpen

movstOpenFileName.lpstrDefExt,offset szExt

invokeGetOpenFileName,offset stOpenFileName

.ifeax == FALSE

ret

.endif

invokeSetDlgItemText,hWinMain,ID_TEXT,addr szBuffer

ret

_BrowseFileendp

;********************************************************************

endstart

程序的分析和要点

本程序在使用调用 GetOpenFileName 或者自己在文本框中输入执行文件名,然后通过 CreateProcess 建立进程,

最后用 WaitForSingleObject 等待进程结束,如果在对话框的处理过程中等待会导致程序在进程返回前无法响应,

所以程序中用 CreateThread 建立一个线程来实现这个过程,当子过程返回的时候,线程结束。dwFlag 中的 0 位作为标志位,

表示是否子过程在运行中,如果这一位置 1 的话,按下“终止”按钮会用 TerminateProcess 来强制终止子进程。

12. 管道操作

概述

Windows 引入了多进程和多线程机制。同时也提供了多个进程之间的通信手段,包括剪贴板、DDE、OLE、管道等,

和其他通信手段相比,管道有它自己的限制和特点,管道实际上是一段共享内存区,进程把共享消息放在那里。

并通过一些 API 提供信息交换。管道是两个头的东西,每个头各连接一个进程或者同一个进程的不同代码,

按照管道的类别分有两种管道,匿名的和命名的;按照管道的传输方向分也可以分成两种,单向的双向的。

根据管道的特点,命名管道通常用在网络环境下不同计算机上运行的进程之间的通信(当然也可以用在同一台机的不同进程中)

它可以是单向或双向的;而匿名管道只能用在同一台计算机中,它只能是单向的。匿名管道其实是通过用给了一个指定名字的有名管道来实现的。

使用管道的好处在于:读写它使用的是对文件操作的 api,结果操作管道就和操作文件一样。即使你在不同的计算机之间用命名管道来通信,

你也不必了解和自己去实现网络间通信的具体细节。

我们简单的介绍一下命名管道的使用。

命名管道是由服务器端的进程建立的,管道的命名必须遵循特定的命名方法,就是 "\\.\pipe\管道名",当作为客户端的进程要使用时,使用"\\计算机名\\pipe\管道名" 来打开使用,具体步骤如下: 服务端通过函数 CreateNamedPipe 创建一个命名管道的实例并返回用于今后操作的句柄,或为已存在的管道创建新的实例。 服务端侦听来自客户端的连接请求,该功能通过 ConnectNamedPipe 函数实现。 客户端通过函数 WaitNamedPipe 来等待管道的出现,如果在超时值变为零以前,有一个管道可以使用,则 WaitNamedPipe 将返回 True,并通过调用 CreateFile 或 CallNamedPipe 来呼叫对服务端的连接。 此时服务端将接受客户端的连接请求,成功建立连接,服务端 ConnectNamedPipe 返回 True 建立连接之后,客户端与服务器端即可通过 ReadFile 和 WriteFile,利用得到的管道文件句柄,彼此间进行信息交换。 当客户端与服务端的通信结束,客户端调用 CloseFile,服务端接着调用 DisconnectNamedPipe。最后调用函数CloseHandle来关闭该管道。 由于命名管道使用时作为客户端的程序必须知道管道的名称,所以更多的用在同一“作者”编写的服务器/工作站程序中,你不可能随便找出一个程序来要求它和你写的程序来通过命名管道通信。

而匿名管道的使用则完全不同,它允许你和完全不相干的进程通信,条件是这个进程通过控制台“console”来输入输出,典型的例子是老的 Dos 应用程序,

它们在运行时 Windows 为它们开了个 Dos 窗口,它们的输入输出就是 console 方式的。还有一些标准的 Win32 程序也使用控制台输入输出,如果在 Win32 编程中不想使用图形界面,

你照样可以使用 AllocConsole 得到一个控制台,然后通过 GetStdHandle 得到输入或输出句柄,再通过 WriteConsole 或 WriteFile 把结果输出到控制台(通常是一个象 Dos 窗口)的屏幕上。

虽然这些程序看起来象 Dos 程序,但它们是不折不扣的 Win32 程序,如果你在纯 Dos 下使用,就会显示“The program must run under Windows!”。

一个控制台有三个句柄:标准输入、标准输出和和标准错误句柄,标准输入、标准输出句柄是可以重新定向的,你可以用匿名管道来代替它,这样一来,你可以在管道的另一端用别的进程来接收或输入,

而控制台一方并没有感到什么不同,就象 Dos 下的 > 或者 < 可以重新定向输出或输入一样。通常控制台程序的输入输出如下:

(控制台进程output) write ----> 标准输出设备(一般是屏幕)

(控制台进程input) read <---- 标准输入设备(一般是键盘)

而用管道代替后:

(作为子进程的控制台进程output) write ----> 管道1 ----> read (父进程)

(作为子进程的控制台进程input) read <----> 管道2 <---- write (父进程)

使用匿名管道的步骤如下: 使用 CreatePipe 建立两个管道,得到管道句柄,一个用来输入,一个用来输出 准备执行控制台子进程,首先使用 GetStartupInfo 得到 StartupInfo 使用第一个管道句柄代替 StartupInfo 中的 hStdInput,第二个代替 hStdOutput、hStdError,即标准输入、输出、错误句柄 使用 CreateProcess 执行子进程,这样建立的子进程输入和输出就被定向到管道中 父进程通过 ReadFile 读第二个管道来获得子进程的输出,通过 WriteFile 写第一个管道来将输入写到子进程 父进程可以通过 PeekNamedPipe 来查询子进程有没有输出 子进程结束后,要通过 CloseHandle 来关闭两个管道。 下面是具体的说明和定义:

1. 建立匿名管道使用 CreatePipe 原形如下:

BOOL CreatePipe(

PHANDLE hReadPipe, // address of variable for read handle

PHANDLE hWritePipe, // address of variable for write handle

LPSECURITY_ATTRIBUTES lpPipeAttributes, // pointer to security attributes

DWORD nSize // number of bytes reserved for pipe

);

当管道建立后,结构中指向的 hReadPipe 和 hWritePipe 可用来读写管道,当然由于匿名管道是单向的,你只能使用其中的一个句柄,参数中的 SECURITY_ATTRIBUTES 的结构必须填写,定义如下:

typedef struct_SECURITY_ATTRIBUTES{

DWORD nLength: //定义以字节为单位的此结构的长度

LPVOID lpSecurityDescriptor; //指向控制这个对象共享的安全描述符,如果为NULL这个对象将被分配一个缺省的安全描述

BOOL bInheritHandle; //当一个新过程被创建时,定义其返回是否是继承的.供系统API函数使用.

}SECURITY_ATTRIBUTES;

2. 填写创建子进程用的 STARTUPINFO 结构,一般我们可以先用 GetStartupInfo 来填写一个缺省的结构,然后改动我们用得到的地方,它们是: hStdInput -- 用其中一个管道的 hWritePipe 代替 hStdOutput、hStdError -- 用另一个管道的 hReadPipe 代替 dwFlags -- 设置为 STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW 表示输入输出句柄及 wShowWindow 字段有效 wShowWindow -- 设置为 SW_HIDE,这样子进程执行时不显示窗口。 填写好以后,就可以用 CreateProcess 来执行子进程了,具体有关执行子进程的操作可以参考上一篇教程《进程控制》

3. 在程序中可以用 PeekNamedPipe 查询子进程有没有输出,原形如下:

BOOL PeekNamedPipe(

HANDLE hNamedPipe, // handle to pipe to copy from

LPVOID lpBuffer, // pointer to data buffer

DWORD nBufferSize, // size, in bytes, of data buffer

LPDWORD lpBytesRead, // pointer to number of bytes read

LPDWORD lpTotalBytesAvail, // pointer to total number of bytes available

LPDWORD lpBytesLeftThisMessage // pointer to unread bytes in this message

);

我们可以将尝试读取 nBuffersize 大小的数据,然后可以通过返回的 BytesRead 得到管道中有多少数据,如果不等于零,则表示有数据可以读取。

4. 用 ReadFile 和 WriteFile 来读写管道,它们的参数是完全一样的,原形如下:

ReadFile or WriteFile(

HANDLE hFile, // handle of file to read 在这里使用管道句柄

LPVOID lpBuffer, // address of buffer that receives data 缓冲区地址

DWORD nNumberOfBytesToRead, // number of bytes to read 准备读写的字节数

LPDWORD lpNumberOfBytesRead, // address of number of bytes read,实际读到的或写入的字节数

LPOVERLAPPED lpOverlapped // address of structure for data 在这里用 NULL

);

5. 用 CloseHandle 关闭管道一和管道二的 hReadPipe和 hWritePipe 这四个句柄。

下面给出了一个例子程序,这个程序是上篇教程《进程控制》的例子的扩充,如果你对有的 api 感到陌生的话,请先阅读上一篇教程。

源程序 - 汇编源文件

DEBUGequ0

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;Programmed by 罗云彬, bigluo@telekbird.com.cn

;Website: http://asm.yeah.net

;LuoYunBin's Win32 ASM page (罗云彬的编程乐园)

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;版本信息

;汇编教程附带例子程序 - 管道例子

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

.386

.model flat, stdcall

option casemap :none ; case sensitive

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;Include 数据

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

includewindows.inc

includeuser32.inc

includekernel32.inc

includecomctl32.inc

includecomdlg32.inc

includegdi32.inc

includelibuser32.lib

includelibkernel32.lib

includelibcomctl32.lib

includelibcomdlg32.lib

includelibgdi32.lib

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;Equ 数据

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

ICO_MAINequ1000

MENU_MAINequ2000

IDM_EXECequ2001

IDM_EXITequ2002

F_RUNNINGequ0001h;进程在运行中

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;数据段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

.data?

stStartUpSTARTUPINFO <?>

hInstancedd?

hMenudd?

hWinMaindd?

hWinTextdd?

hFontdd?

hRunThreaddd?

hRead1dd?

hWrite1dd?

hRead2dd?

hWrite2dd?

szBufferdb512 dup(?)

dwFlagdd?

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

.data

szMenuExecutedb'连接 MS-&DOS 方式',0

szExcuteErrordb'启动应用程序错误!',0

szCaptiondb'管道示例程序 ... http://asm.yeah.net',0

szClassNamedb'PipeExample',0

;szDllNamedb'riched32.dll',0

;szClassNameRedit db'RichEdit',0

szDllNamedb'riched20.dll',0

szClassNameRedit db'richedit20a',0

szCommanddb'c:\command.com',0

stLogFontLOGFONT<24,0,0,0,FW_NORMAL,0,0,0,ANSI_CHARSET,OUT_DEFAULT_PRECIS,CLIP_STROKE_PRECIS,DEFAULT_QUALITY,DEFAULT_PITCH or FF_SWISS,"Fixedsys">

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

;代码段

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

.code

ifDEBUG

includeDebug.asm

endif

includeWin.asm

;********************************************************************

; 执行程序用的线程

; 1. 用 CreateProcess 建立进程

; 2. 用 WaitForSingleOject 等待进程结束

;********************************************************************

_RunThreadprocuses ebx ecx edx esi edi,dwParam:DWORD

local@stSecurity:SECURITY_ATTRIBUTES

local@dwExitCode

local@dwBytesRead

local@stRange:CHARRANGE

ordwFlag,F_RUNNING

;********************************************************************

; “执行”菜单改为“结束”

;********************************************************************

invokeEnableMenuItem,hMenu,IDM_EXEC,MF_GRAYED

invokeEnableMenuItem,hMenu,IDM_EXIT,MF_GRAYED

;********************************************************************

; 建立管道

;********************************************************************

mov@stSecurity.nLength,sizeof SECURITY_ATTRIBUTES

mov@stSecurity.lpSecurityDescriptor,NULL

mov@stSecurity.bInheritHandle,TRUE

invokeCreatePipe,addr hRead1,addr hWrite1,addr @stSecurity,NULL

invokeCreatePipe,addr hRead2,addr hWrite2,addr @stSecurity,NULL

;********************************************************************

; 执行文件,如果成功则等待程序结束

;********************************************************************

invokeGetStartupInfo,addr stStartUp

moveax,hRead1

movstStartUp.hStdInput,eax

moveax,hWrite2

movstStartUp.hStdOutput,eax

movstStartUp.hStdError,eax

movstStartUp.dwFlags,STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW

movstStartUp.wShowWindow,SW_HIDE

invokeCreateProcess,NULL,addr szCommand,NULL,NULL,NULL,NORMAL_PRIORITY_CLASS,NULL,NULL,offset stStartUp,offset stProcInfo

.ifeax !=0

.whileTRUE

invokeGetExitCodeProcess,stProcInfo.hProcess,addr @dwExitCode

.break.if @dwExitCode != STILL_ACTIVE

invokePeekNamedPipe,hRead2,addr szBuffer,511,addr @dwBytesRead,NULL,NULL

.if@dwBytesRead !=0

invoke RtlZeroMemory,addr szBuffer,512

invokeReadFile,hRead2,addr szBuffer,@dwBytesRead,addr @dwBytesRead,NULL

mov@stRange.cpMin,-1

mov@stRange.cpMax,-1

invokeSendMessage,hWinText,EM_EXSETSEL,0,addr@stRange

invokeSendMessage,hWinText,EM_REPLACESEL,FALSE,addr szBuffer

invokeSendMessage,hWinText,EM_SCROLLCARET,NULL,NULL

invokeSendMessage,hWinText,WM_SETFONT,hFont,0

.endif

.endw

invokeCloseHandle,stProcInfo.hProcess

invokeCloseHandle,stProcInfo.hThread

.else

invokeMessageBox,hWinMain,addr szExcuteError,NULL,MB_OK or MB_ICONERROR

.endif

;********************************************************************

; 关闭管道

;********************************************************************

invoke CloseHandle,hRead1

invoke CloseHandle,hWrite1

invoke CloseHandle,hRead2

invoke CloseHandle,hWrite2

;********************************************************************

; 把“结束”菜单改为“执行”

;********************************************************************

invokeEnableMenuItem,hMenu,IDM_EXEC,MF_ENABLED

invokeEnableMenuItem,hMenu,IDM_EXIT,MF_ENABLED

invokeEnableWindow,hWinText,FALSE

anddwFlag,not F_RUNNING

ret

_RunThreadendp

;********************************************************************

;窗口程序

;********************************************************************

WndMainProcprocuses ebx edi esi, hWnd:DWORD,wMsg:DWORD,wParam:DWORD,lParam:DWORD

moveax,wMsg

;********************************************************************

.ifeax ==WM_CREATE

moveax,hWnd

movhWinMain,eax

call_Init

;********************************************************************

.elseifeax ==WM_SIZE

movedx,lParam

movecx,edx

shrecx,16

andedx,0ffffh

invokeMoveWindow,hWinText,0,0,edx,ecx,TRUE

invokePostMessage,hWinText,WM_SIZE,wParam,lParam

;********************************************************************

.elseifeax ==WM_CLOSE

testdwFlag,F_RUNNING

.ifZERO?

invokeDestroyWindow,hWinMain

invokePostQuitMessage,NULL

.endif

;********************************************************************

.elseifeax ==WM_COMMAND

moveax,wParam

.ifax ==IDM_EXEC

;********************************************************************

; 如果没有在执行中(dwFlag 没有置位) 则建立线程,在线程中执行程序

; 如果已经在执行中,则用 TerminateProcess 终止执行

;********************************************************************

testdwFlag,F_RUNNING

.ifZERO?

invokeEnableWindow,hWinText,TRUE

invokeSetFocus,hWinText

invokeCreateThread,NULL,NULL,offset _RunThread,NULL,NULL,offset hRunThread

.else

invokeTerminateProcess,stProcInfo.hProcess,-1

.endif

.elseifax ==IDM_EXIT

invokeDestroyWindow,hWinMain

invokePostQuitMessage,NULL

.endif

.else

invokeDefWindowProc,hWnd,wMsg,wParam,lParam

ret

.endif

xoreax,eax

ret

WndMainProcendp

;********************************************************************

; 程序入口

;********************************************************************

start:

call_WinMain

invokeExitProcess,NULL

;********************************************************************

_WinMainproc

local@stWcMain:WNDCLASSEX

local@stMsg:MSG

local@hRichEdit

invokeLoadLibrary,offset szDllName

mov@hRichEdit,eax

invokeInitCommonControls

invokeGetModuleHandle,NULL

movhInstance,eax

invokeLoadMenu,hInstance,MENU_MAIN

movhMenu,eax

;***************** 注册窗口类 ***************************************

invokeLoadCursor,0,IDC_ARROW

mov@stWcMain.hCursor,eax

mov@stWcMain.cbSize,sizeofWNDCLASSEX

mov@stWcMain.hIconSm,0

mov@stWcMain.style,CS_HREDRAW or CS_VREDRAW

mov@stWcMain.lpfnWndProc,offset WndMainProc

mov@stWcMain.cbClsExtra,0

mov@stWcMain.cbWndExtra,0

moveax,hInstance

mov@stWcMain.hInstance,eax

invokeLoadIcon,hInstance,ICO_MAIN

mov@stWcMain.hIcon,eax

mov@stWcMain.hbrBackground,COLOR_BTNFACE+1

mov@stWcMain.lpszClassName,offset szClassName

mov@stWcMain.lpszMenuName,0

invokeRegisterClassEx,addr @stWcMain

;***************** 建立输出窗口*****************************************

invokeCreateWindowEx,NULL,offset szClassName,offset szCaption,WS_OVERLAPPEDWINDOW,0,0,680,420,NULL,hMenu,hInstance,NULL

invokeShowWindow,hWinMain,SW_SHOWNORMAL

invokeUpdateWindow,hWinMain

;********************************************************************

.whileTRUE

invokeGetMessage,addr @stMsg,NULL,0,0

.break.if eax== 0

invokeTranslateMessage,addr @stMsg

invokeDispatchMessage,addr @stMsg

.endw

invokeFreeLibrary,@hRichEdit

invokeDeleteObject,hFont

ret

_WinMainendp

;********************************************************************

;输入程序

;********************************************************************

_InputProcprocuses ebx edi esi, hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD

local@szBuffer[4]:BYTE

local@dwBytesWrite

moveax,uMsg

.ifeax ==WM_CHAR

moveax,wParam

movzxeax,al

movdword ptr @szBuffer,eax

testdwFlag,F_RUNNING

.if!ZERO?

invokeWriteFile,hWrite1,addr @szBuffer,1,addr @dwBytesWrite,NULL

.endif

xoreax,eax

ret

.endif

invokeGetWindowLong,hWnd,GWL_USERDATA

invokeCallWindowProc,eax,hWnd,uMsg,wParam,lParam

ret

_InputProcendp

;********************************************************************

_Initproc

;*************** 建立输出 RICHEDIT 窗口***********************************

invokeCreateWindowEx,WS_EX_CLIENTEDGE,offset szClassNameRedit,NULL,WS_CHILD OR WS_VISIBLE OR WS_VSCROLL OR WS_HSCROLLOR ES_MULTILINEOR ES_AUTOHSCROLL OR ES_AUTOVSCROLL,0,0,0,0,hWinMain,NULL,hInstance,NULL

movhWinText,eax

;*************** 设置字体 ***********************************************

invokeCreateFontIndirect,offset stLogFont

movhFont,eax

invokeSendMessage,hWinText,WM_SETFONT,hFont,0

invokeSendMessage,hWinText,EM_SETREADONLY,TRUE,NULL

invokeSetWindowLong,hWinText,GWL_WNDPROC,offset _InputProc

invokeSetWindowLong,hWinText,GWL_USERDATA,eax

invokeEnableWindow,hWinText,FALSE

invoke_CenterWindow,hWinMain

invokeSetFocus,hWinText

ret

_Initendp

;********************************************************************

endstart

程序的分析和要点

在程序中,我先建立了一个 Richedit 控件用来显示子进程的输出,同时将 RichEdit 子类化,截取它的键盘输入以便把它发给子进程

invoke SetWindowLong,hWinText,GWL_WNDPROC,offset _InputProc

这条语句将 RichEdit 的过程指到了 _InputProc 中,然后在 _InputProc 的 WM_CHAR 中将键入的字符 WriteFile 到管道中,

我在程序中先建立了两个管道,然后执行 c:\command.com,这样就得到了一个 dos 的命令行进程,

然后在循环中通过 PeekNamedPipe 检测子进程有无输出,如果有的话则通过 ReadFile 读出,在显示到 RichEdit 中。

在运行例子程序的时候要注意,你可以在这个“Command.com” 中执行几乎所有的别的程序,但是不要执行

如 ucdos,pctools 之类不使用标准输入输出的程序(就是在 dos 下用不了“>”或者“<”重定向的程序),由于我们在装载子进程的时候用了 WS_HIDE,

所以原来的 command.com 的窗口是隐藏的,如果你执行了这种程序那就意味着你失去的对子进程的控制,因为它们不使用标准输入来接收键盘,你也就无法通过管道让它们退出。

在这里还可以引申出匿名管道的另一个用法,如果你执行的不是 command.com 而是类似于 arj.exe 的程序,然后也不用把它的输出显示到 RichEdit 中,而是在程序中处理,

那么,你就可以编写一个 winarj,当然你只需编写窗口界面和 arj.exe 之间的配合而已。

13. INI 文件的操作

概述

在程序中经常要用到设置或者其他少量数据的存盘,以便程序在下一次执行的时候可以使用,比如说保存本次程序执行时窗口的位置、大小、一些用户设置的数据等等,

在 Dos 下编程的时候,我们一般自己产生一个文件,由自己把这些数据写到文件中,然后在下一次执行的时候再读出来使用。

在 Win32 编程中当然你也可以这样干,但 Windows 已经为我们提供了两种方便的办法,那就是使用注册表或者 ini 文件(Profile)来保存少量数据。本文中先介绍一下 .ini 文件的使用。

ini 文件是文本文件,中间的数据格式一般为:

[Section1 Name]

KeyName1=value1

KeyName2=value2

...

[Section2 Name]

KeyName1=value1

KeyName2=value2

ini 文件可以分为几个 Section,每个 Section 的名称用 [] 括起来,在一个 Section 中,可以有很多的 Key,每一个 Key 可以有一个值并占用一行,格式是 Key=value,Win32 对 ini 文件操作的 api 中,

有一部分是对 win.ini 操作的,有一部分是对用户自定义的 ini 文件操作的。Win.in 和 system.ini 是Windows的两个非常重要的初始化文件,Windows将用户所作的选择以及各种变化的系统信息记录在这两个文件中。

System.ini 描述了系统硬件的当前状态,Win.ini 文件则包含了Windows 系统运行环境的当前配置。由于 Win.ini 文件的重要性和常用性,Win32 中有专门对 Win.ini 进行操作的 api,它们是: GetProfileInt - 从 Win.ini 文件的某个 Section 取得一个 key 的整数值,它的原形是:

GetProfileInt(

LPCTSTR lpAppName, // 指向包含 Section 名称的字符串地址

LPCTSTR lpKeyName, // 指向包含 Key 名称的字符串地址

INT nDefault // 如果 Key 值没有找到,则返回缺省的值是多少

);

如果 Key 值没有找到的话,返回值是 nDefault 指定的缺省值,如果 Key 中的值是负数,则返回 0,如果 Key 指定的是数字和字符串的混合,则返回数字部分的值,比如说 x=1234abcd,则返回 1234

GetProfileString - 从 Win.ini 文件的某个 Section 取得一个 key 的字符串,它的原形是:

GetProfileString(

LPCTSTR lpAppName, // 指向包含 Section 名称的字符串地址

LPCTSTR lpKeyName, // 指向包含 Key 名称的字符串地址

LPCTSTR lpDefault, // 如果 Key 值没有找到,则返回缺省的字符串的地址

LPTSTR lpReturnedString, // 返回字符串的缓冲区地址

DWORD nSize // 缓冲区的长度

);

返回的字符串在缓冲区内,返回的 eax 值是返回的字符串的长度(不包括尾部的0)

GetProfileSection - 从 Win.ini 文件中读出整个 Section 的内容,它的原形是:

GetProfileSection(

LPCTSTR lpAppName, // 指向包含 Section 名称的字符串地址

LPTSTR lpReturnedString, // 返回数据的缓冲区地址

DWORD nSize // 返回数据的缓冲区长度

);

WriteProfileSection - 将一个整个 Section 的值 写入 Win.ini 文件的指定 Section 中,它的原形是:

WriteProfileSection(

LPCTSTR lpAppName, // 指向包含 Section 名称的字符串地址

LPCTSTR lpString // 要写入的数据的地址

);

如果 Win.ini 没有指定的 Section,API 会新建立一个并写入数据,如果已经存在,则先删除原来 Seciton 中所有的 Key 值然后写入新的。

WriteProfileString - 将一个 Key 值写入 Win.ini 文件的指定 Section 中,它的原形是:

WriteProfileString(

LPCTSTR lpAppName, // 指向包含 Section 名称的字符串地址

LPCTSTR lpKeyName, // 指向包含 Key 名称的字符串地址

LPCTSTR lpString // 要写的字符串地址

);

如果 Win.ini 没有指定的 Section,API 会新建 Section,如果没有指定的 Key 则新建一个 Key 并写入数据,如果已经存在,则用字符串代替原来的值。 以上的 Api 是对 Win.ini 操作的,当然对于我们来说,用的更多的是在程序运行的目录中建立自己的 ini 文件,如果需要对自己的 ini 文件操作,就要用到另一组 Api,这一组 api 和上面的很象,

只要把上面一组的 Profile 换成 PrivateProfile(私有的)就可以了,参数中也相应的多了一个 ini 文件名的参数。

例如 GetPrivateProfileInt、GetPrivateProfileSection、WritePrivateProfileString 等等, 下面分别介绍: GetPrivateProfileInt - 从 ini 文件的某个 Section 取得一个 key 的整数值,它的原形是:

GetPrivateProfileInt(

LPCTSTR lpAppName, // 指向包含 Section 名称的字符串地址

LPCTSTR lpKeyName, // 指向包含 Key 名称的字符串地址

INT nDefault // 如果 Key 值没有找到,则返回缺省的值是多少

LPCTSTR lpFileName // ini 文件的文件名

);

中间参数和返回值的定义和 GetProfileInt 是一样的。

GetPrivateProfileString - 从 ini 文件的某个 Section 取得一个 key 的字符串,它的原形是:

GetPrivateProfileString(

LPCTSTR lpAppName, // 指向包含 Section 名称的字符串地址

LPCTSTR lpKeyName, // 指向包含 Key 名称的字符串地址

LPCTSTR lpDefault, // 如果 Key 值没有找到,则返回缺省的字符串的地址

LPTSTR lpReturnedString, // 返回字符串的缓冲区地址

DWORD nSize // 缓冲区的长度

LPCTSTR lpFileName // ini 文件的文件名

);

GetPrivateProfileSection - 从 ini 文件中读出整个 Section 的内容,它的原形是:

GetPrivateProfileSection(

LPCTSTR lpAppName, // 指向包含 Section 名称的字符串地址

LPTSTR lpReturnedString, // 返回数据的缓冲区地址

DWORD nSize // 返回数据的缓冲区长度

LPCTSTR lpFileName // ini 文件的文件名

);

这个 api 可以读出整个 section 的内容,当你不知道 section 中有哪些 key 的时候,可以使用这个 api 将整个 section 读出后再处理。

GetPrivateProfileSectionNames - 从 ini 文件中获得 Section 的名称,它的原形是:

GetPrivateProfileSectionNames(

LPTSTR lpszReturnBuffer, // 返回数据的缓冲区地址

DWORD nSize // 返回数据的缓冲区长度

LPCTSTR lpFileName // ini 文件的文件名

);

如果 ini 中有两个 Section: [sec1] 和 [sec2],则返回的是 'sec1',0,'sec2',0,0 ,当你不知道 ini 中有哪些 section 的时候可以用这个 api 来获取名称

WritePrivateProfileSection - 将一个整个 Section 的内容入 ini 文件的指定 Section 中,它的原形是:

WritePrivateProfileSection(

LPCTSTR lpAppName, // 指向包含 Section 名称的字符串地址

LPCTSTR lpString // 要写入的数据的地址

LPCTSTR lpFileName // ini 文件的文件名

);

WritePrivateProfileString - 将一个 Key 值写入 ini 文件的指定 Section 中,它的原形是:

WritePrivateProfileString(

LPCTSTR lpAppName, // 指向包含 Section 名称的字符串地址

LPCTSTR lpKeyName, // 指向包含 Key 名称的字符串地址

LPCTSTR lpString // 要写的字符串地址

LPCTSTR lpFileName // ini 文件的文件名

);

如果 ini 中没有指定的 Section,API 会新建 Section,如果没有指定的 Key 则新建一个 Key 并写入数据,如果已经存在,则用字符串代替原来的值。当指定的 ini 也不存在的时候,API 会自动建立一个新的文件,

所以使用 ini 的好处是我们不必为了保存少量的数据涉及到文件操作,就连查找文件是否存在的操作都不必要。

使用要点:

在我们实际使用的时候,用的最多的是 GetPrivateProfileString 和 WritePrivateProfileString,但在对自定义 ini

文件操作的时候要注意的是,如果 lpFileName 指定的文件没有路径的话,Api 会去 Windows 的安装目录去找而不会在当前目录找,

但是每次用到 ini 函数要获取当前路径显然太麻烦了,这里有一个变通的办法,

你只要在 ini 文件名前面加上 .\ 就可以了,比如说要对本目录下的 user.ini 操作,那么文件名就是 '.\user.ini' 这样显然比较方便。

另外,当你要把一个 Key 清除的时候,可以使用把 lpString 指向一个空的字符串然后使用 WritePrivateProfileString。

当你要把一个 section 的全部内容清空的时候,也不必把 key 一个个的清除,

可以使用把 lpString 指向一个空的字符串然后使用 WritePrivateProfileSection。

14.Win32ASM经验点滴

Q1. 如何隐藏/显示任务栏?

Q2. 如何禁止/允许/显示/隐藏开始按钮?

Q3. 如何创建一个真正的"总在最上面"窗口?

Q4. 如何创建热键?比如CTRL + ALT + A

Q5. 如何获得Windows目录和系统目录?

Q6. 如何从我的程序打开开始菜单?

Q7. 如何关闭被正激活的程序?

Q8. 如何去掉窗口标题?

Q9. 如何知道窗口是否在任务栏中(或可见) ?

Q10. 如何隐藏一个窗口?

Q11. 如何将窗口置于前台?

Q12. 如何屏蔽CTRL+ALT+DEL,ALT+TAB+CTRL+ESC这些键?

Q13. 如何确定Windows任务栏的自动隐藏特性是否被激活?

Q14. 如何使用默认的浏览器或邮件程序?

Q15. 如何用Win32 API显示网络连接对话框?

- 如何隐藏/显示任务栏?

shell db "Shell_TrayWnd",0 ; 任务栏的类名

invoke FindWindow,addr shell,NULL ; 先获得句柄,之后隐藏.

.if eax != 0

invoke ShowWindow,eax,SW_HIDE ; 用SW_SHOW显示

.endif

- 如何禁止/允许/显示/隐藏开始按钮?

.data?

buffer db 127 dup(?)

.data

shell db "Shell_TrayWnd",0

sbar db "BUTTON",0

child dd ?

slen dd ?

.code

invoke FindWindow,addr shell,NULL ; 获得状态栏句柄

mov tray, eax

invoke GetWindow,tray, GW_CHILD ; 获得状态栏的子窗口(如果有的话)

mov child, eax

.if child != 0

invoke GetClassName,child,offset buffer, sizeof buffer ;获得子窗口类名

.if eax > 0

invoke lstrlen, offset buffer ;获得类名长度

mov slen,eax

invoke CharUpperBuff,offset buffer,slen ;转为大写

invoke lstrcmp,addr buffer, addr sbar ;将类名与'BUTTON'比较

.if eax == 0

invoke ShowWindow,child,SW_HIDE ; 隐藏开始按钮

; invoke ShowWindow,child,SW_SHOW ; 显示开始按钮

; invoke EnableWindow,child,FALSE ; 禁止开始按钮

; invoke EnableWindow,child,TRUE ; 允许开始按钮

.endif

.endif

.endif

- 如何创建一个真正的"总在最上面"窗口?

invoke SetWindowPos,hWin, HWND_TOPMOST,NULL,NULL,NULL,NULL,SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE

- 如何创建热键?比如CTRL + ALT + A

.data

hmsg db "HotKey CTRL + ALT + A Works good!",0

hcap db "Hotkey Example",0

.code

.if uMsg == WM_CREATE

invoke RegisterHotKey,hWnd,065h,MOD_CONTROL or MOD_ALT, 041h ; CTRL + ALT + A (041h is 65 - 065h is 101)

.elseif uMsg == WM_HOTKEY

invoke MessageBox,hWin,addr hmsg,addr hcap, MB_OK or MB_ICONINFORMATION

.elseif uMsg == WM_DESTROY

invoke UnregisterHotKey,hWin,065h

invoke PostQuitMessage,NULL

return 0

.endif

-如何获得Windows目录和系统目录

.data

buffer db 50 dup(?)

hCap db "WindowsDirectory",0

.code

invoke GetWindowsDirectory, addr buffer, sizeof buffer ; 置Windows目录于缓冲区中

; invoke GetSystemDirectory, addr buffer, sizeof buffer ;置系统目录于缓冲区中

invoke MessageBox,hWnd, addr buffer, addr hCap, MB_OK or MB_ICONINFORMATION

- 如何从我的程序打开开始菜单?

invoke SendMessage,hWnd,WM_SYSCOMMAND,SC_TASKLIST,NULL

- 如何关闭正被激活的程序 ?

.data

fwin dd ?

.code

invoke GetForegroundWindow

mov fwin,eax

invoke SendMessage, fwin, WM_CLOSE,NULL

- 如何去掉窗口标题 ?

invoke GetWindowLong,hWnd,GWL_STYLE ; 获得当前窗口类

and eax,not WS_CAPTION ; 去掉WS_CAPTION

invoke SetWindowLong,hWnd,GWL_STYLE,eax ; 设置窗口类

- 如何知道窗口是否在任务栏中(或可见)?

invoke IsWindowVisible,hWin

.if eax == TRUE

; 窗口可见

.else

; 窗口不可见

.endif

- 如何隐藏一个窗口?

.data

mirc db "mIRC32",0

mhand dd ?

.code

invoke FindWindow,addr mirc, NULL ; 寻找mIRC32

mov mhand,eax

.if mhand != 0 ; 找到?

invoke ShowWindow,mhand,SW_SHOW ; 显示窗口

; invoke ShowWindow,mhand,SW_HIDE ; 隐藏窗口

.else

; mIRC32未运行...

.endif

- 如何将窗口置于前台?

invoke SetForegroundWindow, mhand

- 如何屏蔽CTRL+ALT+DEL,ALT+TAB+CTRL+ESC这些键?

invoke SystemParametersInfo,SPI_SCREENSAVERRUNNING,1,NULL,NULL

; Windows98 only 1 关闭 0 允许

- 如何确定Windows任务栏的自动隐藏特性是否被激活?.data

AppBar APPBARDATA {} ; {} 指使用默认值... Thanks to TTom

.code

mov AppBar.cbSize, sizeof AppBar

invoke SHAppBarMessage, ABM_GETSTATE, addr AppBar ; ShellApi命令

and eax, ABS_AUTOHIDE

.if eax == TRUE

; 任务栏被隐藏

.else

; 任务栏未被隐藏

.endif

- 如何使用默认的浏览器或邮件程序?

.data

lpPage db "http://win32asm.cjb.net",0

lpMail db "ates@anet.net.tr",0

lpOperation db "open",0

.code

invoke ShellExecute,hWin,addr lpOperation, addr lpPage, NULL, NULL, SW_SHOWNORMAL

invoke ShellExecute,hWin,addr lpOperation, addr lpMail, NULL, NULL, SW_SHOWNORMAL

- 如何用Win32 API显示网络连接对话框?

include \MASM32\INCLUDE\mpr.inc

includelib \MASM32\LIB\mpr.lib

invoke WNetConnectionDialog,hWnd,RESOURCETYPE_DISK

Designed By Atilla Yurtseven

Chinese Translation By Orochi,2000.12.16

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有