• (转)Windows管道(Pipe)重定向stdout,stderr,stdin


    参考:

    http://qiusuoge.com/11496.html

    http://www.cnblogs.com/BoyXiao/archive/2011/01/01/1923828.html

    stdin是标准输入,stdout是标准输出,stderr是标准错误输出。大多数的命令行程序从stdin输入,输出到stdout或 stderr,有时我们需要重定向stdout,stderr,stdin。比如:将输出写入文件,又或者我们要将命令行程序输出结果显示到 Windows对话框中。

    相关阅读

    ----命名管道(Named Pipes)通信学习

        在Windows编程中,重定向需要用到管道(Pipe)的概念。管道是一种用于在进程间共享数据的机制。一个管道类似于一个管子的两端,一端是写入的,一端是读出的。由一个进程从写入端写入、另一个进程从读出端读出,从而实现通信,就向一个“管道”一样

    重定向的原理是:

    首先声明两个概念:主程序(重定向的操纵者)、子进程(被重定向的子进程)

    如果要重定位stdout的话,先生成一个管道, 管道的写入端交给子进程去写,主程序从管道的读出端读数据,然后可以把数据写成文件、显示等等。重定向stderr和stdout是相同的。

    同理,要重定向stdin的话,生成一个管道, 管道的写入端由主程序写,子进程从管道的读出端读数据。

    其中需要用到几个Windows API :  CreatePipe, DuplicateHandle, CreateProcess, ReadFile, WriteFile 等,函数详解可参见MSDN.

    一、编程实现原理 ( C语言)

    view plaincopy to clipboard
    #include <windows.h> 
     
      //定义句柄: 构成stdin管道的两端句柄 
      HANDLE  hStdInRead;         //子进程用的stdin的读入端 
      HANDLE  hStdInWrite;        //主程序用的stdin的读入端 
     
      //定义句柄: 构成stdout管道的两端句柄 
      HANDLE  hStdOutRead;     ///主程序用的stdout的读入端 
      HANDLE  hStdOutWrite;    ///子进程用的stdout的写入端 
     
      //定义句柄: 构成stderr管道的句柄,由于stderr一般等于stdout,我们没有定义hStdErrRead,直接用hStdOutRead即可 
      HANDLE  hStdErrWrite;       ///子进程用的stderr的写入端 
     
      //定义一个用于产生子进程的STARTUPINFO结构体 (定义见CreateProcess,函数说明) 
      STARTUPINFO siStartInfo; 
      //定义一个用于产生子进程的PROCESS_INFORMATION结构体 (定义见CreateProcess,函数说明) 
      PROCESS_INFORMATION piProcInfo; 
     
     
      //产生一个用于stdin的管道,得到两个HANDLE:  hStdInRead用于子进程读出数据,hStdInWrite用于主程序写入数据 
      //其中saAttr是一个STARTUPINFO结构体,定义见CreatePipe函数说明 
      if   (!CreatePipe(&hStdInRead, &hStdInWrite,&saAttr, 0)) 
              return; 
     
      //产生一个用于stdout的管道,得到两个HANDLE:  hStdInRead用于主程序读出数据,hStdInWrite用于子程序写入数据 
      if  (!CreatePipe(&hStdOutRead, &hStdOutWrite,&saAttr, 0)) 
            return; 
      //由于stderr一般就是stdout, 直接复制句柄hStdOutWrite,得到 hStdErrWrite 
       if (!DuplicateHandle(GetCurrentProcess(), hStdOutWrite, GetCurrentProcess(), &hStdErrWrite, 0, TRUE, DUPLICATE_SAME_ACCESS)) 
          return; 
     
      //对STARTUPINFO结构体赋值,对stdin,stdout,stderr的Handle设置为刚才得到的管道HANDLE 
      ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) ); 
      siStartInfo.cb = sizeof(STARTUPINFO); 
      siStartInfo.dwFlags  |= STARTF_USESHOWWINDOW; 
      siStartInfo.dwFlags  |= STARTF_USESTDHANDLES; 
      siStartInfo.hStdOutput = hStdOutWrite;     //意思是:子进程的stdout输出到hStdOutWrite 
      siStartInfo.hStdError  =  hStdErrWrite;        //意思是:子进程的stderr输出到hStdErrWrite 
      siStartInfo.hStdInput  = hStdInRead; 
     
      // 产生子进程,具体参数说明见CreateProcess函数 
      bSuccess = CreateProcess(NULL, 
          CommandLine,    // 子进程的命令行 
          NULL,                   // process security attributes 
          NULL,                   // primary thread security attributes 
          TRUE,                   // handles are inherited 
          0,                          // creation flags 
          NULL,                  // use parent's environment 
          NULL,                  // use parent's current directory 
          &siStartInfo,      // STARTUPINFO pointer 
          &piProcInfo);     // receives PROCESS_INFORMATION 
     
       //如果失败,退出 
       if (!bSuccess ) return; 
     
        //然后,就可以读写管道了 
        //写入stdin,具体代码在一个WriteToPipe函数中 
        WriteToPipe(); 
     
       //不断子检测进程有否结束 
        while (GetExitCodeProcess(piProcInfo.hProcess,&process_exit_code)) 
            { 
                //读stdout,stderr 
               ReadFromPipe(); 
               //如果子进程结束,退出循环 
               if (process_exit_code!=STILL_ACTIVE) break; 
            } 
    [html] view plaincopy
    #include <windows.h> 
     
      //定义句柄: 构成stdin管道的两端句柄 
      HANDLE  hStdInRead;         //子进程用的stdin的读入端 
      HANDLE  hStdInWrite;        //主程序用的stdin的读入端 
     
      //定义句柄: 构成stdout管道的两端句柄 
      HANDLE  hStdOutRead;     ///主程序用的stdout的读入端 
      HANDLE  hStdOutWrite;    ///子进程用的stdout的写入端 
     
      //定义句柄: 构成stderr管道的句柄,由于stderr一般等于stdout,我们没有定义hStdErrRead,直接用hStdOutRead即可 
      HANDLE  hStdErrWrite;       ///子进程用的stderr的写入端 
     
      //定义一个用于产生子进程的STARTUPINFO结构体 (定义见CreateProcess,函数说明) 
      STARTUPINFO siStartInfo; 
      //定义一个用于产生子进程的PROCESS_INFORMATION结构体 (定义见CreateProcess,函数说明) 
      PROCESS_INFORMATION piProcInfo; 
     
     
      //产生一个用于stdin的管道,得到两个HANDLE:  hStdInRead用于子进程读出数据,hStdInWrite用于主程序写入数据 
      //其中saAttr是一个STARTUPINFO结构体,定义见CreatePipe函数说明 
      if   (!CreatePipe(&hStdInRead, &hStdInWrite,&saAttr, 0)) 
              return; 
     
      //产生一个用于stdout的管道,得到两个HANDLE:  hStdInRead用于主程序读出数据,hStdInWrite用于子程序写入数据 
      if  (!CreatePipe(&hStdOutRead, &hStdOutWrite,&saAttr, 0)) 
            return; 
      //由于stderr一般就是stdout, 直接复制句柄hStdOutWrite,得到 hStdErrWrite 
       if (!DuplicateHandle(GetCurrentProcess(), hStdOutWrite, GetCurrentProcess(), &hStdErrWrite, 0, TRUE, DUPLICATE_SAME_ACCESS)) 
          return; 
     
      //对STARTUPINFO结构体赋值,对stdin,stdout,stderr的Handle设置为刚才得到的管道HANDLE 
      ZeroMemory( &siStartInfo, sizeof(STARTUPINFO) ); 
      siStartInfo.cb = sizeof(STARTUPINFO); 
      siStartInfo.dwFlags  |= STARTF_USESHOWWINDOW; 
      siStartInfo.dwFlags  |= STARTF_USESTDHANDLES; 
      siStartInfo.hStdOutput = hStdOutWrite;     //意思是:子进程的stdout输出到hStdOutWrite 
      siStartInfo.hStdError  =  hStdErrWrite;        //意思是:子进程的stderr输出到hStdErrWrite 
      siStartInfo.hStdInput  = hStdInRead; 
     
      // 产生子进程,具体参数说明见CreateProcess函数 
      bSuccess = CreateProcess(NULL, 
          CommandLine,    // 子进程的命令行 
          NULL,                   // process security attributes 
          NULL,                   // primary thread security attributes 
          TRUE,                   // handles are inherited 
          0,                          // creation flags 
          NULL,                  // use parent's environment 
          NULL,                  // use parent's current directory 
          &siStartInfo,      // STARTUPINFO pointer 
          &piProcInfo);     // receives PROCESS_INFORMATION 
     
       //如果失败,退出 
       if (!bSuccess ) return; 
     
        //然后,就可以读写管道了 
        //写入stdin,具体代码在一个WriteToPipe函数中 
        WriteToPipe(); 
     
       //不断子检测进程有否结束 
        while (GetExitCodeProcess(piProcInfo.hProcess,&process_exit_code)) 
            { 
                //读stdout,stderr 
               ReadFromPipe(); 
               //如果子进程结束,退出循环 
               if (process_exit_code!=STILL_ACTIVE) break; 
            } 

     
    具体看一下WriteToPipe(), ReadFromPipe函数:

    view plaincopy to clipboard
    //写入stdin 
    BOOL WriteToPipe() 

      DWORD dwWritten; 
      BOOL bSuccess = FALSE; 
       
    //用WriteFile,从hStdInWrite写入数据,数据在in_buffer中,长度为dwSize 
      bSuccess = WriteFile( hStdInWrite, in_buffer, dwSize, &dwWritten, NULL); 
      return bSuccess; 

    [html] view plaincopy
    //写入stdin 
    BOOL WriteToPipe() 

      DWORD dwWritten; 
      BOOL bSuccess = FALSE; 
       
    //用WriteFile,从hStdInWrite写入数据,数据在in_buffer中,长度为dwSize 
      bSuccess = WriteFile( hStdInWrite, in_buffer, dwSize, &dwWritten, NULL); 
      return bSuccess; 

     

    view plaincopy to clipboard
    // 读出stdout 
    BOOL ReadFromPipe() 

      char out_buffer[4096]; 
      DWORD dwRead;   
      BOOL bSuccess = FALSE; 
     
       //用WriteFile,从hStdOutRead读出子进程stdout输出的数据,数据结果在out_buffer中,长度为dwRead 
       bSuccess = ReadFile( hStdOutRead, out_buffer, BUFSIZE, &dwRead, NULL); 
       if ((bSuccess) && (dwRead!=0))  //如果成功了,且长度>0 
                 { 
                       // 此处加入你自己的代码 
                       // 比如:将数据写入文件或显示到窗口中 
                 } 
      return bSuccess; 

    [html] view plaincopy
    // 读出stdout 
    BOOL ReadFromPipe() 

      char out_buffer[4096]; 
      DWORD dwRead;   
      BOOL bSuccess = FALSE; 
     
       //用WriteFile,从hStdOutRead读出子进程stdout输出的数据,数据结果在out_buffer中,长度为dwRead 
       bSuccess = ReadFile( hStdOutRead, out_buffer, BUFSIZE, &dwRead, NULL); 
       if ((bSuccess) && (dwRead!=0))  //如果成功了,且长度>0 
                 { 
                       // 此处加入你自己的代码 
                       // 比如:将数据写入文件或显示到窗口中 
                 } 
      return bSuccess; 

    OK,到此原理写完了。为简化说明原理,上述代码省略了出错处理、结束处理(如:CloseHandle等),具体可以参见我的源码。

    二、封装、实用的代码

    上述过程有些麻烦,实际使用中,我封装成几个函数:

    首先定义三个回调函数 (就是函数指针类型)

    //当stdin输入时,调用此函数。将要写的数据写入buf中,*p_size写为数据长度即可。
    typedef void FuncIn(char *buf,int *p_size);

    //当stdout,stderr输出时,调用此函数。可读取的数据在buf中,数据长度为size。
    typedef void FuncOut(char *buf,int size);

    //当子进程持续过程中,周期性调用此函数,设置p_abort可中断子进程。
    typedef void FuncProcessing(int *p_abort);

    然后定义了四个函数

    //执行一个命令行,重定向stdin, stdout,stderr。
    //OnStdOut是回调函数指针,当有输出时,OnStdOut被调用。
    //OnStdIn是回调函数指针,当输入时,OnStdIn被调用。
    int ExecCommandEx(char *szCommandLine,char *CurrentDirectory,char *Environment,unsigned short ShowWindow,
                  FuncOut *OnStdOut,FuncProcessing *OnProcessing,FuncIn *OnStdIn);

    //执行一个命令行,重定向stdout,stderr。
    //OnLineOut是回调函数指针,当有一行输出时,OnLineOut被调用。
    int ExecCommandOutput(char *szCommandLine,char *Environment,unsigned short ShowWindow,
                           FuncOut *OnLineOut,FuncProcessing *OnProcessing);

    //执行一个命令行,等待子进程结束,返回子进程的程序退出代码。不处理重定向。
    int ExecCommandWait(char *szCommandLine,unsigned short ShowWindow,FuncProcessing *OnProcessing);

    //执行一个命令行,不等待子进程结束,即返回。不处理重定向。功能相当于 Windows API  WinExec.
    int ExecCommandNoWait(char *szCommandLine,unsigned short ShowWindow);

    还定义了一个存储数据的EXEC_INFO结构体及操作它的函数。

    全部代码为C语言,在JExecution.c, JExecution.h两个文件中。只用到了Windows API,没有用MFC及其他库。

    三、使用方法

    有了JExecution.c,使用就很方便了。比如:

    view plaincopy to clipboard
    #include <stdio.h> 
    #include <windows.h> 
    #include "JExecution.h" 
     
    //定义一个处理输出的回调函数 
    void OnLineOut(char *buf,int size) 

       printf("%s ", buf); 

     
     
    int main(int argc, char* argv[]) 

      int n; 
      char *command; 
     
      command = "cmd.exe  /r dir/w ";   //这个命令的意思就是调用DOS,执行dir命令,显示当前目前下的文件 
      n=ExecCommandOutput(command,NULL,SW_HIDE,OnLineOut,NULL); 
      printf("<Return:>%d",n); 

    [html] view plaincopy
    #include <stdio.h> 
    #include <windows.h> 
    #include "JExecution.h" 
     
    //定义一个处理输出的回调函数 
    void OnLineOut(char *buf,int size) 

       printf("%s ", buf); 

     
     
    int main(int argc, char* argv[]) 

      int n; 
      char *command; 
     
      command = "cmd.exe  /r dir/w ";   //这个命令的意思就是调用DOS,执行dir命令,显示当前目前下的文件 
      n=ExecCommandOutput(command,NULL,SW_HIDE,OnLineOut,NULL); 
      printf("<Return:>%d",n); 

    我用C++ Builder 6写了一个示范程序。示范将stdout重定向,输出到窗口中。

    四、小结

    Windows管道虽然有些麻烦,却可以产生好的效果。

    比如:我们常用的IDE就是把调用命令行的编译程序,将其stdout重定向,将结果信息显示在窗口中。
    又比如,我们把上述的程序改一下,把dir命令的结果通过网络发送出去,嗯,Hacker.

  • 相关阅读:
    【LeetCode-位运算】汉明距离总和
    python类的继承和重写
    单元测试unittest使用说明
    Java学习90
    Java学习89
    Java学习88
    Java学习87
    Java学习86
    Java学习85
    Java学习84
  • 原文地址:https://www.cnblogs.com/predator-wang/p/4911370.html
Copyright © 2020-2023  润新知