Windows中的管道解析

来源:互联网 发布:java ee看源代码 编辑:程序博客网 时间:2024/06/05 19:32

具体来讲,Pipe是一种POSIX规范,在不同系统上都有实现。msvcrt提供了_pipe这个函数。但是,它的实现是基于CreatePipe,这是无庸置疑的。这种非标准(带下划线)的C函数,在CRT中的很多。比如_open返回的文件指针FIFL*,很多时候我们都没有注意到,它几乎等同于CreateFile传回来的HANDLE。在Windows核心编程中,我们知道,每个进程有一个句柄表。创建子进程时,可以指定子进程是否继承父进程句柄表。如果子进程继承了父进程,且句柄有有继承属性,就可以很方便地共享句柄,如果这人句柄是管道,则可以用于进程间通讯。

       

       言归正传,现在正式介绍管道。管道其实比较容易理解,它就像一个管子一样,但是要注意它是有方向性。即,一个管道只允许在同一时间,以某一方向操作。换而言之,同一时间,其中一个进程在写管道,而另只能读管道。先看看Win32中的管道创建方法。

BOOL CreatePipe(

 PHANDLE hReadPipe,

 PHANDLE hWritePipe,

 LPSECURITY_ATTRIBUTES lpPipeAttributes,

 DWORD nSize

);

       34个参数用于属性,只使用一次。最为重要的是hReadPipehWritePipe,它分别代表管道的读端与写端。这里有几点要说明:

       1.确切地说,HANDLE只是一个有特殊意义的整数。比如,我们在CreatePipe后又调用CreateProcess创建子进程,并都设置了继承属性,那么这个整数在两个线程中都有效。而且,我们倾向于用命令行参数的方式传给子线程。

       2.假设父进程创建了一个管道,读端和写端分别是fhR, fhW,它把它两个值传给子进程(假设就是用命令行的方式),分别为shW, shR,注意到,这里把RW的标识反着写了,这是通俗写法。例如,我在子进程使用shW来写数据,它在父进程中刚好对应fhR;反之父进程用fdW写,子进程用shR读。

       来看一例子

[cpp] view plaincopy
  1. #include <stdio.h>   
  2.   
  3. #include <windows.h>   
  4.   
  5.  #define BUFSIZE 4096  HANDLE hfInRd, hfInWr, hfoutWrDup, hfOutRd, hfOutWr, hChildStdoutRdDup,  hStdout;     
  6.   
  7. DWORD main(int argc, char *argv[])  
  8.   
  9.  {   
  10.   
  11.    SECURITY_ATTRIBUTES saAttr;  
  12.   
  13.     BOOL fSuccess;  // 设置一个有继承属性的安全属性,用于创建管道.  
  14.   
  15.     saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);  
  16.   
  17.     saAttr.bInheritHandle = TRUE;  
  18.   
  19.     saAttr.lpSecurityDescriptor = NULL; // 创建一个有继承属性的管道  
  20.   
  21.    CreatePipe(&hfOutRd, &hSInWr, &saAttr, 0); //给父进程读的// 将管道的读句柄拷贝一份到hfRdDup  
  22.   
  23.     DuplicateHandle(GetCurrentProcess(), hfOutRd,   
  24.   
  25.         GetCurrentProcess(), &hfOutRdDup , 0,   
  26.   
  27.         FALSE,      // 非继承  
  28.   
  29.         DUPLICATE_SAME_ACCESS);   //关闭读管道,注意,虽然它关闭了,但是还有一个可读管道保存在hfRdDup中  
  30.   
  31.     CloseHandle(hfRd);  
  32.   
  33.     CreatePipe(&hSOutRd, &hfInWr, &saAttr, 0)); //给父进程写的  
  34.   
  35.     DuplicateHandle(GetCurrentProcess(), hfInWr,  
  36.   
  37.        GetCurrentProcess(), &hfInWrDup, 0,  
  38.   
  39.        FALSE,           // 非继承  
  40.   
  41.       DUPLICATE_SAME_ACCESS);  
  42.   
  43.      CloseHandle(hfInWr);  // 创建进程  
  44.   
  45.    PROCESS_INFORMATION piProcInfo;  
  46.   
  47.     STARTUPINFO siStartInfo;  
  48.   
  49.    BOOL bFuncRetn = FALSE;  
  50.   
  51.     ZeroMemory( &piProcInfo, sizeof(PROCESS_INFORMATION) );  
  52.   
  53.     ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) );   
  54.   
  55.     siStartInfo.cb = sizeof(STARTUPINFO);  
  56.   
  57.     siStartInfo.hStdError = hSInWr;  
  58.   
  59.    siStartInfo.hStdOutput = hSInWr;  
  60.   
  61.    siStartInfo.hStdInput = hSOutRd;  
  62.   
  63.    siStartInfo.dwFlags |= STARTF_USESTDHANDLES;  //子进程的hStdOutput被赋予了hSInWr, 而这个写端对应的读端是hfOutRd,所以父进程可以   //从hfOutRdDup上读到子进程的标准输出;   //而子程序的hStdInput被赋予了hfInRd, 这个端对应的写端是hfInWr   //这表示,父进程可以通过hfInWrUp把数据写到子进程的标准输入上   //这里主要以父进程为目标来说明的,因为子进程通常是别人写的程序。所以创建了两个管道,分别用于输入到输出(相对于子进程),否则,完全可以用一个管道,由两个进程协商IO的顺序  
  64.   
  65.    CreateProcess(NULL,       "child",       // command line  
  66.   
  67.        NULL,          // process security attributes  
  68.   
  69.        NULL,          // primary thread security attributes  
  70.   
  71.        TRUE,          // handles are inherited  
  72.   
  73.        0,             // creation flags  
  74.   
  75.        NULL,          // use parent's environment  
  76.   
  77.        NULL,          // use parent's current directory  
  78.   
  79.        &siStartInfo,  // STARTUPINFO pointer  
  80.   
  81.        &piProcInfo);  // receives PROCESS_INFORMATION  
  82.   
  83.       
  84.   
  85.     CloseHandle(piProcInfo.hProcess);   
  86.   
  87.     CloseHandle(piProcInfo.hThread);  
  88.   
  89.     WriteToPipe(hfInWrDup); // 。。。  
  90.   
  91.     ReadFromPipe(hfOutRdDup); //。。。  
  92.   
  93.     return 0;  
  94.   
  95.  }   

CreateProcess有一个参数,可以指定创建的子进程所使用的管道,这一机制非常方便。例如,在没有STDINSTDOUTGUI程序中,就可以创建两个管道,然后调用控制台程序,可以很容易捕获到输出。

关于管道,其实CRT中也有提供。事实上,管道是POSIX标准之一,很多系统上都提供其实现。下面看看两个重要的管道函数。

       int_pipe(int*phandles,unsignedintpsize,inttextmode);

       FILE*_popen(constchar*command,constchar*mode);

 

       第一个函数非常类似于CreatePipe函数,phandles是一个int[2]数组;_popen函数创建一个进程,mode如果指定了“r",即读管道,那么返回的FILE是一个用于读的管道,你可以用fgetsStream I/O函数读,而且父进程的stdin自动转发到子进程的stdin;如果mode指定的"w",那么是一个写管道,用fputs可以写到子进程的stdin,而子进程的stdout是在创建时就连接到父进程的stdou上了。

       phandles[0]phandles[1]CreatePipe创建的管道一样,注意到,它是一个整数,或者专业一点:文件描述符,它其实对应的是一个句柄(经过一系列转换)。文件描述符可以用_read, _write等操作,通常称之为Low-Level I/O。例如,打开文件有两种方式:

       int_open(constchar*filename,intoflag [,intpmode);

       FILE*fopen(constchar*filename,constchar*mode);

       两个函数都是打开文件,区别在于后者有缓冲的概念。例如,stdinstdout就是属于流对象。


       
Stream I/OLow-Level I/O的子类(我是这样理解的),_fileno函数可以得到流对应的文件描述符。我以前很少使用fopen这种C语言流,因为对于流,我更倾向于用iostream。不过,C++没有提供Low-Level I/O,所以很多时候很有必要使用它。这里有两个函数非常有用,_dup_dup2。它类似于DuplicateHandle函数,可以用于子进程与父进程通信。

       正如前面所述,子进程可以继承父进程的句柄表。当用dup复制一个文件描述符后,就可以用于通信了(比如管道或共享文件)。

       下面这个简单程序,展示的是如何通过管道来读子进程的输出。

[cpp] view plaincopy
  1. #include <stdio.h>  
  2.   
  3. #include <string.h>  
  4.   
  5.    
  6.   
  7. int main()  
  8.   
  9. {  
  10.   
  11.    int   i;  
  12.   
  13.    for(i=0;i<100;++i)  
  14.   
  15.    {  
  16.   
  17.         printf("\nThis is speaker beep number %d... \n\7", i+1);  
  18.   
  19.     }  
  20.   
  21.    return 0;  
  22.   
  23. }  
  24.   
  25.    
  26.   
  27.    
  28.   
  29. // BeepFilter.Cpp  
  30.   
  31. /* Compile options needed: none 
  32.  
  33.    Execute as:BeepFilter.exe <path>Beeper.exe 
  34.  
  35. */  
  36.   
  37. #include <windows.h>  
  38.   
  39. #include <process.h>  
  40.   
  41. #include <memory.h>  
  42.   
  43. #include <string.h>  
  44.   
  45. #include <stdio.h>  
  46.   
  47. #include <fcntl.h>  
  48.   
  49. #include <io.h>  
  50.   
  51.    
  52.   
  53. #define  OUT_BUFF_SIZE 512  
  54.   
  55. #define  READ_HANDLE 0  
  56.   
  57. #define   WRITE_HANDLE1  
  58.   
  59. #define   BEEP_CHAR7  
  60.   
  61.    
  62.   
  63. char szBuffer[OUT_BUFF_SIZE];  
  64.   
  65.    
  66.   
  67. int Filter(char* szBuff, ULONG nSize, int nChar)  
  68.   
  69. {  
  70.   
  71.    char* szPos =szBuff + nSize -1;  
  72.   
  73.    char* szEnd =szPos;  
  74.   
  75.    int nRet =nSize;  
  76.   
  77.    
  78.   
  79.    while (szPos> szBuff)  
  80.   
  81.    {  
  82.   
  83.       if (*szPos ==nChar)  
  84.   
  85.          {  
  86.   
  87.            memmove(szPos, szPos+1, szEnd - szPos);  
  88.   
  89.             --nRet;  
  90.   
  91.          }  
  92.   
  93.       --szPos;  
  94.   
  95.    }  
  96.   
  97.    return nRet;  
  98.   
  99. }  
  100.   
  101.    
  102.   
  103. int main(int argc, char** argv)  
  104.   
  105. {  
  106.   
  107.    int nExitCode =STILL_ACTIVE;  
  108.   
  109.    if (argc >=2)  
  110.   
  111.    {  
  112.   
  113.       HANDLEhProcess;  
  114.   
  115.       int hStdOut;  
  116.   
  117.       inthStdOutPipe[2];  
  118.   
  119.    
  120.   
  121.       // Create thepipe  
  122.   
  123.      if(_pipe(hStdOutPipe, 512, O_BINARY | O_NOINHERIT) == -1)  
  124.   
  125.         return   1;  
  126.   
  127.    
  128.   
  129.       // Duplicatestdout handle (next line will close original)  
  130.   
  131.       hStdOut =_dup(_fileno(stdout));  
  132.   
  133.    
  134.   
  135.       // Duplicate write end of pipe to stdouthandle  
  136.   
  137.      if(_dup2(hStdOutPipe[WRITE_HANDLE], _fileno(stdout)) != 0)  
  138.   
  139.         return   2;  
  140.   
  141.    
  142.   
  143.       // Closeoriginal write end of pipe  
  144.   
  145.      close(hStdOutPipe[WRITE_HANDLE]);  
  146.   
  147.    
  148.   
  149.       // Spawnprocess  
  150.   
  151.       hProcess =(HANDLE)spawnvp(P_NOWAIT, argv[1],   
  152.   
  153.        (const char*const*)&argv[1]);  
  154.   
  155.    
  156.   
  157.       // Duplicatecopy of original stdout back into stdout  
  158.   
  159.      if(_dup2(hStdOut, _fileno(stdout)) != 0)  
  160.   
  161.         return   3;  
  162.   
  163.    
  164.   
  165.       // Closeduplicate copy of original stdout  
  166.   
  167.      close(hStdOut);  
  168.   
  169.    
  170.   
  171.       if(hProcess)  
  172.   
  173.       {  
  174.   
  175.          intnOutRead;  
  176.   
  177.          while   (nExitCode == STILL_ACTIVE)  
  178.   
  179.          {  
  180.   
  181.            nOutRead = read(hStdOutPipe[READ_HANDLE],   
  182.   
  183.             szBuffer, OUT_BUFF_SIZE);  
  184.   
  185.            if(nOutRead)  
  186.   
  187.             {  
  188.   
  189.               nOutRead = Filter(szBuffer, nOutRead, BEEP_CHAR);  
  190.   
  191.               fwrite(szBuffer, 1, nOutRead, stdout);  
  192.   
  193.             }  
  194.   
  195.    
  196.   
  197.            if(!GetExitCodeProcess(hProcess,(unsigned long*)&nExitCode))  
  198.   
  199.               return 4;  
  200.   
  201.          }  
  202.   
  203.       }  
  204.   
  205.    }  
  206.   
  207.    
  208.   
  209.   printf("\nPress \'ENTER\' key to continue... ");  
  210.   
  211.    getchar();  
  212.   
  213.    returnnExitCode;  
  214.   
  215. }  

下面这个程序是popen的示例,它展示的是子进程与父进程共享句柄表的的方式。

[cpp] view plaincopy
  1. #include <stdio.h>  
  2.   
  3. #include <stdlib.h>  
  4.   
  5.    
  6.   
  7. void main( void )  
  8.   
  9. {  
  10.   
  11.    
  12.   
  13.    char   psBuffer[128];  
  14.   
  15.    FILE   *chkdsk;  
  16.   
  17.    
  18.   
  19.         /* Run DIRso that it writes its output to a pipe. Open this 
  20.  
  21.     * pipe withread text attribute so that we can read it  
  22.  
  23.          * like atext file.  
  24.  
  25.     */  
  26.   
  27.    if( (chkdsk =_popen( "dir *.c /on /p""rt" )) == NULL )  
  28.   
  29.       exit( 1 );  
  30.   
  31.    
  32.   
  33.    /* Read pipeuntil end of file. End of file indicates that  
  34.  
  35.     * CHKDSK closedits standard out (probably meaning it  
  36.  
  37.          *terminated). 
  38.  
  39.     */  
  40.   
  41.    while( !feof(chkdsk ) )  
  42.   
  43.    {  
  44.   
  45.       if( fgets(psBuffer, 128, chkdsk ) != NULL )  
  46.   
  47.          printf(psBuffer );  
  48.   
  49.    }  
  50.   
  51.    
  52.   
  53.    /* Close pipeand print return value of CHKDSK. */  
  54.   
  55.    printf("\nProcess returned %d\n", _pclose( chkdsk ) );  
  56.   
  57. }  


关于管道,还有一个非常好的资料,即popen的实现源码,从那里可以看到背后的一切。

0 0