Win32汇编教程的知识点
有关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 上面的要定时处理的程序,它可以定时显示程序运行的情况,如发送了多少内容,接收了多到内容等等。
-
多任务 - 如果程序有大量的数据处理,除了用多线程的办法,还可以用定时器,在每一个定时器消息中处理一小块内容。
总的来说,在 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 消息并没有被补上,
- 所以如果你设计一个时钟程序,你不能使用定时器消息来计数,而必须在消息中每次获取正确的系统时间。
-
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 函数就行了。
-
取消定时器
-
如果 lpTimerFunc 参数是 NULL,Windows 会把 WM_TIMER 消息放入消息循环中,消息的 hWnd 是第一个参数中指定的 hWnd,也就是说向这个窗口发送了 WM_TIMER 消息。
-
调用的参数是 CALLBACK TimerProc(hwnd,WM_TIMER,iTimerID,dwTime),iTimerID 是定时器 ID,dwTime 是系统时间;
-
如果这个参数不是 NULL,windows 在到时间后会调用lpTimerFunc 指定的过程,
-
如果你要设置一个1秒的定时器,这个值就是1000,lpTimerFunc 是处理定时器消息的过程,
-
用来区分在多个定时器的情况下,这条消息是由哪个定时器产生的。uElapse 是定时器间隔的毫秒数,
在本节的例子程序中,我在对话框中的 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来关闭该管道。
-
建立连接之后,客户端与服务器端即可通过 ReadFile 和 WriteFile,利用得到的管道文件句柄,彼此间进行信息交换。
-
此时服务端将接受客户端的连接请求,成功建立连接,服务端 ConnectNamedPipe 返回 True
-
客户端通过函数 WaitNamedPipe 来等待管道的出现,如果在超时值变为零以前,有一个管道可以使用,则 WaitNamedPipe 将返回 True,并通过调用 CreateFile 或 CallNamedPipe 来呼叫对服务端的连接。
-
服务端侦听来自客户端的连接请求,该功能通过 ConnectNamedPipe 函数实现。
由于命名管道使用时作为客户端的程序必须知道管道的名称,所以更多的用在同一“作者”编写的服务器/工作站程序中,你不可能随便找出一个程序来要求它和你写的程序来通过命名管道通信。
而匿名管道的使用则完全不同,它允许你和完全不相干的进程通信,条件是这个进程通过控制台“console”来输入输出,典型的例子是老的 Dos 应用程序,
它们在运行时 Windows 为它们开了个 Dos 窗口,它们的输入输出就是 console 方式的。还有一些标准的 Win32 程序也使用控制台输入输出,如果在 Win32 编程中不想使用图形界面,
你照样可以使用 AllocConsole 得到一个控制台,然后通过 GetStdHandle 得到输入或输出句柄,再通过 WriteConsole 或 WriteFile 把结果输出到控制台(通常是一个象 Dos 窗口)的屏幕上。
虽然这些程序看起来象 Dos 程序,但它们是不折不扣的 Win32 程序,如果你在纯 Dos 下使用,就会显示“The program must run under Windows!”。一个控制台有三个句柄:标准输入、标准输出和和标准错误句柄,标准输入、标准输出句柄是可以重新定向的,你可以用匿名管道来代替它,这样一来,你可以在管道的另一端用别的进程来接收或输入,
而控制台一方并没有感到什么不同,就象 Dos 下的
-
lpStartupInfo:STARTUPINFO 结构,用于在创建子进程时设置各种属性。
-
lpCurrentDirectory:表示子进程运行的初始目录。
-
lpEnvironment:表示子进程所使用的环境变量,如果为NULL,则表示与当前进程使用相同的环境变量。
-
dwCreationFlags:表示创建标记,通过该标记可以设置进程的创建状态和优先级别。常用的有下面的标记:
-
bInheritHandles:表示当前进程中的打开的句柄是否能够被创建的子进程所继承。
-
lpProcessAttributes,lpThreadAttributes:分别描述了创建的进程和线程安全属性,如果使用NULL表示使用默认的安全描述。
-
lpCommandLine:为参数行,如果无参数可以为NULL,在有参数传递给进程时可以如下设置:lpApplicationName=文件名;lpCommandLine=参数,或者 lpApplicationName=NULL;lpCommandLine=文件名 + 参数。
-
CombineRgn(hDest,hSource1,hSource2,CombineMode) - 合并区域
-
CreatePolygonRgn(lpPoints,NumberOfPoints,Mode) - 建立多边形区域,这些API返回区域句柄
-
CreateEllipticRgn(Left,Top,Right,Bottom) - 建立椭圆区域
-
由于窗口没有标题栏,所以在右击窗口时弹出一个菜单。(参考菜单一节)
-
在窗口的 WM_PAINT 消息中更新窗口的图片。(参考图形界面一节)
-
设置窗口为特殊形状。(见下面的程序分析)
本文地址:http://www.45fan.com/dnjc/71198.html