今天碰到一个有意思的问题,如何在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")



Feedback

# re: 关于管道——子进程调用命令行并返回执行结果的问题  回复  更多评论   

2010-03-06 09:49 by zuhd
以前获取mac地址时用到了这个东西,基本上用的第一个方案,学习!

# re: 关于管道——子进程调用命令行并返回执行结果的问题  回复  更多评论   

2010-03-06 12:30 by David Fang
@zuhd
呵呵,我也是看了书才弄出来的。

# re: 关于管道——子进程调用命令行并返回执行结果的问题  回复  更多评论   

2011-11-25 20:23 by christopher
If any of the exec() functions returns, an error will have occurred.
——from "man execlp"
_______________________________________________________
67 行那条语句不可能被执行到。

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   博问   Chat2DB   管理


posts - 9, comments - 13, trackbacks - 0, articles - 0

Copyright © David Fang