C++中一個工程呼叫另一個 工程的 .exe檔案
用於在不同的工程檔案中,一種語言的工程A去呼叫另一種語言的的工程B。並且可能工程B中要用到工程A 中的引數變數。
如果工程A中每個引數變數是一個一維矩陣,也即N*1;這時候如果需要每取變數中一個值,就需要呼叫一次工程B中.exe檔案,如果寫成for 迴圈來做的話,需要呼叫N次.exe檔案,耗時太長。改進的做法就是把工程A中的引數變數寫到一個.txt 檔案中,然後再去讀.txt檔案,每讀取一次變數值,呼叫用到的工程B中函式,然後將計算的結果寫到.txt檔案中,最後再編譯封裝成一個 .exe檔案,最終在工程A檔案中去呼叫一次 .exe檔案即可,省時間。
方法一:
1/ 用system()
#include <stdlib.h>
int system(const char *command);
函式工作大致流程:system()函式先fork一個子程序,在這個子程序中呼叫/bin/sh -c來執行command指定的命令。/bin/sh在系統中一般是個軟連結,指向dash或者bash等常用的shell,-c選項是告訴shell從字串command中讀取要執行的命令(shell將擴充套件command中的任何特殊字元)。父程序則呼叫waitpid()函式來為變成殭屍的子程序收屍,獲得其結束狀態,然後將這個結束狀態返回給system()函式的呼叫者。
具體的 內容詳解見https://blog.csdn.net/linuxheik/article/details/52825010
具體呼叫的格式:
system(".exe檔案的絕對路徑,引數一,引數二,引數三 ......");
c_str()函式的用法:
c++語言提供了兩種字串實現,其中較原始的一種只是字串的c語言實現。與C語言的其他部分一樣,它在c++的所有實現中可用,我們將這種實現提供的字串物件,歸為c-串,每個c-串char*型別的。
標準標頭檔案<cstring>包含操作c-串的函式庫。這些庫函式表達了我們希望使用的幾乎每種字串操作。 當呼叫庫函式,客戶程式提供的是string類型引數,而庫函式內部實現用的是c-串,因此需要將string物件,轉化為char*物件,而c_str()提供了這樣一種方法,它返回const char*型別(可讀不可改)的指向字元
2/用popen函式 可以獲取 system 函式執行的結果,也即cout輸出到 螢幕上的資料。最好是有返回值的。
- #include <iostream>
- #include <unistd.h>
- #include <stdlib.h>
- #include <stdio.h>
- usingnamespace std;
- string getCmdResult(const string &strCmd)
- {
- char buf[10240] = {0};
- FILE *pf = NULL;
- if( (pf = popen(strCmd.c_str(), "r")) == NULL )
- {
- return"";
- }
- string strResult;
- while(fgets(buf, sizeof buf, pf))
- {
- strResult += buf;
- }
- pclose(pf);
- unsigned int iSize = strResult.size();
- if(iSize > 0 && strResult[iSize - 1] == '\n') // linux
- {
- strResult = strResult.substr(0, iSize - 1);
- }
- return strResult;
- }
- int main()
- {
- cout << getCmdResult("date") << endl;
- cout << getCmdResult("echo -n abc | md5sum | awk '{print $1}'") << endl;
- return 0;
- }
Linux 中的popen機制可以在程式中執行一個shell命令,有兩種操作模式,分別為讀和寫。在讀模式中,程式中可以讀取到命令的輸出,其中有一個應用就是獲取網路介面的引數。在寫模式中,最常用的是建立一個新的檔案或開啟其他服務等。如果要用 popen函式來只執行.exe 檔案的話,應該用pf = popen(strCmd.c_str(), "w"))這個命令,另外,strcmd =.exe 的絕對路徑+引數變數。
3/
system和popen
1. system()和popen()簡介
在linux中我們可以通過system()來執行一個shell命令,popen()也是執行shell命令並且通過管道和shell命令進行通訊。
system()、popen()給我們處理了fork、exec、waitpid等一系列的處理流程,讓我們只需要關注最後的返回結果(函式的返回值)即可。
2. system()、popen()原始碼
首先我們來看一下這兩個函式在原始碼(虛擬碼)上面的差異。
int system(const char *command)
{
struct sigaction sa_ignore, sa_intr, sa_quit;
sigset_t block_mask, orig_mask;
pid_t pid;
sigemptyset(&block_mask);
sigaddset(&block_mask, SIGCHLD);
sigprocmask(SIG_BLOCK, &block_mask, &orig_mask); //1. block SIGCHLD
sa_ignore.sa_handler = SIG_IGN;
sa_ignore.sa_flags = 0;
sigemptyset(&sa_ignore.sa_mask);
sigaction(SIGINT, &sa_ignore, &sa_intr); //2. ignore SIGINT signal
sigaction(SIGQUIT, &sa_ignore, &sa_quit); //3. ignore SIGQUIT signal
switch((pid = fork()))
{
case -1:
return -1;
case 0:
sigaction(SIGINT, &sa_intr, NULL);
sigaction(SIGQUIT, &sa_quit, NULL);
sigprocmask(SIG_SETMASK, &orig_mask, NULL);
execl("/bin/sh", "sh", "-c", command, (char *) 0);
exit(127);
default:
while(waitpid(pid, NULL, 0) == -1) //4. wait child process exit
{
if(errno != EINTR)
{
break;
}
}
}
}
return 0;
上面是一個不算完整的system函式原始碼,後面需要我們關注和popen差異的部分已經用數字標示出來了。
static pid_t *childpid = NULL;
/* ptr to array allocated at run-time */
static int maxfd; /* from our open_max(), {Prog openmax} */
#define SHELL "/bin/sh"
FILE *
popen(const char *cmdstring, const char *type)
{
int i, pfd[2];
pid_t pid;
FILE *fp;
/* only allow "r" or "w" */
if ((type[0] != 'r' && type[0] != 'w') || type[1] != 0) {
errno = EINVAL; /* required by POSIX.2 */
return(NULL);
}
if (childpid == NULL) { /* first time through */
/* allocate zeroed out array for child pids */
maxfd = open_max();
if ( (childpid = calloc(maxfd, sizeof(pid_t))) == NULL)
return(NULL);
}
if (pipe(pfd) < 0)
return(NULL); /* errno set by pipe() */
if ( (pid = fork()) < 0)
return(NULL); /* errno set by fork() */
else if (pid == 0) { /* child */
if (*type == 'r') {
close(pfd[0]);
if (pfd[1] != STDOUT_FILENO) {
dup2(pfd[1], STDOUT_FILENO);
close(pfd[1]);
}
} else {
close(pfd[1]);
if (pfd[0] != STDIN_FILENO) {
dup2(pfd[0], STDIN_FILENO);
close(pfd[0]);
}
}
/* close all descriptors in childpid[] */
for (i = 0; i < maxfd; i++)
if (childpid[ i ] > 0)
close(i);
execl(SHELL, "sh", "-c", cmdstring, (char *) 0);
_exit(127);
}
/* parent */
if (*type == 'r') {
close(pfd[1]);
if ( (fp = fdopen(pfd[0], type)) == NULL)
return(NULL);
} else {
close(pfd[0]);
if ( (fp = fdopen(pfd[1], type)) == NULL)
return(NULL);
}
childpid[fileno(fp)] = pid; /* remember child pid for this fd */
return(fp);
}
上面是popen的原始碼。
3. 執行流程
從上面的原始碼可以看到system和popen都是執行了類似的執行流程,大致是fork->execl->return。但是我們看到system在執行期間呼叫程序會一直等待shell命令執行完成(waitpid等待子程序結束)才返回,但是popen無須等待shell命令執行完成就返回了。我們可以理解system為序列執行,在執行期間呼叫程序放棄了”控制權”,popen為並行執行。
popen中的子程序沒人給它”收屍”了啊?是的,如果你沒有在呼叫popen後呼叫pclose那麼這個子程序就可能變成”殭屍”。
上面我們沒有給出pclose的原始碼,其實我們根據system的原始碼差不多可以猜測出pclose的原始碼就是system中第4部分的內容。
4. 訊號處理
我們看到system中對SIGCHLD、SIGINT、SIGQUIT都做了處理,但是在popen中沒有對訊號做任何的處理。
SIGCHLD是子程序退出的時候發給父程序的一個訊號,system()中為什麼要遮蔽SIGCHLD訊號可以參考:system函式的總結、waitpid(or wait)和SIGCHILD的關係,總結一句就是為了system()呼叫能夠及時的退出並且能夠正確的獲取子程序的退出狀態(成功回收子程序)。
popen沒有遮蔽SIGCHLD,主要的原因就是popen是”並行”的。如果我們在呼叫popen的時候遮蔽了SIGCHLD,那麼如果在呼叫popen和pclose之間呼叫程序又建立了其它的子程序並且呼叫程序註冊了SIGCHLD訊號處理控制代碼來處理子程序的回收工作(waitpid)那麼這個回收工作會一直阻塞到pclose呼叫。這也意味著如果呼叫程序在pclose之前執行了一個wait()操作的話就可能獲取到popen建立的子程序的狀態,這樣在呼叫pclose的時候就會回收(waitpid)子程序失敗,返回-1,同時設定errno為ECHLD,標示pclose無法獲取子程序狀態。
system()中遮蔽SIGINT、SIGQUIT的原因可以繼續參考上面提到的system函式的總結,popen()函式中沒有遮蔽SIGINT、SIGQUIT的原因也還是因為popen是”並行的”,不能影響其它”並行”程序。
5. 功能
從上面的章節我們基本已經把這兩個函式剖析的差不多了,這兩個的功能上面的差異也比較明顯了,system就是執行shell命令最後返回是否執行成功,popen執行命令並且通過管道和shell命令進行通訊。
NOTE
在特權(setuid、setgid)程序中千萬注意不要使用system和popen。
補充: **fork用來建立一個子程序** 一個程式一呼叫fork函式,首先,系統讓新的程序與舊的程序使用同一個程式碼段,因為它們的程式還是相同的,對於資料段和堆疊段,系統則複製一份給新的程序,這樣,父程序的所有資料都可以留給子程序,但是,子程序一旦開始執行,雖然它繼承了父程序的一切資料,但實際上資料卻已經分開,相互之間不再有影響了,也就是說,它們之間不再共享任何資料了。而如果兩個程序要共享什麼資料的話,就要使用另一套函式(shmget,shmat,shmdt等)來操作。現在,已經是兩個程序了,對於父程序,fork函式返回了子程式的程序號,而對於子程式,fork函式則返回零,這樣,對於程式,只要判斷fork函式的返回值,就知道自己是處於父程序還是子程序中。 **system 可以看做是fork + execl + waitpid。system()函式,**功能強大 當system接受的命令為NULL時直接返回,否則fork出一個子程序,因為fork在兩個程序:父程序和子程序中都返回,這裡要檢查返回的pid,fork在子程序中返回0,在父程序中返回子程序的pid,父程序使用waitpid等待子程序結束,子程序則是呼叫execl來啟動一個程式代替自己,execl(“/bin/sh”, “sh”, “-c”, cmdstring, (char*)0)是呼叫shell,這個shell的路徑是/bin/sh,後面的字串都是引數,然後子程序就變成了一個shell程序,這個shell的引數是cmdstring,就是system接受的引數。在windows中的shell是command,想必大家很熟悉shell接受命令之後做的事了。 **popen()也常常被用來執行一個程式**
FILE *popen(const char *command, const char *type);int pclose(FILE *stream);
popen() 函式用建立管道的方式啟動一個 程序, 並呼叫 shell. 因為管道是被定義成單向的, 所以 type 引數只能定義成只讀或者只寫, 不能是兩者同時, 結果流也相應的是隻讀或者只寫. command 引數是一個字串指標, 指向的是一個以 null 結束符結尾的字串, 這個字串包含一個 shell 命令. 這個命令被送到 /bin/sh 以 -c 引數執行, 即由 shell 來執行. type 引數也是一個指向以 null 結束符結尾的字串的指標, 這個字串必須是 ‘r‘ 或者 ‘w’ 來指明是讀還是寫.
popen() 函式的返回值是一個普通的標準I/O流, 它只能用 pclose() 函式來關閉, 而不是 fclose() 函式. 向這個流的寫入被轉化為對 command 命令的標準輸入; 而 command 命令的標準輸出則是和呼叫 popen(), 函式的程序相同,除非這個被command命令自己改變. 相反的, 讀取一個 “被popen了的” 流, 就相當於讀取 command 命令的標準輸出, 而 command 的標準輸入則是和呼叫 popen, 函式的程序相同.