1. 程式人生 > >QT中呼叫外部程式的方法 QProcess類

QT中呼叫外部程式的方法 QProcess類

QT4對於介面程式設計無疑是一個很方便的工具。但是由於它介面開發專項特性,可能導致了某些方面的不足(到目前為止暫時沒有使用到這類複雜功能,所以只能是推測)。這樣當整個程式需要某些功能時,就需要外部模組的支援。為了能夠與外部程式相互聯絡,Qt4提供了強大的外部程式呼叫類。先說說QProcess類,目前主要用到程式呼叫函式。官方說明如下:

(引用自ttp://qt.nokia.com/doc/4.5/qprocess.html#execute-2)

#include <QProcess>

int QProcess::execute ( const QString & program, const QStringList & arguments )   [static]
Starts the program program with the arguments arguments in a new process, waits for it to finish, and then returns the exit code of the process. Any data the new process writes to the console is forwarded to the calling process.

The environment and working directory are inherited by the calling process.

On Windows, arguments that contain spaces are wrapped in quotes.

int QProcess::execute ( const QString & program )   [static]
This is an overloaded function.

Starts the program program in a new process. program is a single string of text containing both the program name and its arguments. The arguments are separated by one or more spaces.

   這兩個函式使用起來是很簡單的,很方便就可以呼叫外部程式。

   但是有一點是值得注意的,當外部函式被呼叫以後,主程式的程序就會被呼叫的函式阻塞,導致無法繼續對操作做出反映。要解決這個問題,可以利用QThread類為呼叫的外部程式建立一個新程序,建立方法如下:

例如需要在視窗中開啟登錄檔編輯器

    1、明確開啟檔案的位置和名稱,登錄檔編輯器名稱為regedit

    2、建立一個類MyThread,用於繼承QThread類,並宣告虛擬函式run(),該函式用於程式呼叫該程序時自動呼叫。

       #include <QThread>

       class MyThread : public QThread

       {

        public

            void run();

       };

    3、在MyTread::run()函式中新增需要該程序執行的內容(本例子中要利用QProcess類呼叫regedit)

       #include <QProcess>

       void MyThread::run()

       {

            QProcess process;

            process.execute("regedit");

        }

     經過以上的處理後,在執行regedit的時候主視窗就不會因阻塞而無響應了。

     這只是個簡單的實現外部函式呼叫的方法,至於程序如何同步的問題,還在繼續研究中

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

問題:
    我做的那個小軟體的圖形介面是基於QT3.2的,在主介面的命令列編輯框輸入命令以後要執行別人已經寫好的可執行檔案。這些可執行檔案執行的時間比較長,在終端上執行時會顯示一些執行的資訊,最後才顯示執行結果。我的介面上有一個文字框,我想把它們在後臺執行過程中的資訊不斷新增到文字框中,相當於實時顯示吧,不過要求也不是那麼高。

    我說我現在怎麼做的吧,我在一個叫做QGUI_CommandWidget類(屬於主視窗)中定義了一個命令列編輯框輸入命令,定義一個QTextEdit物件用來顯示那些執行資訊,定義了一個自定義的MyThread類物件用來執行外部程式,在這個執行緒的run函式裡我呼叫fork,execv函式執行外部程式,把可執行程式的標準輸出重定向到管道,然後從管道讀那些資訊,再把這些資訊用QApplication::postEvent()函式傳回主執行緒,由主執行緒把這些資訊append到文字框中。

    我現在的疑問是:
        第一,執行外部程式,用fork、execv函式是不是不行,非要用QProcess不可?為什麼呢?論壇上講的也不是很清楚。具體怎麼做呢?在那個QGUI_CommandWidget類建立一個QProcess類物件還是在我MyThread類物件裡再建立一個QProcess類物件?
        第二,基於qt3的GUI執行緒和非GUI執行緒的通訊,應該怎麼做?那個外部程式我是不能更改的,它什麼時候結束我也不知道。用QProcess的話它的輸出資訊我要怎麼樣才能讀到然後回顯在我的主視窗的文字框中?怎麼知道可執行程式結束然後殺死該執行緒?
        第三,我在《C++ GUI programming with qt3》中看到:“ QTimer 類以及應用於網路的QFtp, QHttp, QSocket, and QSocketNotifier 類都是基於訊息事件迴圈的,所以也不能用在非GUI執行緒中。”這是為什麼呢?還有我看了別人用QQSocketdevice的例子裡都用到了QSocketNotifier。在你的部落格說到用Qthread、QQSocketdevice、QWaitCondition可以完成視訊採集,你是否也用到QSocketNotifier?

回答:
1)完全可以使用fork,execv函式,其實QProcess類只是對這些底層函式的封裝而
已,但是考慮到使用QProcess的話,不需要自己處理程式管道,也不需要自己處理
windows下的情況,可以省去很多時間,因此還是推薦使用QProcess,他們的效果
將是一樣的。

2)照你的需求,完全可以不需要使用執行緒,因為QProcess已經自己處理掉這些事
了,在使用QProcess的start函式執行外部程式後,這個函式不會被阻塞,另外
QProcess也會以事件的方式自動將外部程式傳回的資訊反饋回來。具體看以下這個
簡單的例子:

class enstCdRecord : public QObject
...{
  。。。。。。。。。。。。
  QProcess mProcess;
};

enstCdRecord::enstCdRecord(QWidget *pParent)
...{
  mParent = pParent;
  connect(&mProcess, SIGNAL(readyReadStandardError()), this,
  SLOT(ReadProcessOutput())); //連線readyReadStandardError事件,這樣就可以
    得到程式StdErr中的資訊了,同樣也可以連線其 readyReadOutput事件。
}

bool enstCdRecord::CreateCd(const QString &pImageFile)
...{
  QStringList cmdlist;
  cmdlist.append("-v");
  cmdlist.append("speed=2");
  cmdlist.append(pImageFile);

  mProcess.start("cdrecord.exe", cmdlist);
  while (! mProcess.waitForFinished(300)) ...{ //啟動程式後,用迴圈等待其結
    束,如果對程式何時結束並不關心,以下程式碼可以不需要。
  if (mProcess.state() == QProcess::NotRunning) ...{ //process failed
    QMessageBox::critical(mParent, SYSTEMNAME, tr("Error when record cd."));
    return false;
  }
  qApp->processEvents(); //防止UI死鎖,一般情況下,用這種等一小段時間(這
    裡是300ms),讓UI響應一次的辦法,已經足夠使用了。
  }
  if (mProcess.exitCode() != 0) ...{ //error when run process
    QMessageBox::critical(mParent, SYSTEMNAME, tr("Error when record cd."));
    return false;
  }
  return true;
}

void enstCdRecord::ReadProcessOutput()
...{
  QMessageBox::critical(mParent, SYSTEMNAME,
  mProcess.readAllStandardError()); //將程式的StdErr資訊顯示出來。
}

外部程式究竟使用StdOut還是使用StdErr來作為執行時狀態的輸出,各個程式的處
理方式都不一樣,甚至可能根本沒有輸出,這個需要自己試驗。

3)一般網路程式中只要在主執行緒中使用QSocket和QSocketNotifier,就可以完成
資料的傳送和接收了,它們不會造成程式介面死鎖。在我寫的blog的情況下,我的
程式在從網路上接收到資料後,需要做一些計算處理和儲存工作,由於資料量很
大,並且程式對效能的要求比較高,因此只能將整個網路資料接收功能放到執行緒中
了,在那種情況下,QSocket並不適用,而必須使用同步的QSocketDevice。

其實,QT3下提供兩種網路訪問功能,一類是QFtp, QHttp, QSocket, and
QSocketNotifier等,它們都會在接收到資料後,以事件方式進行通知。另一類是
QSocketDevice,它不會主動發出任何通知事件,而是必須靠外部程式來查詢,或
者等待其接收到資料,才能知道何時有資料被收到。

《C++ GUI programming with qt3》中的那句話沒有錯,(但是它是針對QT3說的,
在QT4中根本就沒有這幾個類了,並且QT4中Thread也可以用事件了)。在QT3中,
只有 UI執行緒在可以使用事件,在其它輔助執行緒中是無法使用的,因此不能使用靠
事件進行通知的那幾個類了。

我的程式中沒有使用QSocketNotifier,正如前面說的,這個類只是配合QSocket使
用的,它是考事件進行通知的。我的程式使用的是 QSocketDevice,並且靠其Wait
功能,來等待資料到達。