有关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 数据 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> include windows.inc include user32.inc include kernel32.inc include comctl32.inc include comdlg32.inc include gdi32.inc includelib user32.lib includelib kernel32.lib includelib comctl32.lib includelib comdlg32.lib includelib gdi32.lib ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Equ 数据 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> DLG_MAIN equ 1000 ID_BITMAP equ 1001 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 数据段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .data? hWinPic dd ? hDcMem dd ? hBitmap dd ? hWinDesktop dd ? hInstance dd ? szBuffer db 256 dup (?) ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 子程序声明 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _ProcDlgMain PROTO :DWORD,:DWORD,:DWORD,:DWORD .data ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 代码段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .code include Win.asm ;******************************************************************** _ProcDlgMain proc uses ebx edi esi, / hWnd:DWORD,wMsg:DWORD,wParam:DWORD,lParam:DWORD local @stPoint:POINT local @hDcDesktop,@hDcPic mov eax,wMsg .if eax == WM_CLOSE invoke EndDialog,hWnd,NULL invoke KillTimer,hWnd,1 invoke DeleteDC,hDcMem invoke DeleteObject,hBitmap ; ******************************************************************* .elseif eax == WM_INITDIALOG invoke GetDlgItem,hWnd,ID_BITMAP mov hWinPic,eax invoke GetDesktopWindow mov hWinDesktop,eax invoke SetWindowPos,hWnd,HWND_TOPMOST,0,0,0,0,/ SWP_NOMOVE or SWP_NOSIZE ; ******************************************************************* invoke GetDC,hWinDesktop mov @hDcDesktop,eax invoke CreateCompatibleDC,@hDcDesktop mov hDcMem,eax invoke CreateCompatibleBitmap,@hDcDesktop,80,80 mov hBitmap,eax invoke SelectObject,hDcMem,hBitmap invoke ReleaseDC,hWinDesktop,@hDcDesktop invoke SetTimer,hWnd,1,100,NULL ; ******************************************************************* .elseif eax == WM_TIMER invoke GetCursorPos,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 invoke GetDC,hWinDesktop mov @hDcDesktop,eax invoke GetDC,hWinPic mov @hDcPic,eax invoke PatBlt,hDcMem,0,0,80,80,BLACKNESS invoke StretchBlt,hDcMem,0,0,80,80,/ @hDcDesktop,@stPoint.x,@stPoint.y,40,40,SRCCOPY invoke BitBlt,@hDcPic,0,0,80,80,/ hDcMem,0,0,SRCCOPY invoke ReleaseDC,hWinDesktop,@hDcDesktop invoke ReleaseDC,hWinPic,@hDcPic .else ;******************************************************************** ; 注意:对话框的消息处理后,要返回 TRUE,对没有处理的消息 ; 要返回 FALSE ;******************************************************************** mov eax,FALSE ret .endif mov eax,TRUE ret _ProcDlgMain endp ;******************************************************************** start: invoke GetModuleHandle,NULL mov hInstance,eax invoke DialogBoxParam,hInstance,DLG_MAIN,NULL,offset _ProcDlgMain,0 invoke ExitProcess,NULL end start
程序的分析和要点
在程序的初始化中,我们用GetDc 取的桌面的屏幕的 DC,再用 CreateCompatibleDC 建立一个内存DC做缓冲区,建立一个位图再用 SelectObject 把 hDcMem 设置为这个位图是为了是 hDcMem 的大小变为 80x80。
invoke GetDC,hWinDesktop mov @hDcDesktop,eax invoke CreateCompatibleDC,@hDcDesktop mov hDcMem,eax invoke CreateCompatibleBitmap,@hDcDesktop,80,80 mov hBitmap,eax invoke SelectObject,hDcMem,hBitmap invoke ReleaseDC,hWinDesktop,@hDcDesktop
然后在程序的每 0.1 秒一次的 WM_TIMER 定时器消息中,我们先用 GetDC 取得桌面和对话框中文本框的句柄,然后用 PatBlt 把内存DC清除为黑色,再用 StretchBlt 从桌面DC中拷贝 40x40的区域到内存 DC 中,新的大小是 80x80(放大功能就是这样实现的),拷贝的位置是用 GetCursorPos 取得的,也就是鼠标的当前位置,最后用 BitBlt 把内存DC 拷贝到对话框中。如果直接把桌面DC 拷贝到对话框中也可以,但是当鼠标移动到屏幕边缘上时,由于屏幕外的点是无效的,所以对话框中的一部分会花屏,大家可以改动程序试试。
invoke GetCursorPos,addr @stPoint invoke GetDC,hWinDesktop mov @hDcDesktop,eax invoke GetDC,hWinPic mov @hDcPic,eax invoke PatBlt,hDcMem,0,0,80,80,BLACKNESS invoke StretchBlt,hDcMem,0,0,80,80,/ @hDcDesktop,@stPoint.x,@stPoint.y,40,40,SRCCOPY invoke BitBlt,@hDcPic,0,0,80,80,/ hDcMem,0,0,SRCCOPY invoke ReleaseDC,hWinDesktop,@hDcDesktop invoke ReleaseDC,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 数据 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> include windows.inc include user32.inc include kernel32.inc include comctl32.inc include comdlg32.inc include shell32.inc include gdi32.inc includelib user32.lib includelib kernel32.lib includelib comctl32.lib includelib comdlg32.lib includelib shell32.lib includelib gdi32.lib ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Equ 数据 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ;************** Equ 数据 ********************************** IDI_MAIN equ 1 ;icon IDC_HANDLE equ 2 ;Cursor ;************** Equ 数据 ********************************** DLG_ABOUT equ 1200 ;dialog - about ID_ABOUT_OK equ 1201 ID_EMAIL equ 1202 ID_HOMEPAGE equ 1203 ;************** Equ 数据 ********************************** IDM_MAIN equ 2000 IDM_ABOUT equ 2001 IDM_EXIT equ 2002 ;************** Equ 数据 ********************************** IDB_0 equ 3000 ;bitmap ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 数据段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .data? hInstance dd ? hWinMain dd ? hIcon dd ? hCursor dd ? hMenu dd ? hBmpBack dd ? ;background bitmap hDcBack dd ? ;************** 数据段 ************************************ .data szClassName db 'ShapeWindow',0 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 代码段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .code if DEBUG include Debug.asm endif ;******************************************************************** ; 设置窗口形状为BMP图形形状 ; 参数:窗口句柄,BMP图形句柄 ; 输入BMP图形要求:0,0处颜色为背景色 ;******************************************************************** _SetWindowShape proc hWnd: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 invoke GetObject,hBitMap,sizeof BITMAP,addr @stBmp invoke GetWindowRect,hWnd,addr @stRect invoke ShowWindow,hWnd,SW_HIDE invoke MoveWindow,hWnd,@stRect.left,@stRect.top,/ @stBmp.bmWidth,@stBmp.bmHeight,FALSE invoke GetDC,hWnd mov @hDC,eax invoke CreateCompatibleDC,@hDC mov @hBmpDC,eax invoke SelectObject,@hBmpDC,hBitMap ;*************** 计算窗口形状 *************************************** invoke GetPixel,@hBmpDC,0,0 mov @rgbBack,eax invoke CreateRectRgn,0,0,0,0 mov @hRgn,eax mov @dwY,0 .while TRUE mov @dwX,0 mov @dwStartX,-1 .while TRUE invoke GetPixel,@hBmpDC,@dwX,@dwY .if @dwStartX == -1 .if eax != @rgbBack mov eax,@dwX mov @dwStartX,eax .endif .else .if eax == @rgbBack mov ecx,@dwY inc ecx invoke CreateRectRgn,@dwStartX,@dwY,@dwX,ecx invoke CombineRgn,@hRgn,@hRgn,eax,RGN_OR mov @dwStartX,-1 .else mov eax,@dwX .if eax == @stBmp.bmWidth inc eax mov ecx,@dwY inc ecx invoke CreateRectRgn,@dwStartX,@dwY,eax,ecx invoke CombineRgn,@hRgn,@hRgn,eax,RGN_OR mov @dwStartX,-1 .endif .endif .endif inc @dwX mov eax,@dwX .break .if eax > @stBmp.bmWidth .endw inc @dwY mov eax,@dwY .break .if eax > @stBmp.bmHeight .endw invoke SetWindowRgn,hWnd,@hRgn,TRUE ;******************************************************************** invoke BitBlt,@hDC,0,0,@stBmp.bmWidth,@stBmp.bmHeight,/ @hBmpDC,0,0,SRCCOPY invoke DeleteDC,@hBmpDC invoke ReleaseDC,hWnd,@hDC invoke InvalidateRect,hWnd,NULL,-1 ret _SetWindowShape endp ;******************************************************************** ; 将窗口移动到屏幕中间 ; 参数:窗口句柄 ;******************************************************************** _CenterWindow proc hWnd:DWORD local @stRectDeskTop:RECT,@stRectWin:RECT local @dwWidth:DWORD,@dwHeight:DWORD invoke GetWindowRect,hWnd,addr @stRectWin invoke GetDesktopWindow mov ebx,eax invoke GetWindowRect,ebx,addr @stRectDeskTop mov eax,@stRectWin.bottom sub eax,@stRectWin.top mov @dwHeight,eax mov eax,@stRectWin.right sub eax,@stRectWin.left mov @dwWidth,eax mov ebx,@stRectDeskTop.bottom sub ebx,@dwHeight shr ebx,1 mov ecx,@stRectDeskTop.right sub ecx,@dwWidth shr ecx,1 invoke MoveWindow,hWnd,ecx,ebx,@dwWidth,@dwHeight,FALSE ret _CenterWindow endp ;******************************************************************** include About.asm ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 程序开始 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start: call _WinMain invoke ExitProcess,NULL ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 主窗口程序 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _WinMain proc local @stWcMain:WNDCLASSEX local @stMsg:MSG invoke InitCommonControls invoke GetModuleHandle,NULL mov hInstance,eax invoke LoadIcon,hInstance,IDI_MAIN mov hIcon,eax invoke LoadMenu,hInstance,IDM_MAIN invoke GetSubMenu,eax,0 ;PopUp 菜单要用到子菜单 mov hMenu,eax ;*************** 注册窗口类 ***************************************** invoke LoadCursor,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 mov eax,hInstance mov @stWcMain.hInstance,eax mov @stWcMain.hIcon,0 mov @stWcMain.hbrBackground,COLOR_WINDOW + 1 mov @stWcMain.lpszClassName,offset szClassName mov @stWcMain.lpszMenuName,0 invoke RegisterClassEx,addr @stWcMain ;***************** 建立输出窗口 ***************************************** ; 属性:没有标题栏,不显示在任务栏 ;******************************************************************** invoke CreateWindowEx,WS_EX_TOOLWINDOW,/ offset szClassName,NULL,/ WS_POPUP or WS_SYSMENU,/ 0,0,1,1,/ NULL,NULL,hInstance,NULL invoke ShowWindow,hWinMain,SW_SHOWNORMAL invoke UpdateWindow,hWinMain ;*************** 消息循环 ******************************************* .while TRUE invoke GetMessage,addr @stMsg,NULL,0,0 .break .if eax == 0 invoke TranslateMessage,addr @stMsg invoke DispatchMessage,addr @stMsg .endw ret _WinMain endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> WndMainProc proc uses ebx edi esi, / hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD local @stPos:POINT local @stPs:PAINTSTRUCT,@hDC:DWORD mov eax,uMsg .if eax == WM_CREATE mov eax,hWnd mov hWinMain,eax call _Init ;******************************************************************** .elseif eax == WM_PAINT invoke BeginPaint,hWnd,addr @stPs mov @hDC,eax mov eax,@stPs.rcPaint.right sub eax,@stPs.rcPaint.left mov ecx,@stPs.rcPaint.bottom sub ecx,@stPs.rcPaint.top invoke BitBlt,@hDC,@stPs.rcPaint.left,@stPs.rcPaint.top,eax,ecx,/ hDcBack,@stPs.rcPaint.left,@stPs.rcPaint.top,SRCCOPY invoke EndPaint,hWnd,addr @stPs ;******************************************************************** ; 由于没有菜单,下面代码用于按下右键时弹出POPUP菜单 ;******************************************************************** .elseif eax == WM_RBUTTONDOWN .if wParam == MK_RBUTTON invoke GetCursorPos,addr @stPos invoke TrackPopupMenu,hMenu,TPM_LEFTALIGN,@stPos.x,@stPos.y,NULL,hWnd,NULL .endif ;******************************************************************** ; 由于没有标题栏,下面代码用于按下左键时移动窗口 ;******************************************************************** .elseif eax == WM_LBUTTONDOWN invoke UpdateWindow,hWnd ;即时刷新 invoke ReleaseCapture invoke SendMessage,hWnd,WM_NCLBUTTONDOWN,HTCAPTION,0 ;******************************************************************** .elseif eax == WM_COMMAND .if lParam == 0 mov eax,wParam .if ax == IDM_EXIT call _Quit .elseif ax == IDM_ABOUT invoke DialogBoxParam,hInstance,DLG_ABOUT,hWnd,offset AboutDialogProc,DLG_ABOUT .endif .endif ;******************************************************************** .elseif eax == WM_CLOSE call _Quit ;******************************************************************** .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif ;******************************************************************** ; 注意:WndProc 处理 Windows 消息后,必须在 Eax 中返回 0 ; 但是由 DefWindowProc 处理后的返回值不能改变,否则窗口 ; 将无法显示! ;******************************************************************** xor eax,eax ret WndMainProc endp ;******************************************************************** _Init proc local @hDC invoke SendMessage,hWinMain,WM_SETTEXT,0,offset szClassName invoke SendMessage,hWinMain,WM_SETICON,ICON_SMALL,hIcon invoke LoadBitmap,hInstance,IDB_0 ;装入背景图片 mov hBmpBack,eax invoke _SetWindowShape,hWinMain,hBmpBack ;设置窗口形状为背景图片 invoke GetDC,hWinMain mov @hDC,eax invoke CreateCompatibleDC,@hDC ;建立背景及数字 DC mov hDcBack,eax invoke ReleaseDC,hWinMain,@hDC invoke SelectObject,hDcBack,hBmpBack invoke _CenterWindow,hWinMain ret _Init endp ;******************************************************************** _Quit proc local @stWindow:RECT invoke DestroyMenu,hMenu invoke DeleteDC,hDcBack invoke DeleteObject,hBmpBack invoke DestroyWindow,hWinMain invoke PostQuitMessage,NULL ret _Quit endp ;******************************************************************** end start程序的分析和要点
创建窗口的时候,窗口风格为 WS_POPUP,所以创建的窗口没有标题栏,这样的窗口适合于设置成特殊形状的窗口
invoke CreateWindowEx,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 invoke UpdateWindow,hWnd ;即时刷新 invoke ReleaseCapture invoke SendMessage,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 数据 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> include windows.inc include user32.inc include kernel32.inc include comctl32.inc include comdlg32.inc includelib user32.lib includelib kernel32.lib includelib comctl32.lib includelib comdlg32.lib ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Equ 数据 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> IDI_MAIN equ 1 IDI_MOON1 equ 2 IDI_MOON2 equ 3 IDI_MOON3 equ 4 IDI_MOON4 equ 5 IDI_MOON5 equ 6 IDI_MOON6 equ 7 IDI_MOON7 equ 8 IDI_MOON8 equ 9 DLG_MAIN equ 1000 ID_MOON equ 1001 ID_TIMER1 equ 1 ID_TIMER2 equ 2 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 数据段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .data? hInstance dd ? dwCounter1 dd ? dwCounter2 dd ? hIcon1 dd ? hIcon2 dd ? hIcon3 dd ? hIcon4 dd ? hIcon5 dd ? hIcon6 dd ? hIcon7 dd ? hIcon8 dd ? szBuffer db 256 dup (?) .data ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 代码段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .code ;******************************************************************** _ProcDlgMain proc uses ebx edi esi, / hWnd:DWORD,wMsg:DWORD,wParam:DWORD,lParam:DWORD mov eax,wMsg ;******************************************************************** .if eax == WM_CLOSE invoke KillTimer,hWnd,ID_TIMER1 invoke KillTimer,hWnd,ID_TIMER2 invoke EndDialog,hWnd,NULL ;******************************************************************** .elseif eax == WM_INITDIALOG mov edi,offset hIcon1 mov ebx,IDI_MOON1 mov ecx,8 @@: push ecx invoke LoadIcon,hInstance,ebx cld stosd inc ebx pop ecx loop @B invoke SetTimer,hWnd,ID_TIMER1,500,NULL invoke SetTimer,hWnd,ID_TIMER2,200,NULL invoke SendMessage,hWnd,WM_SETICON,ICON_SMALL,hIcon1 ;******************************************************************** .elseif eax == WM_TIMER .if wParam == ID_TIMER1 inc dwCounter1 .if dwCounter1 == 8 mov dwCounter1,0 .endif mov eax,dwCounter1 shl eax,2 add eax,offset hIcon1 mov eax,[eax] invoke SendMessage,hWnd,WM_SETICON,ICON_SMALL,eax .else inc dwCounter2 .if dwCounter2 == 8 mov dwCounter2,0 .endif mov eax,dwCounter2 shl eax,2 add eax,offset hIcon1 mov eax,[eax] invoke SendDlgItemMessage,hWnd,ID_MOON,BM_SETIMAGE,IMAGE_ICON,eax .endif ;******************************************************************** .else mov eax,FALSE ret .endif mov eax,TRUE ret _ProcDlgMain endp ;******************************************************************** start: invoke GetModuleHandle,NULL mov hInstance,eax invoke DialogBoxParam,hInstance,DLG_MAIN,NULL,offset _ProcDlgMain,0 invoke ExitProcess,NULL ;******************************************************************** end start程序的分析和要点
有了上面的介绍,这个程序是很容易看懂的,在 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 数据 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> include windows.inc include user32.inc include kernel32.inc include comctl32.inc include comdlg32.inc include gdi32.inc includelib user32.lib includelib kernel32.lib includelib comctl32.lib includelib comdlg32.lib includelib gdi32.lib ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Equ 数据 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> DLG_MAIN equ 3000 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ID_BROWSE equ 3001 ID_RUN equ 3002 ID_EXIT equ 3003 ID_TEXT equ 3004 F_RUNNING equ 0001h ;进程在运行中 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 数据段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .data? stStartUp STARTUPINFO <?> stProcInfo PROCESS_INFORMATION <?> stOpenFileName OPENFILENAME <?> hRunThread dd ? hInstance dd ? hWinMain dd ? hIcon dd ? szBuffer db 512 dup (?) dwFlag dd ? ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .data szExcute db '执行(&E)',0 ;按钮文字 szKill db '终止(&E)',0 szExcuteError db '启动应用程序错误!',0 szTitleOpen db "Open executable file...",0 szExt db '*.exe',0 szFilter db 'Excutable Files',0,'*.exe;*.com',0 db 0 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 代码段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .code if DEBUG include Debug.asm endif include Win.asm ;******************************************************************** ; 执行程序用的线程 ; 1. 用 CreateProcess 建立进程 ; 2. 用 WaitForSingleOject 等待进程结束 ;******************************************************************** _RunThread proc uses ebx ecx edx esi edi,/ dwParam:DWORD or dwFlag,F_RUNNING ;******************************************************************** ; 取消“退出”按钮并把“执行”按钮改为“中止” ;******************************************************************** invoke GetDlgItem,hWinMain,ID_EXIT invoke EnableWindow,eax,FALSE invoke SendDlgItemMessage,hWinMain,ID_RUN,WM_SETTEXT,0,offset szKill ;******************************************************************** ; 执行文件,如果成功则等待程序结束 ;******************************************************************** invoke GetStartupInfo,addr stStartUp invoke CreateProcess,NULL,addr szBuffer,NULL,NULL,/ NULL,NORMAL_PRIORITY_CLASS,NULL,NULL,offset stStartUp,offset stProcInfo .if eax != 0 invoke WaitForSingleObject,stProcInfo.hProcess,INFINITE invoke CloseHandle,stProcInfo.hProcess invoke CloseHandle,stProcInfo.hThread .else invoke MessageBox,hWinMain,addr szExcuteError,NULL,MB_OK or MB_ICONERROR .endif ;******************************************************************** ; Enable “退出”按钮并把“中止”按钮改为“执行” ;******************************************************************** invoke GetDlgItem,hWinMain,ID_EXIT invoke EnableWindow,eax,TRUE invoke SendDlgItemMessage,hWinMain,ID_RUN,WM_SETTEXT,0,offset szExcute and dwFlag,not F_RUNNING ret _RunThread endp ;******************************************************************** ; 窗口程序 ;******************************************************************** DialogMainProc proc uses ebx edi esi, / hWnd:DWORD,wMsg:DWORD,wParam:DWORD,lParam:DWORD mov eax,wMsg ;******************************************************************** .if eax == WM_INITDIALOG mov eax,hWnd mov hWinMain,eax call _Init ;******************************************************************** .elseif eax == WM_CLOSE invoke EndDialog,hWinMain,NULL ;******************************************************************** .elseif eax == WM_COMMAND mov eax,wParam .if ax == ID_BROWSE call _BrowseFile call _CheckText .elseif ax == ID_TEXT invoke GetDlgItemText,hWinMain,ID_TEXT,addr szBuffer,512 call _CheckText .elseif ax == ID_RUN ;******************************************************************** ; 如果没有在执行中(dwFlag 没有置位) 则建立线程,在线程中执行程序 ; 如果已经在执行中,则用 TerminateProcess 终止执行 ;******************************************************************** test dwFlag,F_RUNNING .if ZERO? invoke CreateThread,NULL,NULL,offset _RunThread,/ NULL,NULL,offset hRunThread .else invoke TerminateProcess,stProcInfo.hProcess,-1 .endif .elseif ax == ID_EXIT invoke EndDialog,hWinMain,NULL .endif .else ;******************************************************************** ; 注意:对话框的消息处理后,要返回 TRUE,对没有处理的消息 ; 要返回 FALSE ;******************************************************************** mov eax,FALSE ret .endif mov eax,TRUE ret DialogMainProc endp ;******************************************************************** ; 程序入口 ;******************************************************************** start: invoke InitCommonControls invoke GetModuleHandle,NULL mov hInstance,eax invoke DialogBoxParam,hInstance,DLG_MAIN,NULL,offset DialogMainProc,0 invoke ExitProcess,NULL ;******************************************************************** _Init proc invoke _CenterWindow,hWinMain invoke SendDlgItemMessage,hWinMain,ID_TEXT,EM_LIMITTEXT,512,NULL invoke GetDlgItem,hWinMain,ID_RUN invoke EnableWindow,eax,FALSE ret _Init endp ;******************************************************************** ; 根据 text control 中有无字符决定是否将“执行”按钮 Disable 掉 ;******************************************************************** _CheckText proc invoke GetDlgItemText,hWinMain,ID_TEXT,addr szBuffer,512 invoke lstrlen,addr szBuffer .if eax != 0 || (dwFlag & F_RUNNING) invoke GetDlgItem,hWinMain,ID_RUN invoke EnableWindow,eax,TRUE .else invoke GetDlgItem,hWinMain,ID_RUN invoke EnableWindow,eax,FALSE .endif ret _CheckText endp ;******************************************************************** _BrowseFile proc mov stOpenFileName.Flags,OFN_PATHMUSTEXIST or OFN_FILEMUSTEXIST mov stOpenFileName.lStructSize,SIZEOF stOpenFileName mov eax,hWinMain mov stOpenFileName.hwndOwner,eax mov stOpenFileName.lpstrFilter,offset szFilter ;扩展名 mov stOpenFileName.lpstrFile,offset szBuffer ;文件名缓冲 mov stOpenFileName.nMaxFile,512 ;文件名缓冲长度 mov stOpenFileName.lpstrInitialDir,0 mov stOpenFileName.lpstrTitle,offset szTitleOpen mov stOpenFileName.lpstrDefExt,offset szExt invoke GetOpenFileName,offset stOpenFileName .if eax == FALSE ret .endif invoke SetDlgItemText,hWinMain,ID_TEXT,addr szBuffer ret _BrowseFile endp ;******************************************************************** end start程序的分析和要点
本程序在使用调用 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 感到陌生的话,请先阅读上一篇教程。
源程序 - 汇编源文件
DEBUG equ 0 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 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 数据 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> include windows.inc include user32.inc include kernel32.inc include comctl32.inc include comdlg32.inc include gdi32.inc includelib user32.lib includelib kernel32.lib includelib comctl32.lib includelib comdlg32.lib includelib gdi32.lib ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Equ 数据 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ICO_MAIN equ 1000 MENU_MAIN equ 2000 IDM_EXEC equ 2001 IDM_EXIT equ 2002 F_RUNNING equ 0001h ;进程在运行中 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 数据段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .data? stStartUp STARTUPINFO <?> hInstance dd ? hMenu dd ? hWinMain dd ? hWinText dd ? hFont dd ? hRunThread dd ? hRead1 dd ? hWrite1 dd ? hRead2 dd ? hWrite2 dd ? szBuffer db 512 dup (?) dwFlag dd ? ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .data szMenuExecute db '连接 MS-&DOS 方式',0 szExcuteError db '启动应用程序错误!',0 szCaption db '管道示例程序 ... http://asm.yeah.net',0 szClassName db 'PipeExample',0 ;szDllName db 'riched32.dll',0 ;szClassNameRedit db 'RichEdit',0 szDllName db 'riched20.dll',0 szClassNameRedit db 'richedit20a',0 szCommand db 'c:/command.com',0 stLogFont LOGFONT <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 if DEBUG include Debug.asm endif include Win.asm ;******************************************************************** ; 执行程序用的线程 ; 1. 用 CreateProcess 建立进程 ; 2. 用 WaitForSingleOject 等待进程结束 ;******************************************************************** _RunThread proc uses ebx ecx edx esi edi,/ dwParam:DWORD local @stSecurity:SECURITY_ATTRIBUTES local @dwExitCode local @dwBytesRead local @stRange:CHARRANGE or dwFlag,F_RUNNING ;******************************************************************** ; “执行”菜单改为“结束” ;******************************************************************** invoke EnableMenuItem,hMenu,IDM_EXEC,MF_GRAYED invoke EnableMenuItem,hMenu,IDM_EXIT,MF_GRAYED ;******************************************************************** ; 建立管道 ;******************************************************************** mov @stSecurity.nLength,sizeof SECURITY_ATTRIBUTES mov @stSecurity.lpSecurityDescriptor,NULL mov @stSecurity.bInheritHandle,TRUE invoke CreatePipe,addr hRead1,addr hWrite1,addr @stSecurity,NULL invoke CreatePipe,addr hRead2,addr hWrite2,addr @stSecurity,NULL ;******************************************************************** ; 执行文件,如果成功则等待程序结束 ;******************************************************************** invoke GetStartupInfo,addr stStartUp mov eax,hRead1 mov stStartUp.hStdInput,eax mov eax,hWrite2 mov stStartUp.hStdOutput,eax mov stStartUp.hStdError,eax mov stStartUp.dwFlags,STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW mov stStartUp.wShowWindow,SW_HIDE invoke CreateProcess,NULL,addr szCommand,NULL,NULL,/ NULL,NORMAL_PRIORITY_CLASS,NULL,NULL,offset stStartUp,offset stProcInfo .if eax != 0 .while TRUE invoke GetExitCodeProcess,stProcInfo.hProcess,addr @dwExitCode .break .if @dwExitCode != STILL_ACTIVE invoke PeekNamedPipe,hRead2,addr szBuffer,511,addr @dwBytesRead,NULL,NULL .if @dwBytesRead != 0 invoke RtlZeroMemory,addr szBuffer,512 invoke ReadFile,hRead2,addr szBuffer,@dwBytesRead,addr @dwBytesRead,NULL mov @stRange.cpMin,-1 mov @stRange.cpMax,-1 invoke SendMessage,hWinText,EM_EXSETSEL,0,addr @stRange invoke SendMessage,hWinText,EM_REPLACESEL,FALSE,addr szBuffer invoke SendMessage,hWinText,EM_SCROLLCARET,NULL,NULL invoke SendMessage,hWinText,WM_SETFONT,hFont,0 .endif .endw invoke CloseHandle,stProcInfo.hProcess invoke CloseHandle,stProcInfo.hThread .else invoke MessageBox,hWinMain,addr szExcuteError,NULL,MB_OK or MB_ICONERROR .endif ;******************************************************************** ; 关闭管道 ;******************************************************************** invoke CloseHandle,hRead1 invoke CloseHandle,hWrite1 invoke CloseHandle,hRead2 invoke CloseHandle,hWrite2 ;******************************************************************** ; 把“结束”菜单改为“执行” ;******************************************************************** invoke EnableMenuItem,hMenu,IDM_EXEC,MF_ENABLED invoke EnableMenuItem,hMenu,IDM_EXIT,MF_ENABLED invoke EnableWindow,hWinText,FALSE and dwFlag,not F_RUNNING ret _RunThread endp ;******************************************************************** ; 窗口程序 ;******************************************************************** WndMainProc proc uses ebx edi esi, / hWnd:DWORD,wMsg:DWORD,wParam:DWORD,lParam:DWORD mov eax,wMsg ;******************************************************************** .if eax == WM_CREATE mov eax,hWnd mov hWinMain,eax call _Init ;******************************************************************** .elseif eax == WM_SIZE mov edx,lParam mov ecx,edx shr ecx,16 and edx,0ffffh invoke MoveWindow,hWinText,0,0,edx,ecx,TRUE invoke PostMessage,hWinText,WM_SIZE,wParam,lParam ;******************************************************************** .elseif eax == WM_CLOSE test dwFlag,F_RUNNING .if ZERO? invoke DestroyWindow,hWinMain invoke PostQuitMessage,NULL .endif ;******************************************************************** .elseif eax == WM_COMMAND mov eax,wParam .if ax == IDM_EXEC ;******************************************************************** ; 如果没有在执行中(dwFlag 没有置位) 则建立线程,在线程中执行程序 ; 如果已经在执行中,则用 TerminateProcess 终止执行 ;******************************************************************** test dwFlag,F_RUNNING .if ZERO? invoke EnableWindow,hWinText,TRUE invoke SetFocus,hWinText invoke CreateThread,NULL,NULL,offset _RunThread,/ NULL,NULL,offset hRunThread .else invoke TerminateProcess,stProcInfo.hProcess,-1 .endif .elseif ax == IDM_EXIT invoke DestroyWindow,hWinMain invoke PostQuitMessage,NULL .endif .else invoke DefWindowProc,hWnd,wMsg,wParam,lParam ret .endif xor eax,eax ret WndMainProc endp ;******************************************************************** ; 程序入口 ;******************************************************************** start: call _WinMain invoke ExitProcess,NULL ;******************************************************************** _WinMain proc local @stWcMain:WNDCLASSEX local @stMsg:MSG local @hRichEdit invoke LoadLibrary,offset szDllName mov @hRichEdit,eax invoke InitCommonControls invoke GetModuleHandle,NULL mov hInstance,eax invoke LoadMenu,hInstance,MENU_MAIN mov hMenu,eax ;***************** 注册窗口类 *************************************** invoke LoadCursor,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 mov eax,hInstance mov @stWcMain.hInstance,eax invoke LoadIcon,hInstance,ICO_MAIN mov @stWcMain.hIcon,eax mov @stWcMain.hbrBackground,COLOR_BTNFACE+1 mov @stWcMain.lpszClassName,offset szClassName mov @stWcMain.lpszMenuName,0 invoke RegisterClassEx,addr @stWcMain ;***************** 建立输出窗口 ***************************************** invoke CreateWindowEx,NULL,/ offset szClassName,offset szCaption,/ WS_OVERLAPPEDWINDOW,/ 0,0,680,420,/ NULL,hMenu,hInstance,NULL invoke ShowWindow,hWinMain,SW_SHOWNORMAL invoke UpdateWindow,hWinMain ;******************************************************************** .while TRUE invoke GetMessage,addr @stMsg,NULL,0,0 .break .if eax == 0 invoke TranslateMessage,addr @stMsg invoke DispatchMessage,addr @stMsg .endw invoke FreeLibrary,@hRichEdit invoke DeleteObject,hFont ret _WinMain endp ;******************************************************************** ; 输入程序 ;******************************************************************** _InputProc proc uses ebx edi esi, / hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD local @szBuffer[4]:BYTE local @dwBytesWrite mov eax,uMsg .if eax == WM_CHAR mov eax,wParam movzx eax,al mov dword ptr @szBuffer,eax test dwFlag,F_RUNNING .if !ZERO? invoke WriteFile,hWrite1,addr @szBuffer,1,addr @dwBytesWrite,NULL .endif xor eax,eax ret .endif invoke GetWindowLong,hWnd,GWL_USERDATA invoke CallWindowProc,eax,hWnd,uMsg,wParam,lParam ret _InputProc endp ;******************************************************************** _Init proc ;*************** 建立输出 RICHEDIT 窗口 *********************************** invoke CreateWindowEx,WS_EX_CLIENTEDGE,offset szClassNameRedit,/ NULL,WS_CHILD OR WS_VISIBLE OR WS_VSCROLL OR WS_HSCROLL/ OR ES_MULTILINE OR ES_AUTOHSCROLL OR ES_AUTOVSCROLL,/ 0,0,0,0,/ hWinMain,NULL,hInstance,NULL mov hWinText,eax ;*************** 设置字体 *********************************************** invoke CreateFontIndirect,offset stLogFont mov hFont,eax invoke SendMessage,hWinText,WM_SETFONT,hFont,0 invoke SendMessage,hWinText,EM_SETREADONLY,TRUE,NULL invoke SetWindowLong,hWinText,GWL_WNDPROC,offset _InputProc invoke SetWindowLong,hWinText,GWL_USERDATA,eax invoke EnableWindow,hWinText,FALSE invoke _CenterWindow,hWinMain invoke SetFocus,hWinText ret _Init endp ;******************************************************************** end start程序的分析和要点
在程序中,我先建立了一个 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
.endifinvoke SetWindowPos,hWin, HWND_TOPMOST,NULL,NULL,NULL,NULL,SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE
.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.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_ICONINFORMATIONinvoke SendMessage,hWnd,WM_SYSCOMMAND,SC_TASKLIST,NULL
.data
fwin dd ?
.code
invoke GetForegroundWindow
mov fwin,eax
invoke SendMessage, fwin, WM_CLOSE,NULLinvoke 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_SHOWNORMALinclude /MASM32/INCLUDE/mpr.inc
includelib /MASM32/LIB/mpr.lib
invoke WNetConnectionDialog,hWnd,RESOURCETYPE_DISKDesigned By Atilla Yurtseven
Chinese Translation By Orochi,2000.12.16