关于管道——子进程调用命令行并返回执行结果的问题

    今天碰到一个有意思的问题,如何在GUI里调用命令行并返回处理结果信息。说是在GUI程序里调用,其实和普通进程调用命令行并返回结果的原理是一样的。一般情况下,用system或CreateProcess创建一个进程来执行一个命令行,并不关心它执行过程中的具体信息,有时甚至不在乎它何时结束,而在今天所碰到的问题中,却恰恰需要关注这两点。关于这两点,可以再强调一下:
1.在一个进程中(GUI进程或普通的Console进程,原理都是一样的)调用一个指定的命令行,并等待其返回,也就是说调用过程是阻塞的,不然没办法搜取到准确的信息。
2.调用完毕后必须得到这个命令行调用过程中的输出信息,即单独在控制台上执行该命令行所输出的信息。
    好了,这就是我们的需求。我们主函数看起来是这个样子的:

 1  /*  Main.cpp  */
 2  #include  " cmd_caller.h "
 3  #include  < iostream >
 4 
 5  int  main( int  argc,  char *  argv[])
 6 
 7  {
 8      cmd_caller caller( " netstat -ano " );
 9      std:: string  retstring;
10       if  ( ! caller.call_cmd(retstring))
11      {
12          std::cout << " call failed\n " ;
13           return   1 ;
14      }
15      std::cout.write(retstring.c_str(), retstring.size());
16       return   0 ;
17  }

    cmd_caller类做些什么?简单的说就是创建一个进程,然后让该进程执行给定的命令,最后通过某种方式返回结果,而这种方式就是我们所说的管道。我们先看看Windows系统上的实现,这个实现是基于网上的一篇文章。
    这个是cmd_caller的类申明,cmd_caller_private_data结构便于跨平台实现(具体请看后面):

 1  /*  cmd_caller.h  */
 2  #ifndef __CMD_CALLER_H__
 3  #define  __CMD_CALLER_H__
 4  #include  < string >
 5 
 6  struct  cmd_caller_private_data;
 7  class  cmd_caller
 8  {
 9  public :
10      cmd_caller( const   char *  cmdstr);
11      cmd_caller( const  std:: string &  cmdstr);
12       virtual   ~ cmd_caller();
13       void  set_cmd( const   char *  cmdstr);
14       void  set_cmd( const  std:: string &  cmdstr);
15       bool  call_cmd(std:: string &  output);
16  protected :
17  private :
18       bool  init_private_data();
19      cmd_caller_private_data *  private_data_;
20  };
21  #endif
    
    下面是cmd_caller在Windows上的一种实现:

  1  #include  " cmd_caller.h "
  2  #include  < Windows.h >
  3  #include  < stdio.h >
  4 
  5  struct  cmd_caller_private_data 
  6  {
  7      std:: string  cmd_str;
  8      HANDLE hPipeRead;
  9      HANDLE hPipeWrite;
 10      SECURITY_ATTRIBUTES sa;
 11      STARTUPINFOA si;
 12      PROCESS_INFORMATION pi;
 13  };
 14 
 15  cmd_caller::cmd_caller( const   char *  cmdstr)
 16  {
 17      private_data_  =   new  cmd_caller_private_data();
 18      private_data_ -> cmd_str  =  std:: string (cmdstr);
 19  }
 20 
 21  cmd_caller::cmd_caller( const  std:: string &  cmdstr)
 22  {
 23      private_data_  =   new  cmd_caller_private_data();
 24      private_data_ -> cmd_str  =  cmdstr;
 25  }
 26 
 27  bool  cmd_caller::init_private_data()
 28  {
 29      private_data_ -> sa.nLength  =   sizeof (SECURITY_ATTRIBUTES);
 30      private_data_ -> sa.lpSecurityDescriptor  =  NULL;
 31      private_data_ -> sa.bInheritHandle  =  TRUE;
 32 
 33       if  ( ! CreatePipe( & private_data_ -> hPipeRead,
 34           & private_data_ -> hPipeWrite,
 35           & private_data_ -> sa,
 36           0 ))     // 创建管道)
 37      {
 38           return   false ;
 39      }
 40      
 41 
 42      private_data_ -> si.cb  =   sizeof (STARTUPINFOA);
 43      GetStartupInfo( & private_data_ -> si);             // 设置启动信息
 44      private_data_ -> si.hStdError  =  private_data_ -> hPipeWrite;   // 将子进程错误输出结果写到管道
 45      private_data_ -> si.hStdOutput  =  private_data_ -> hPipeWrite;  // 将子进程标准输出结果写出管道
 46      private_data_ -> si.wShowWindow  =  SW_HIDE;         // 隐式调用子程序,及不显示子程序窗口
 47      private_data_ -> si.dwFlags  =  STARTF_USESHOWWINDOW  |  STARTF_USESTDHANDLES;
 48 
 49       return   true ;
 50  }
 51 
 52  cmd_caller:: ~ cmd_caller()
 53  {
 54      delete private_data_;
 55  }
 56 
 57  void  cmd_caller::set_cmd( const   char *  cmdstr)
 58  {
 59      private_data_ -> cmd_str  =  std:: string (cmdstr);
 60  }
 61 
 62  void  cmd_caller::set_cmd( const  std:: string &  cmdstr)
 63  {
 64      private_data_ -> cmd_str  =  cmdstr;
 65  }
 66 
 67  bool  cmd_caller::call_cmd(std:: string &  output)
 68  {
 69       if ( ! init_private_data())
 70      {
 71           return   false ;
 72      }
 73 
 74       // 根据设定的参数创建并执行子进程
 75       if ( ! CreateProcessA(NULL, 
 76          (LPSTR)private_data_ -> cmd_str.c_str(), 
 77          NULL, 
 78          NULL, 
 79          TRUE, 
 80          NULL, 
 81          NULL, 
 82          NULL, 
 83           & private_data_ -> si, 
 84           & private_data_ -> pi))
 85      {
 86           return   false ;
 87      }
 88 
 89       // 等待子进程退出
 90       if (WAIT_FAILED  ==  WaitForSingleObject(private_data_ -> pi.hProcess, INFINITE))
 91      {
 92           return   false ;
 93      }
 94      
 95       // 关闭子进程的相关句柄,释放资源
 96      CloseHandle(private_data_ -> pi.hProcess);
 97      CloseHandle(private_data_ -> pi.hThread);
 98 
 99       // Note:We must close the write handle of pipe, else the ReadFile call will pending infinitely.
100      CloseHandle(private_data_ -> hPipeWrite);
101 
102       char  buf[ 1024 ];
103      DWORD dwRead;
104      output.clear();
105 
106       for  (;;)
107      {
108          BOOL result  =  ReadFile(private_data_ -> hPipeRead, buf,  1024 & dwRead, NULL);
109 
110           if ( ! result  ||  dwRead  ==   0 )
111          {
112               break
113          }
114           else
115          {
116              output.append(buf, dwRead);
117          }
118      }
119       return   true ;
120  }

    下面我们再来看一下Linux下的实现:

 1  #include  " cmd_caller.h "
 2  #include  < stdio.h >
 3  #include  < unistd.h >
 4 
 5  struct  cmd_caller_private_data
 6  {
 7      std:: string  cmd_str;
 8       int  pipefd[ 2 ];
 9  };
10 
11  cmd_caller::cmd_caller( const   char   * cmdstr)
12  {
13      private_data_  =   new  cmd_caller_private_data();
14      private_data_ -> cmd_str  =  std:: string (cmdstr);
15  }
16 
17  cmd_caller::cmd_caller( const  std:: string   & cmdstr)
18  {
19     private_data_  =   new  cmd_caller_private_data();
20      private_data_ -> cmd_str  =  cmdstr;
21  }
22 
23  cmd_caller:: ~ cmd_caller()
24  {   delete private_data_;
25  }
26 
27  void  cmd_caller::set_cmd( const   char *  cmdstr)
28  {
29      private_data_ -> cmd_str  =  std:: string (cmdstr);
30  }
31 
32  void  cmd_caller::set_cmd( const  std:: string &  cmdstr)
33  {
34      private_data_ -> cmd_str  =  cmdstr;
35  }
36 
37  bool  cmd_caller::init_private_data()
38  {
39       return  ( - 1   !=  pipe(private_data_ -> pipefd));
40  }
41 
42 
43  bool  cmd_caller::call_cmd(std:: string   & output)
44  {
45       if ( ! init_private_data())
46           return   false ;
47 
48       int  pid  =  fork();
49       if ( - 1   ==  pid)
50      {
51           return   false ;
52      }
53       else   if ( 0   ==  pid)
54      {
55           // 在子进程中,将管道的写入文件描述符重定向到标准输出和标准错误输出
56           // 这样命令执行的结果将通过管道发送到父进程
57          dup2(private_data_ -> pipefd[ 1 ], STDOUT_FILENO);
58          dup2(private_data_ -> pipefd[ 1 ], STDERR_FILENO);
59 
60           // 在子进程中,我们不需要用到读管道,把他关掉
61          close(private_data_ -> pipefd[ 0 ]);
62 
63           if ( - 1   ==  execlp(private_data_ -> cmd_str.c_str(), private_data_ -> cmd_str.c_str(), NULL))
64               return   false ;
65 
66           // 写管道已经用完,不需要在往管道中写了,我们把它关了
67          close(private_data_ -> pipefd[ 1 ]);
68      
69       return   true ;
70      }
71       else
72      {
73           char  buf[ 1024 ];
74          size_t readsize;
75           // 在父进程中我们不需要用到写管道,可以关掉
76          close(private_data_ -> pipefd[ 1 ]);
77           while ((readsize  =  read(private_data_ -> pipefd[ 0 ], buf,  1024 ))  >   0 )
78          {
79              output.append(buf, readsize);
80          }
81           // 读管道已经用完,可以把它关掉
82          close(private_data_ -> pipefd[ 0 ]);
83           return   true ;
84      }
85  }

    到这里时,任务已近完成,然而,还有一种更简单的调用方法,就是popen/pclose函数(Windows上是_popen/_pclose函数),下面就是利用这种方法的实现:
 1  /*
 2  *其他实现不变
 3  */
 4 
 5  bool  cmd_caller::init_private_data()
 6  {
 7       return   true ;
 8  }
 9 
10  bool  cmd_caller::call_cmd(std:: string   & output)
11  {
12       char    buffer[ 128 ];
13      FILE    * fpipe;
14 
15       if ( (fpipe  =  popen(private_data_ -> cmd_str.c_str(),  " r "  ))  ==  NULL )
16           return   false ;
17 
18       // Read pipe until end of file, or an error occurs.
19 
20      output.clear();
21       while (fgets(buffer,  128 , fpipe))
22      {
23          output.append(buffer);
24      }
25 
26       // Close pipe and print return value of fpipe.
27       if  (feof(fpipe))
28      {
29          pclose(fpipe);
30           return   true ;
31      }
32       else
33      {
34          pclose(fpipe);
35           return   false ;
36      }
37  }

    同样,在Windows上 :

 1  /*
 2  *其他实现不变
 3  */
 4 
 5  bool  cmd_caller::init_private_data()
 6  {
 7       return   true ;
 8  }
 9 
10  bool  cmd_caller::call_cmd(std:: string   & output)
11  {
12       char    psBuffer[ 128 ];
13      FILE    * pPipe;
14 
15       if ( (pPipe  =  _popen(private_data_ -> cmd_str.c_str(),  " rt "  ))  ==  NULL )
16           return   false ;
17 
18       // Read pipe until end of file, or an error occurs.
19 
20      output.clear();
21       while (fgets(psBuffer,  128 , pPipe))
22      {
23          output.append(psBuffer);
24      }
25 
26       // Close pipe and print return value of pPipe.
27       if  (feof( pPipe))
28      {
29          _pclose(pPipe);
30           return   true ;
31      }
32       else
33      {
34          _pclose(pPipe);
35           return   false ;
36      }
37  }
38 

    值得注意的是,以上两种方法有下面一些不同:
1.第一种实现直接将子进程的标准输出和错误输出重定向到管道,而第二种实现却需要在命令中指出重定向。
2.调用方式。如果命令是一个路径,并且路径中间有特殊字符如空格,那么两种实现的调用方法可能有些区别,前一种方式更直接,而后一种方式需要加转义。最后以Windows上的一个例子来说明这两种调用之间的区别:
以第一种实现调用
cmd_caller caller( " E:\\Program Files\\Nmap\\nmap.exe -T4 -A -v -PE -PS22,25,80 -PA21,23,80,3389  www.baidu.com " )
对应于以第二种实现调用
cmd_caller caller( " \ " E:\\Program Files\\Nmap\\nmap.exe\ "  -T4 -A -v -PE -PS22,25,80 -PA21,23,80,3389 www.baidu.com 2>&1 " )



你可能感兴趣的:(关于管道——子进程调用命令行并返回执行结果的问题)