1. 程式人生 > >C++中一個工程呼叫另一個 工程的 .exe檔案

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輸出到 螢幕上的資料。最好是有返回值的。

  1. #include <iostream>
  2. #include <unistd.h>
  3. #include <stdlib.h>
  4. #include <stdio.h>
  5. usingnamespace std;  
  6. string getCmdResult(const string &strCmd)  
  7. {  
  8.     char buf[10240] = {0};  
  9.     FILE *pf = NULL;  
  10.     if( (pf = popen(strCmd.c_str(), "r")) == NULL )  
  11.     {  
  12.         return"";  
  13.     }  
  14.     string strResult;  
  15.     while(fgets(buf, sizeof buf, pf))  
  16.     {  
  17.         strResult += buf;  
  18.     }  
  19.     pclose(pf);  
  20.     unsigned int iSize =  strResult.size();  
  21.     if(iSize > 0 && strResult[iSize - 1] == '\n')  // linux
  22.     {  
  23.         strResult = strResult.substr(0, iSize - 1);  
  24.     }  
  25.     return strResult;  
  26. }  
  27. int main()  
  28. {  
  29.     cout << getCmdResult("date") << endl;  
  30.     cout << getCmdResult("echo -n abc | md5sum | awk '{print $1}'") << endl;  
  31.     return 0;  
  32. }  

    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, 函式的程序相同.