Qt 進程和線程之一:運行一個進程和進程間通信


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 Commu­nication)。 簡單介紹如下 :


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()函數將進程與共享內存段進行分離。最后將圖片顯示到標簽中。

參考:

Qt 之進程間通信(共享內存)



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM