今天碰到一个有意思的问题,如何在GUI里调用命令行并返回处理结果信息。说是在GUI程序里调用,其实和普通进程调用命令行并返回结果的原理是一样的。一般情况下,用system或CreateProcess创建一个进程来执行一个命令行,并不关心它执行过程中的具体信息,有时甚至不在乎它何时结束,而在今天所碰到的问题中,却恰恰需要关注这两点。关于这两点,可以再强调一下:
1.在一个进程中(GUI进程或普通的Console进程,原理都是一样的)调用一个指定的命令行,并等待其返回,也就是说调用过程是阻塞的,不然没办法搜取到准确的信息。
2.调用完毕后必须得到这个命令行调用过程中的输出信息,即单独在控制台上执行该命令行所输出的信息。
好了,这就是我们的需求。我们主函数看起来是这个样子的:
cmd_caller类做些什么?简单的说就是创建一个进程,然后让该进程执行给定的命令,最后通过某种方式返回结果,而这种方式就是我们所说的管道。我们先看看Windows系统上的实现,这个实现是基于网上的一篇文章。
这个是cmd_caller的类申明,cmd_caller_private_data结构便于跨平台实现(具体请看后面):
下面是cmd_caller在Windows上的一种实现:
下面我们再来看一下Linux下的实现:
到这里时,任务已近完成,然而,还有一种更简单的调用方法,就是popen/pclose函数(Windows上是_popen/_pclose函数),下面就是利用这种方法的实现:
同样,在Windows上 :
值得注意的是,以上两种方法有下面一些不同:
1.第一种实现直接将子进程的标准输出和错误输出重定向到管道,而第二种实现却需要在命令中指出重定向。
2.调用方式。如果命令是一个路径,并且路径中间有特殊字符如空格,那么两种实现的调用方法可能有些区别,前一种方式更直接,而后一种方式需要加转义。最后以Windows上的一个例子来说明这两种调用之间的区别:
以第一种实现调用
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 }
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
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 }
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 }
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 }
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
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
"
)