百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

Qt进程和线程之一:运行一个进程和进程间通信

wxin55 2024-10-30 04:35 8 浏览 0 评论

Qt提供了一个与平台无关的QProcess类,用以对进程的支持。本节讲述了怎样在Qt应用程序中启动一个外部程序进程,以及几种常用的进程间通信方法。

设计应用程序时,有时不希望将一个不太相关的功能集成到程序中,或者是因为该功能与当前设计的应用程序联系不大,或者是因为该功能已经可以使用现成的程序很好地实现了,这时就可以在当前的应用程序中调用外部的程序来实现该功能,这就会使用到进程。Qt应用程序可以很容易地启动一个外部应用程序,而且Qt也提供了多种进程间通信的方法。

1、运行一个进程

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()阻塞直到进程已经结束。

2、进程间通信方式

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提供了会话管理的支持。会话允许事件传播到进程,例如,当检测到关机时。进程和应用程序可以执行任何必要的操作,例如:保存打开的文档。

3、共享内存之进程间通信实例

下面来看一个使用共享内存的例子,它实现的功能是:先在一个对话框中将一张图片写入到共享内存段中,然后再在另一个对话框中从共享内存段读出该图片。

新建Qt Gui应用,名称为mylPC,类名为Dialog,基类选择QDialog。完成后进人设计模式,向界面中放入两个Push Button部件和一个Label部件。将一个按钮的显示文本更改为“从文件中加载图片”,将其objectName属性更改为loadFromFileButton,将另一个按钮的显示文本更改为“从共享内存显示图片”,将其objectName属性更改为loadFromSharedMemoryButton。然后进人dialog.h文件,修改如下:

QT开发交流+赀料君羊:714620761

#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();
}

在一个运行的实例上单击“从文件中加载图片”按钮,然后选择一张图片。在第二个运行的实例上单击“从共享内存显示图片”按 钮,这时便会显示第一个实例中加载的图片,效果如下所示:


4、程序分析

(1)loadFromFile()

  • 这里先使用isAttached()函数判断该进程是否已经连接到共享内存段,如果是,那么就调用detach()先将该进程与共享内存段进行分离。
  • 然后使用QFileDialog类来打开一个图片文件,并将其显示到标签上。为了将图片加载到共享内存,这里使用了QBuffer来暂存图片,这样便可以获得图片的大小,还可以获得图片数据的指针。
  • 后面使用了create()函数来创建指定大小的共享内存段,其大小的单位是字节,该函数还会自动将共享内存段连接到本进程上。
  • 在操作共享内存段时要使用lock()进行加锁,然后才可以使用memcpy()函数将buffer对应的数据段复制到共享内存段,操作完成后要使用unlock()进行解锁。这样在同一时间,就只能有一个进程允许操作共享内存段了。

(2)loadFromMemory()

  • 这里先使用attache()函数将进程连接到共享内存段。
  • 在操作共享内存段时要使用lock()进行加锁,然后才可以使用QBuffer来读取共享内存段中的数据,操作完成后要使用unlock()进行解锁。
  • 因为现在已经不需要使用共享内存了,所以调用detach()函数将进程与共享内存段进行分离。最后将图片显示到标签中。

相关推荐

ES6中 Promise的使用场景?(es6promise用法例子)

一、介绍Promise,译为承诺,是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大在以往我们如果处理多层异步操作,我们往往会像下面那样编写我们的代码doSomething(f...

JavaScript 对 Promise 并发的处理方法

Promise对象代表一个未来的值,它有三种状态:pending待定,这是Promise的初始状态,它可能成功,也可能失败,前途未卜fulfilled已完成,这是一种成功的状态,此时可以获取...

Promise的九大方法(promise的实例方法)

1、promise.resolv静态方法Promise.resolve(value)可以认为是newPromise方法的语法糖,比如Promise.resolve(42)可以认为是以下代码的语...

360前端一面~面试题解析(360前端开发面试题)

1.组件库按需加载怎么做的,具体打包配了什么-按需加载实现:借助打包工具(如Webpack的require.context或ES模块动态导入),在使用组件时才引入对应的代码。例如在V...

前端面试-Promise 的 finally 怎么实现的?如何在工作中使用?

Promise的finally方法是一个非常有用的工具,它无论Promise是成功(fulfilled)还是失败(rejected)都会执行,且不改变Promise的最终结果。它的实现原...

最简单手写Promise,30行代码理解Promise核心原理和发布订阅模式

看了全网手写Promise的,大部分对于新手还是比较难理解的,其中几个比较难的点:状态还未改变时通过发布订阅模式去收集事件实例化的时候通过调用构造函数里传出来的方法去修改类里面的状态,这个叫Re...

前端分享-Promise可以中途取消啦(promise可以取消吗)

传统Promise就像一台需要手动组装的设备,每次使用都要重新接线。而Promise.withResolvers的出现,相当于给开发者发了一个智能遥控器,可以随时随地控制异步操作。它解决了三大...

手写 Promise(手写输入法 中文)

前言都2020年了,Promise大家肯定都在用了,但是估计很多人对其原理还是一知半解,今天就让我们一起实现一个符合PromiseA+规范的Promise。附PromiseA+规范地址...

什么是 Promise.allSettled()!新手老手都要会?

Promise.allSettled()方法返回一个在所有给定的promise都已经fulfilled或rejected后的promise,并带有一个对象数组,每个对象表示对应的pr...

前端面试-关于Promise解析与高频面试题示范

Promise是啥,直接上图:Promise就是处理异步函数的API,它可以包裹一个异步函数,在异步函数完成时抛出完成状态,让代码结束远古时无限回掉的窘境。配合async/await语法糖,可...

宇宙厂:为什么前端离不开 Promise.withResolvers() ?

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发。1.为什么需要Promise.with...

Promise 新增了一个超实用的 API!

在JavaScript的世界里,Promise一直是处理异步操作的神器。而现在,随着ES2025的发布,Promise又迎来了一个超实用的新成员——Promise.try()!这个新方法简...

一次搞懂 Promise 异步处理(promise 异步顺序执行)

PromisePromise就像这个词的表面意识一样,表示一种承诺、许诺,会在后面给出一个结果,成功或者失败。现在已经成为了主流的异步编程的操作方式,写进了标准里面。状态Promise有且仅有...

Promise 核心机制详解(promise机制的实现原理)

一、Promise的核心状态机Promise本质上是一个状态机,其行为由内部状态严格管控。每个Promise实例在创建时处于Pending(等待)状态,此时异步操作尚未完成。当异步操作成功...

javascript——Promise(js实现promise)

1.PromiseES6开始支持,Promise对象用于一个异步操作的最终完成(包括成功和失败)及结果值的表示。简单说就是处理异步请求的。之所以叫Promise,就是我承诺,如果成功则怎么处理,失败怎...

取消回复欢迎 发表评论: