ITEEDU

第1章 Iczelion的Win32汇编教程

第2章 Iczelion的ODBC教程

第3章 Iczelion的VxD教程

第4章 Iczelion的PE教程

第5章 罗云彬的Win32汇编教程

第6章 Win32ASM经验点滴

第7章 X86汇编语言编程

第8章 加密解密

第9章 病毒的分析和防治

Win32汇编教程十二

管道操作

概述

Windows 引入了多进程和多线程机制。同时也提供了多个进程之间的通信手段,包括剪贴板、DDE、OLE、管道等,和其他通信手段相比,管道有它自己的限制和特点,管道实际上是一段共享内存区,进程把共享消息放在那里。并通过一些 API 提供信息交换。
管道是两个头的东西,每个头各连接一个进程或者同一个进程的不同代码,按照管道的类别分有两种管道,匿名的和命名的;按照管道的传输方向分也可以分成两种,单向的双向的。根据管道的特点,命名管道通常用在网络环境下不同计算机上运行的进程之间的通信(当然也可以用在同一台机的不同进程中)它可以是单向或双向的;而匿名管道只能用在同一台计算机中,它只能是单向的。匿名管道其实是通过用给了一个指定名字的有名管道来实现的。
使用管道的好处在于:读写它使用的是对文件操作的 api,结果操作管道就和操作文件一样。即使你在不同的计算机之间用命名管道来通信,你也不必了解和自己去实现网络间通信的具体细节。

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

命名管道是由服务器端的进程建立的,管道的命名必须遵循特定的命名方法,就是 "\\.\pipe\管道名",当作为客户端的进程要使用时,使用"\\计算机名\\pipe\管道名" 来打开使用,具体步骤如下:

  1. 服务端通过函数 CreateNamedPipe 创建一个命名管道的实例并返回用于今后操作的句柄,或为已存在的管道创建新的实例。
  2. 服务端侦听来自客户端的连接请求,该功能通过 ConnectNamedPipe 函数实现。
  3. 客户端通过函数 WaitNamedPipe 来等待管道的出现,如果在超时值变为零以前,有一个管道可以使用,则 WaitNamedPipe 将返回 True,并通过调用 CreateFile 或 CallNamedPipe 来呼叫对服务端的连接。
  4. 此时服务端将接受客户端的连接请求,成功建立连接,服务端 ConnectNamedPipe 返回 True
  5. 建立连接之后,客户端与服务器端即可通过 ReadFile 和 WriteFile,利用得到的管道文件句柄,彼此间进行信息交换。
  6. 当客户端与服务端的通信结束,客户端调用 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 (父进程)

使用匿名管道的步骤如下:

  1. 使用 CreatePipe 建立两个管道,得到管道句柄,一个用来输入,一个用来输出
  2. 准备执行控制台子进程,首先使用 GetStartupInfo 得到 StartupInfo
  3. 使用第一个管道句柄代替 StartupInfo 中的 hStdInput,第二个代替 hStdOutput、hStdError,即标准输入、输出、错误句柄
  4. 使用 CreateProcess 执行子进程,这样建立的子进程输入和输出就被定向到管道中
  5. 父进程通过 ReadFile 读第二个管道来获得子进程的输出,通过 WriteFile 写第一个管道来将输入写到子进程
  6. 父进程可以通过 PeekNamedPipe 来查询子进程有没有输出
  7. 子进程结束后,要通过 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 的结构必须填写,定义如下:

              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 之间的配合而已。