Qt提供了一個與平台無關的QProcess類,用以對進程的支持。本節講述了怎樣在Qt應用程序中啟動一個外部程序進程,以及幾種常用的進程間通信方法。如果對進程和線程的概念不是很了解,可以看我的另一篇博客:[多進程和多線程的概念。
設計應用程序時,有時不希望將一個不太相關的功能集成到程序中,或者是因為該功能與當前設計的應用程序聯系不大,或者是因為該功能已經可以使用現成的程序很好地實現了,這時就可以在當前的應用程序中調用外部的程序來實現該功能,這就會使用到進程。Qt應用程序可以很容易地啟動一個外部應用程序,而且Qt也提供了多種進程間通信的方法。
一、運行一個進程
Qt的QProcess類可以用來啟動一個外部程序並與其進行通信。下面我們來看一下怎么在Qt代碼中啟動一個進程。
運行一個進程打開記事本
首先創建QtGui應用,工程名稱為“myProcess”,其他選項保持默認即可。先進入mainwindow.h文件添加代碼,添加私有對象定義:QProcess myProcess。然后在設計模式往界面上拖入一個Push Button部件,修改其顯示文本為“啟動一個進程”。在按鈕上點擊鼠標右鍵,轉到其clicked()信號對應的槽,更改如下:
void MainWindow::on_pushButton_clicked()
{
myProcess.start("notepad.exe");
}
這里我們使用QProcess對象運行了Windows系統下的記事本程序(即notepad.exe程序),因為該程序在系統目錄中,所以這里不需要指定其路徑。大家也可以運行其他任何的程序,只需要指定其具體路徑即可。我們看到,可以使用start()來啟動一個程序,有時啟動一個程序時需要指定啟動參數,這種情況在命令行啟動程序時是很常見的。
運行程序,單擊界面上的按鈕時就會彈出一個記事本程序。
QProcess也提供了一組函數,可以脫離事件循環來使用,它們會掛起調用的線程直到確定的信號被發射:
- waitForStarted()阻塞直到進程已經啟動;
- waitForReadyRead()阻塞直到在當前讀通道上有可讀的數據;
- waitForBytesWritten()阻塞直到一個有效負載數據已經被寫人到進程;
- waitForFinished()阻塞直到進程已經結束。
二、進程間通信方式
Qt提供了多種方法在Qt應用程序中實現進程間通信IPC(Inter-Process Communication)。 簡單介紹如下 :
TCP/IP
跨平台的Qt Network模塊提供的類可以讓網絡編程更加便攜和方便。它提供了高級類(例如:QNetworkAccessManager、QFtp)通信,使用特定的應用程序級協議,和較底層的類(例如:QTcpSocket、QTcpServer、QSslSocket)用於實現協議。
共享內存
跨平台的QSharedMemory-共享內存類,提供對操作系統的共享內存的實現。它允許多個線程和進程安全訪問共享內存段。此外,QSystemSemaphore可以用來控制訪問由系統共享的資源,以及進程之間的通信。
D-Bus
Qt的D-Bus模塊是一種可用於使用D-Bus協議實現IPC的唯一Unix庫。它將Qt的信號和槽機制延伸到IPC級別,允許由一個進程發出的信號被連接到另一個進程的槽。Qt的D-Bus文檔已經詳細說明如何使用Qt中的D-Bus模塊。
QProcess
使用先前提到的QProcess類。跨平台類QProcess可以用於啟動外部程序作為子進程,並與它們進行通信。它提供了用於監測和控制該子進程狀態的API。另外,QProcess為從QIODevice繼承的子進程提供了輸入/輸出通道。
會話管理
在Linux/X11平台上,Qt提供了會話管理的支持。會話允許事件傳播到進程,例如,當檢測到關機時。進程和應用程序可以執行任何必要的操作,例如:保存打開的文檔。
三、共享內存之進程間通信實例
下面來看一個使用共享內存的例子,它實現的功能是:先在一個對話框中將一張圖片寫入到共享內存段中,然后再在另一個對話框中從共享內存段讀出該圖片。
新建Qt Gui應用,名稱為mylPC,類名為Dialog,基類選擇QDialog。完成后進人設計模式,向界面中放入兩個Push Button部件和一個Label部件。將一個按鈕的顯示文本更改為“從文件中加載圖片”,將其objectName屬性更改為loadFromFileButton,將另一個按鈕的顯示文本更改為“從共享內存顯示圖片”,將其objectName屬性更改為loadFromSharedMemoryButton。然后進人dialog.h文件,修改如下:
#ifndef DIALOG_H
#define DIALOG_H
#include <QDialog>
#include <QSharedMemory>
namespace Ui {
class Dialog;
}
class Dialog : public QDialog
{
Q_OBJECT
public:
explicit Dialog(QWidget *parent = nullptr);
~Dialog();
public slots:
//從文件中加載圖片到共享內存
void loadFromFile();
//從共享內存中加載圖片
void loadFromMemory();
private slots:
//從文件中加載圖片按鈕
void on_loadFromFileButton_clicked();
//從共享內存加載並顯示圖片按鈕
void on_loadFromSharedMemoryButton_clicked();
private:
//將進程與共享內存段進行分離,如果失敗則進行提示
void detach();
private:
Ui::Dialog *ui;
QSharedMemory sharedMemory; //定義一個共享內存對象
};
#endif // DIALOG_H
然后進人dialog.cpp文件,修改如下:
#include "dialog.h"
#include "ui_dialog.h"
#include <QFileDialog>
#include <QBuffer>
#include <QDebug>
Dialog::Dialog(QWidget *parent) :
QDialog(parent),
ui(new Ui::Dialog)
{
ui->setupUi(this);
//在使用共享內存以前,需要設置key,系統用它作為底層共享內存段的標識
sharedMemory.setKey("QSharedMemoryExample");
}
Dialog::~Dialog()
{
delete ui;
}
//從文件中加載圖片到共享內存
void Dialog::loadFromFile()
{
//判斷該進程是否已經連接到共享內存段,連接成功返回true
if (sharedMemory.isAttached())
detach();
ui->label->setText(tr("選擇一個圖片文件!"));
//使用文件對話框獲得打開圖片路徑
QString fileName = QFileDialog::getOpenFileName(nullptr, QString(), QString(),
tr("Images (*.png *.jpg)"));
//label標簽顯示圖片
QImage image;
if (!image.load(fileName))
{
ui->label->setText(tr("選擇的文件不是圖片,請選擇圖片文件!"));
return;
}
ui->label->setPixmap(QPixmap::fromImage(image));
//將圖片加載到共享內存
//使用QBuffer來暫存圖片,這樣便可以獲得圖片的大小,還可以獲得圖片數據的指針
QBuffer buffer;
buffer.open(QBuffer::ReadWrite);
QDataStream out(&buffer);
out << image;
int size = static_cast<int>(buffer.size());
//使用create()函數創建指定大小的共享內存段,該函數還會自動將共享內存段連接到本進程上。
if (!sharedMemory.create(size))
{
ui->label->setText(tr("無法創建共享內存段!"));
return;
}
//在進行共享內存段的操作前,需要先進行加鎖
sharedMemory.lock();
char *to = static_cast<char*>(sharedMemory.data());
const char *from = buffer.data().data();
//使用memcpy()函數將圖片數據復制到共享內存
memcpy(to, from, static_cast<size_t>(qMin(sharedMemory.size(), size)));
//等操作完成后,再進行解鎖。
sharedMemory.unlock();
}
//從共享內存中加載圖片
void Dialog::loadFromMemory()
{
//使用attache()函數將進程連接到共享內存段
if (!sharedMemory.attach())
{
ui->label->setText(tr("無法連接到共享內存段,\n"
"請先加載一張圖片!"));
return;
}
QBuffer buffer;
QDataStream in(&buffer);
QImage image;
//使用QBuffer來讀取共享內存段中的數據
sharedMemory.lock();
buffer.setData((char*)sharedMemory.constData(), sharedMemory.size());
buffer.open(QBuffer::ReadOnly);
in >> image;
sharedMemory.unlock();
//將進程與共享內存段進行分離,如果失敗則進行提示
sharedMemory.detach();
ui->label->setPixmap(QPixmap::fromImage(image));
}
//將進程與共享內存段進行分離,如果失敗則進行提示
void Dialog::detach()
{
if (!sharedMemory.detach())
ui->label->setText(tr("無法從共享內存中分離!"));
}
//從文件中加載圖片按鈕
void Dialog::on_loadFromFileButton_clicked()
{
loadFromFile();
}
//從共享內存加載並顯示圖片按鈕
void Dialog::on_loadFromSharedMemoryButton_clicked()
{
loadFromMemory();
}
在一個運行的實例上單擊“從文件中加載圖片”按鈕,然后選擇一張圖片。在第二個運行的實例上單擊“從共享內存顯示圖片”按 鈕,這時便會顯示第一個實例中加載的圖片,效果如下所示:
四、程序分析
(1)loadFromFile()
- 這里先使用isAttached()函數判斷該進程是否已經連接到共享內存段,如果是,那么就調用detach()先將該進程與共享內存段進行分離。
- 然后使用QFileDialog類來打開一個圖片文件,並將其顯示到標簽上。為了將圖片加載到共享內存,這里使用了QBuffer來暫存圖片,這樣便可以獲得圖片的大小,還可以獲得圖片數據的指針。
- 后面使用了create()函數來創建指定大小的共享內存段,其大小的單位是字節,該函數還會自動將共享內存段連接到本進程上。
- 在操作共享內存段時要使用lock()進行加鎖,然后才可以使用memcpy()函數將buffer對應的數據段復制到共享內存段,操作完成后要使用unlock()進行解鎖。這樣在同一時間,就只能有一個進程允許操作共享內存段了。
(2)loadFromMemory()
- 這里先使用attache()函數將進程連接到共享內存段。
- 在操作共享內存段時要使用lock()進行加鎖,然后才可以使用QBuffer來讀取共享內存段中的數據,操作完成后要使用unlock()進行解鎖。
- 因為現在已經不需要使用共享內存了,所以調用detach()函數將進程與共享內存段進行分離。最后將圖片顯示到標簽中。
參考: