今天碰到一個(gè)有意思的問題,如何在GUI里調(diào)用命令行并返回處理結(jié)果信息。說是在GUI程序里調(diào)用,其實(shí)和普通進(jìn)程調(diào)用命令行并返回結(jié)果的原理是一樣的。一般情況下,用system或CreateProcess創(chuàng)建一個(gè)進(jìn)程來執(zhí)行一個(gè)命令行,并不關(guān)心它執(zhí)行過程中的具體信息,有時(shí)甚至不在乎它何時(shí)結(jié)束,而在今天所碰到的問題中,卻恰恰需要關(guān)注這兩點(diǎn)。關(guān)于這兩點(diǎn),可以再強(qiáng)調(diào)一下:
1.在一個(gè)進(jìn)程中(GUI進(jìn)程或普通的Console進(jìn)程,原理都是一樣的)調(diào)用一個(gè)指定的命令行,并等待其返回,也就是說調(diào)用過程是阻塞的,不然沒辦法搜取到準(zhǔn)確的信息。
2.調(diào)用完畢后必須得到這個(gè)命令行調(diào)用過程中的輸出信息,即單獨(dú)在控制臺(tái)上執(zhí)行該命令行所輸出的信息。
好了,這就是我們的需求。我們主函數(shù)看起來是這個(gè)樣子的:
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類做些什么?簡單的說就是創(chuàng)建一個(gè)進(jìn)程,然后讓該進(jìn)程執(zhí)行給定的命令,最后通過某種方式返回結(jié)果,而這種方式就是我們所說的管道。我們先看看Windows系統(tǒng)上的實(shí)現(xiàn),這個(gè)實(shí)現(xiàn)是基于網(wǎng)上的一篇文章。
這個(gè)是cmd_caller的類申明,cmd_caller_private_data結(jié)構(gòu)便于跨平臺(tái)實(shí)現(xiàn)(具體請看后面):
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上的一種實(shí)現(xiàn):
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)) //創(chuàng)建管道)
37 {
38 return false;
39 }
40
41
42 private_data_->si.cb = sizeof(STARTUPINFOA);
43 GetStartupInfo(&private_data_->si); //設(shè)置啟動(dòng)信息
44 private_data_->si.hStdError = private_data_->hPipeWrite; //將子進(jìn)程錯(cuò)誤輸出結(jié)果寫到管道
45 private_data_->si.hStdOutput = private_data_->hPipeWrite; //將子進(jìn)程標(biāo)準(zhǔn)輸出結(jié)果寫出管道
46 private_data_->si.wShowWindow = SW_HIDE; //隱式調(diào)用子程序,及不顯示子程序窗口
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 //根據(jù)設(shè)定的參數(shù)創(chuàng)建并執(zhí)行子進(jìn)程
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 //等待子進(jìn)程退出
90 if(WAIT_FAILED == WaitForSingleObject(private_data_->pi.hProcess, INFINITE))
91 {
92 return false;
93 }
94
95 //關(guān)閉子進(jìn)程的相關(guān)句柄,釋放資源
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下的實(shí)現(xiàn):
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 //在子進(jìn)程中,將管道的寫入文件描述符重定向到標(biāo)準(zhǔn)輸出和標(biāo)準(zhǔn)錯(cuò)誤輸出
56 //這樣命令執(zhí)行的結(jié)果將通過管道發(fā)送到父進(jìn)程
57 dup2(private_data_->pipefd[1], STDOUT_FILENO);
58 dup2(private_data_->pipefd[1], STDERR_FILENO);
59
60 //在子進(jìn)程中,我們不需要用到讀管道,把他關(guān)掉
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 //寫管道已經(jīng)用完,不需要在往管道中寫了,我們把它關(guān)了
67 close(private_data_->pipefd[1]);
68
69 return true;
70 }
71 else
72 {
73 char buf[1024];
74 size_t readsize;
75 //在父進(jìn)程中我們不需要用到寫管道,可以關(guān)掉
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 //讀管道已經(jīng)用完,可以把它關(guān)掉
82 close(private_data_->pipefd[0]);
83 return true;
84 }
85 }
到這里時(shí),任務(wù)已近完成,然而,還有一種更簡單的調(diào)用方法,就是popen/pclose函數(shù)(Windows上是_popen/_pclose函數(shù)),下面就是利用這種方法的實(shí)現(xiàn):
1 /*
2 *其他實(shí)現(xiàn)不變
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 *其他實(shí)現(xiàn)不變
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.第一種實(shí)現(xiàn)直接將子進(jìn)程的標(biāo)準(zhǔn)輸出和錯(cuò)誤輸出重定向到管道,而第二種實(shí)現(xiàn)卻需要在命令中指出重定向。
2.調(diào)用方式。如果命令是一個(gè)路徑,并且路徑中間有特殊字符如空格,那么兩種實(shí)現(xiàn)的調(diào)用方法可能有些區(qū)別,前一種方式更直接,而后一種方式需要加轉(zhuǎn)義。最后以Windows上的一個(gè)例子來說明這兩種調(diào)用之間的區(qū)別:
以第一種實(shí)現(xiàn)調(diào)用
cmd_caller caller("E:\\Program Files\\Nmap\\nmap.exe -T4 -A -v -PE -PS22,25,80 -PA21,23,80,3389 www.baidu.com")
對應(yīng)于以第二種實(shí)現(xiàn)調(diào)用
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")