Chinaunix首页 | 论坛 | 博客
  • 博客访问: 129620
  • 博文数量: 37
  • 博客积分: 1490
  • 博客等级: 上尉
  • 技术积分: 326
  • 用 户 组: 普通用户
  • 注册时间: 2009-12-01 16:38
文章分类

全部博文(37)

文章存档

2011年(1)

2010年(23)

2009年(13)

我的朋友

分类: WINDOWS

2009-12-18 15:40:17

                       第二十一课:管道
 
   什么是管道,管道的用处是什么,为了让它更加有趣,我将此技术放在如何改变一个编辑控件的背景和文本颜色上。
 
     管道是一通讯管道或者是有两端的通路。你能用管道在两个不同的进程或同一个进程之间交换数据。它看起来像步话机。你给对方一个装置然后他就能用它来和你进行通讯。
 
   这里有两种类型的管道:有名管道和匿名管道。
         匿名管道的意思是:你使用它的时候不用知道它的名字。
         有名管道正好相反:你必须知道它的名字才能使用它。
   也可以依据管道的特性来对它加以分类:单向的或双向的。
         在一个单向管道中,数据只能在一个方向流动:即从一端流向另一端。
         而在双向管道中,数据能在管道的两端之间进行交换。

    匿名管道总是单向的,而一个已命名的管道则是单向或双向中的一种。有名管道通常用于一个服务器连接多个客户端的网络环境中。

     这里我们将详细地讨论匿名管道。匿名管道的主要用途是作为父进程和子进程之间或者是子进程之间的通讯通路。
     当你涉及到控制台程序时,命名管道是很有用的。控制台应用程序是Win32程序的一种,用于控制它们的输入和输出。一个控制台就像一个DOS窗口。然而,一个控制台应用程序又的的确确是32位的应用程序。它和其它的图形用户界面程序一样,可以使用任何GUI函数。它仅是偶然的使用一下控制台罢了。
     控制台应用程序有三个句柄,它能用作它的输入和输出。它们是被调用的标准句柄。
           这三个句柄是:标准输入,标准输出和标准错误句柄。
           标准输入句柄被用于从一个控制台中读或者是获取信息;
           标准输出句柄被用于输出或打印控制台信息。
           标准错误句柄用于报告错误状态,因为控制台的输出可能被重定向。
     控制台应用程序能通过调用GetStdHandle函数来获得这三个标准句柄,只需要指定你想要的句柄就能够得到它。一个图像用户界面程序没有控制台。如果你调用GetStHandle函数,它将返回错误代码。如果你真的想使用控制台,你可以调用AllocConsole来分配一个新个控制台。然后,当你完成控制台的工作时,不要忘记调用FreeConsole函数释放它。
 
     匿名管道被频繁的用于重定向 子控制台应用程序的输入和输出。父进程可能是一个控制台或者是一个GUI应用程序,但是子进程必须是一个控制台应用程序。为了这个工作,像你所知道的一样,一个控制台应用程序为它的输入和输出使用标准句柄。如果你想重定向一个控制台应用程序的输入和(或)输出,我们必须用一个管道一端的句柄来替换这个句柄,而控制台应用程序并不知道它使用的句柄是一个管道的一端。它仍将把它当作一个标准句柄使用。在面向对象的行话中,这属于多态性的一种。这是一种很有效的方法,因为我们并不需要用任何途径来修改子进程。

     其它你还应该知道的事,就是一个控制台程序从哪儿获取这些标准句柄。当一个控制台应用程序被创建时,父进程有两个选择:为子进程创建一个新的控制台 或者让子进程继承它已有的控制台。如果选用的是第二种方法,父进程必须是一个控制台应用程序,如果它是一个图形用户界面程序,那么它必须首先调用AllocConsole函数来分配一个控制台。
 
让我们开始工作吧!为了创建一个匿名管道,你需要调用CreatePipe函数,CreatePipe句法如下:
        CreatePipe proto pReadHandle:DWORD, \
                         pWriteHandle:DWORD,\
                         pPipeAttributes:DWORD,\
                         nBufferSize:DWORD

       pReadHandle:是一指向DWORD类型变量的指针,这个变量接收管道读的那端的句柄。
       pWriteHandle: 是一指向DWORD类型变量的指针,这个变量接收管道写的那端的句柄。
       pPipeAttributes: 指向一个SECURITY_ATTRIBUTES结构体的指针,这个结构用于确定读写句 
                        柄是否能被子进程继承。
       nBufferSize 建议为用户保留的缓冲区大小。这仅仅是一个建议的大小。你能使用NULL来告诉
                   函数使用默认的大小。
如果这个调用是成功的,返回值非零。如果失败,返回值为零(0)。
     在CreatePipe调用成功之后,你将得到两个句柄,一个是管道读端的句柄,另一个是写端的句柄。现在我将重点放在重定向子控制台程序的标准输出到你拥有的进程所需要的步骤上。
     注意:我使用的方法和在Borland的win32 api 手册中的方法有所不同。在win32 api手册中的这种方法如下:父进程是一个控制台程序从而子进程能从它那继承这些标准句柄。但是大所数时候,我们将需要从一个控制台程序中重定向输出到一个图形用户界面程序。
      用CreatePipe函数创建一个匿名管道,不要忘记设置SECUTITY_ATTRIBUTES结构的bInheritable成员的值为TRUE,如此,这些句柄才能被继承。
      现在我们必须准备一下将传递给CreateProcess函数的参数了,因为我们将使用它加载一个子控制台应用程序。一个重要的结构是STARTUPINF结构体。这个结构确定当子进程第一次出现时主窗口的外部特征。这个结构对我们的目的来说是必不可少的。你能用它隐藏主窗口并把管道句柄传递给子控制台进程。下面这些成员是你必须填写的:
          cb:STARTUPINFO结构体的大小。
          dwFlags:二进制位标志字。它确定了结构的那一个成员是有效的 同时还支配着主窗口的显示或
                  隐藏状态。为了我们的目标你应该用STARTF_USESHOWWINDOW 和
                  和STARTF_USESTDHANDLES的组合值
          hStdOutput 和hSedError: 这是你想在子进程中使用的标准输出和标准错误的句柄。为了达
                  的目标,我们传递管道写端的句柄作为子进程的标准输出和标准错误的句柄。所以当
                  子进程输出一些东西到标准输出和错误时,它实际上是通过管道把这些信息发送给
                  父进程。
          WshowWindow 管理主窗口的显示和隐藏状态。为达到我们的目标,我们并不想子进程的控制窗口显示,所以我们 把SW_HIDE放进这个成员中。

调用CreateProcess来加载子应用程序。在CreateProcess调用成功后,子进程处于睡眠状态,它被装进内存但是它不立即运行。
关闭些管道句柄。这一步是必须的。因为父进程并不需要写管道句柄,如果这里有超过一个写端,这个管道将不工作,为了从一个管道中读进数据我们必须关闭它。然而,如果在调用CreateProcess之前,不关闭写句柄,你们的管道将是破裂的。你应该在CreateProcess刚刚返回并且在你从一个管道的一个读端读数据之前,关闭管道写端。
         现在你可以用ReadFile函数从管道的读端读取数据。通过使用ReadFile函数,你能把子进程踢进运行模式。它将开始执行并且当它写一东西到标准输出句柄(实际上是管道的写端句柄)时,这些数据通过管道被送到读端。你可以把ReadFile函数看作是从管道的读端上吸食数据。你应该重复的调用ReadFile函数直到它的返回值为0. 返回0意味着这里没有更多数据能被读取了。你能对从管道读来的数据做任何处理。在我们的例子中,我把它们放在编辑控件中。
关闭读管道句柄。

.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\gdi32.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
.const
IDR_MAINMENU equ 101         ; the ID of the main menu
IDM_ASSEMBLE equ 40001
.data
ClassName            db "PipeWinClass",0
AppName              db "One-way Pipe Example",0 EditClass db "EDIT",0
CreatePipeError     db "Error during pipe creation",0
CreateProcessError     db "Error during process creation",0
CommandLine     db "ml /c /coff /Cp test.asm",0
.data?
hInstance HINSTANCE ?
hwndEdit dd ?
.code
start:
    invoke GetModuleHandle, NULL
    mov hInstance,eax
    invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
    invoke ExitProcess,eax
WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORD
    LOCAL wc:WNDCLASSEX
    LOCAL msg:MSG
    LOCAL hwnd:HWND
    mov wc.cbSize,SIZEOF WNDCLASSEX
    mov wc.style, CS_HREDRAW or CS_VREDRAW mov wc.lpfnWndProc, OFFSET WndProc
    mov wc.cbClsExtra,NULL
    mov wc.cbWndExtra,NULL
    push hInst
    pop wc.hInstance
    mov wc.hbrBackground,COLOR_APPWORKSPACE
    mov wc.lpszMenuName,IDR_MAINMENU
    mov wc.lpszClassName,OFFSET ClassName
    invoke LoadIcon,NULL,IDI_APPLICATION
    mov wc.hIcon,eax
    mov wc.hIconSm,eax
    invoke LoadCursor,NULL,IDC_ARROW
    mov wc.hCursor,eax
    invoke RegisterClassEx, addr wc
    invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName,\ WS_OVERLAPPEDWINDOW+WS_VISIBLE,CW_USEDEFAULT,\ CW_USEDEFAULT,400,200,NULL,NULL,\ hInst,NULL
    mov hwnd,eax
    .while TRUE
        invoke GetMessage, ADDR msg,NULL,0,0
        .BREAK .IF (!eax)
        invoke TranslateMessage, ADDR msg
        invoke DispatchMessage, ADDR msg
    .endw
    mov eax,msg.wParam
    ret
WinMain endp
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
    LOCAL rect:RECT
    LOCAL hRead:DWORD
    LOCAL hWrite:DWORD
    LOCAL startupinfo:STARTUPINFO
    LOCAL pinfo:PROCESS_INFORMATION
    LOCAL buffer[1024]:byte
    LOCAL bytesRead:DWORD
    LOCAL hdc:DWORD
    LOCAL sat:SECURITY_ATTRIBUTES
    .if uMsg==WM_CREATE
        invoke CreateWindowEx,NULL,addr EditClass, NULL, WS_CHILD+ WS_VISIBLE+ ES_MULTILINE+ ES_AUTOHSCROLL+ ES_AUTOVSCROLL, 0, 0, 0, 0, hWnd, NULL, hInstance, NULL
        mov hwndEdit,eax
    .elseif uMsg==WM_CTLCOLOREDIT
        invoke SetTextColor,wParam,Yellow
        invoke SetBkColor,wParam,Black
       invoke GetStockObject,BLACK_BRUSH
        ret
    .elseif uMsg==WM_SIZE
        mov edx,lParam
        mov ecx,edx
        shr ecx,16
        and edx,0ffffh
        invoke MoveWindow,hwndEdit,0,0,edx,ecx,TRUE
    .elseif uMsg==WM_COMMAND
       .if lParam==0
            mov eax,wParam
            .if ax==IDM_ASSEMBLE
                mov sat.niLength,sizeof SECURITY_ATTRIBUTES
                mov sat.lpSecurityDescriptor,NULL
                mov sat.bInheritHandle,TRUE
                invoke CreatePipe,addr hRead,addr hWrite,addr sat,NULL
                .if eax==NULL
                    invoke MessageBox, hWnd, addr CreatePipeError, addr AppName, MB_ICONERROR+ MB_OK
                .else
                    mov startupinfo.cb,sizeof STARTUPINFO
                    invoke GetStartupInfo,addr startupinfo
                    mov eax, hWrite
                    mov startupinfo.hStdOutput,eax
                    mov startupinfo.hStdError,eax
                    mov startupinfo.dwFlags, STARTF_USESHOWWINDOW+ STARTF_USESTDHANDLES
                    mov startupinfo.wShowWindow,SW_HIDE
                    invoke CreateProcess, NULL, addr CommandLine, NULL, NULL, TRUE, NULL, NULL, NULL, addr startupinfo, addr pinfo
                    .if eax==NULL
                        invoke MessageBox,hWnd,addr CreateProcessError,addr         AppName,MB_ICONERROR+MB_OK
                    .else
                        invoke CloseHandle,hWrite
                        .while TRUE
                            invoke RtlZeroMemory,addr buffer,1024
                            invoke ReadFile,hRead,addr buffer,1023,addr bytesRead,NULL
                            .if eax==NULL
                                .break
                            .endif
                            invoke SendMessage,hwndEdit,EM_SETSEL,-1,0
                            invoke SendMessage,hwndEdit,EM_REPLACESEL,FALSE,addr buffer
                        .endw
                    .endif
                    invoke CloseHandle,hRead
                .endif
            .endif
        .endif
    .elseif uMsg==WM_DESTROY
        invoke PostQuitMessage,NULL
    .else
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret
    .endif
    xor eax,eax
    ret
WndProc endp
end start
分析:
这个例子将调用Ml.EXE来汇编一个名字为test.asm的文件并重定向ml.exe的输出到编辑框的客户区中。
当程序被装载时,它照常注册一个窗口类,并创建一个主窗口。它要做的第一件事就是在主窗口创建的时候创建一个编辑控件。这个编辑控件被用于显示ml.exe的输出。

现在来到我们感兴趣的部分,我们将改变编辑控件的背景色和文本颜色。当编辑控件将要绘制它的客户区时,它发送WM_CTLCOLOREDIT消息给它的父亲。
wParam包含的是设备描述表的句柄,设备描述表将被编辑控件用来写它的客户区。我们能利用这个时机来修改HDC(设备描述表也称设备环境)的特征属性值。
.elseif uMsg==WM_CTLCOLOREDIT
        invoke SetTextColor,wParam,Yellow
        invoke SetTextColor,wParam,Black
        invoke GetStockObject,BLACK_BRUSH
        ret
SetTextColor 改变文本颜色为黄色。SetTextColor改变文本的背景色为黑色。最后,我们获得了黑色画刷的句柄并将它返回给Windows。在处理WM_CTLCOLOREDIT消息时,你必须返回一个画刷的句柄。Windows将用这个画刷来绘制编辑控件的背景色。在我们的例子中,我想让背景为黑色所以我返回一个黑色的画刷给Windows。现在,当用户选择Assemble菜单项时,它将创建一个匿名管道。
            .if ax==IDM_ASSEMBLE
                mov sat.niLength,sizeof SECURITY_ATTRIBUTES
                mov sat.lpSecurityDescriptor,NULL
                mov sat.bInheritHandle,TRUE
在调用CreatePipe之前,我们必须首先填充SECURITY_ATTRIBUTES结构。如果我们不关心安全性描述我们能设置ipSecurityDescriptor成员的值为NULL。还有,我们的bInheritHandle成员必须为TRUE以至于让我们的管道句柄能被子进程继承。
             invoke CreatePipe,addr hRead,addr hWrite,addr sat,NULL
在上面工作完成之后,我们调用CreatePipe函数,如果这个函数调用成功,hRead和hWrite变量将被分别填充到管道的读端和写端的句柄中。
                   mov startupinfo.cb,sizeof STARTUPINFO
                    invoke GetStartupInfo,addr startupinfo
                    mov eax, hWrite
                    mov startupinfo.hStdOutput,eax
                    mov startupinfo.hStdError,eax
                    mov startupinfo.dwFlags, STARTF_USESHOWWINDOW+ STARTF_USESTDHANDLES
                    mov startupinfo.wShowWindow,SW_HIDE
下面我们将填写STARTUPINFO结构。我们调用GetStartupInfo函数来用父进程的默认值填充STARTUPINFO结构。如果你打算让你的代码在Win9x和NT中都能工作,那么你必须用这个调用来填充这个STARTUPINFO结构。在GetStartupInfo调用返回后,你能修改那些重要的成员。我们复制管道的写端句柄给hStdOutput和hStdError因为我们想让子进程使用它来代替默认的标准输出/错误句柄。我们还需要隐藏子进程的控制台窗口,所以我们设置wShowWIndow成员的值为SW_HIDE。在最后,我们必须指出hStdOutput ,hStdError和wShowWindow成员是有效的并且必须把被指定的标志STARTF_USESHOWWINDOW和STARTF_USESTDHANDLES的组合值填进dwFlags成员中。
                   invoke CreateProcess, NULL, addr CommandLine, NULL, NULL, TRUE, NULL, NULL, NULL, addr startupinfo, addr pinfo
现在,我们调用CreateProcess函数创建了子进程。注意,为了能让管道句柄能工作,我们必须把bInheritHandles参数值设置为TRUE。
                       invoke CloseHandle,hWrite
在成功创建了子进程之后,我们必须关闭管道的写端。记住,我们通过STARTUPINFO结构来传递写端句柄给子进程。如果我们不关闭管道一端的写句柄,这个管道将有两个写端。这样,管道就不能工作了。我们必须在CreateProcess调用后但是要在从管道的读端读数据之前关闭写端句柄。
                        .while TRUE
                            invoke RtlZeroMemory,addr buffer,1024
                            invoke ReadFile,hRead,addr buffer,1023,addr bytesRead,NULL
                            .if eax==NULL
                                .break
                            .endif
                            invoke SendMessage,hwndEdit,EM_SETSEL,-1,0
                            invoke SendMessage,hwndEdit,EM_REPLACESEL,FALSE,addr buffer
                        .endw

现在,我们准备从子进程的标准输出中读数据。我们将驻留在一个死循环中,直到从管道中再也没有数据读出为止才离开。我们调用RtlZeroMemory函数用零填充缓冲区然后传递管道的读端句柄给ReadFile函数的文件句柄并调用它。注意,我们能读的最大字节数为1023字节,因为我们需要把数据转换成能传递给编辑控件的ASCIIZ字符串。
当ReadFile把数据置于缓冲区中并返回时,我们把数据填进编辑控件中。然而,这里有一个小问题。如果我们用SetWindowText函数来把数据放入编辑控件中,新的数据将覆盖已经存在的数据!我们希望数据能给补充在存在数据的后面。
为完成这个目标,首先,我们通过在发送EM_SETSEL消息的时候设置wParam参数的值为-1来把插入符移动到文本的末尾。然后,我们发送一个EM_REPLACESEL消息来添加加数据。

                   invoke CloseHandle,hRead
 当ReadFile函数返回值为NULL时,我们退出循环并且关闭读端句柄。
阅读(1009) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~