文章目录
一、内存共享示例图
读取文本
下图是读取文本的操作,其中显示了中文所占字节数及发送者和读取者的通信。
读取图片
下图将对应宏放开则是图片读取效果,其中图片读取的是死路径,需要自己定义。
二、界面操作共享内存示例图
文本读取示例图
下图操作了从创建key到共享内存的步骤,其中包括接收者修改数据的部分。
图片读取示例图
下图仅为图片的共享与读取。
弹窗示例图
下图仅演示了部分提示弹窗的效果,具体以代码为准。
提示:只需要了解用法,查看简易共享内存代码即可。
不会使用Qt设计师设计界面的小伙伴点击这里
二、个人理解与一些心得
- create函数创建的大小和实际指定的大小可能存在差异,我猜测会考虑内存对齐等等情况,讯飞星火认知大模型给出的回答是“这是因为在创建共享内存时,操作系统会为共享内存预留一些额外的空间,以便在需要时进行扩展。因此,实际获取到的共享内存大小可能会比创建时指定的大小要大一些。”;
- 当最后一个持有程序detach后,共享内存控件将自动释放,所以建议共享内存创建者的detach放在析构函数,其他进程“实报实销”。
三、源码
简易内存共享Demo
创建者
main.cpp
#include <QApplication>
#include <QDebug>
#include <QSharedMemory>
#include <QBuffer>
#include <QDataStream>
#include <QLabel>
#include <QImage>
#include <QPixmap>
//#define ACTIVE_SHARED_IMG
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QSharedMemory sharedMemSender;
// 设置共享内存区域Key
sharedMemSender.setKey("SimpleShared");
// 数据大小对象
int size = 0;
#ifndef ACTIVE_SHARED_IMG
// 创建字符数据
QString sendData = u8"共享内存中的数据";
//! 获取字符大小(中文在字节中所占小的存储与字符个数不一致)
//! 下方代码中一个中文字符占三个字节
size = sendData.toUtf8().size();
qDebug() << u8"一个中文字符所占字节数:" << size / sendData.size();
#else
// 创建QImage对象,并传入图片路径
QImage img("D:/xx.jpg");
// 创建QBuffer对象并以读写方式打开
QBuffer buffer;
buffer.open(QIODevice::ReadWrite);
// 创建QDataStream对象并指定数据存储对象为buffer
QDataStream inStream(&buffer);
// 将图片数据传入
inStream << img;
// 获取需要共享的数据大小
size = buffer.size();
// 将共享的图片用label显示出来
QLabel label;
label.setPixmap(QPixmap::fromImage(img));
label.setWindowTitle("Sender");
label.show();
#endif
// 创建共享内存区域
sharedMemSender.create(488812);
// 将共享内存区域锁定
sharedMemSender.lock();
//! 写入数据的操作
#ifndef ACTIVE_SHARED_IMG
memcpy(sharedMemSender.data(), sendData.toStdString().data(), size);
#else
memcpy(sharedMemSender.data(), buffer.data().data(), buffer.size());
#endif
// 将共享内存区域解锁
sharedMemSender.unlock();
return a.exec();
}
接收者
#include <QApplication>
#include <QDebug>
#include <QSharedMemory>
#include <QBuffer>
#include <QDataStream>
#include <QLabel>
#include <QImage>
#include <QPixmap>
//#define ACTIVE_SHARED_IMG
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QSharedMemory sharedMemReceiver;
// 设置共享内存区域Key
sharedMemReceiver.setKey("SimpleShared");
// 将共享内存区域与程序挂接
sharedMemReceiver.attach();
// 将共享内存区域锁定
sharedMemReceiver.lock();
// 获取共享内存大小(此处相当于数据大小)
int size = sharedMemReceiver.size();
#ifndef ACTIVE_SHARED_IMG
QByteArray data;
data.resize(size);
memcpy(data.data(), sharedMemReceiver.data(), size);
qDebug() << QString(data);
#else
// 创建QBuffer对象
QBuffer buffer;
// 将共享内存的数据设置到buffer容器中
buffer.setData((char *)sharedMemReceiver.constData(), size);
// 然后以只读的方式打开
buffer.open(QBuffer::ReadOnly);
// 创建QDataStream对象读取数据,并指定数据buffer
QDataStream outStream(&buffer);
// 创建QImage并通过数据流对象将图片数据写入
QImage img;
outStream >> img;
// 创建label将读取到的图片显示出来
QLabel label;
label.setWindowTitle("Receiver");
label.setPixmap(QPixmap::fromImage(img));
label.show();
#endif
// 将共享内存解锁
sharedMemReceiver.unlock();
// 将挂接的共享内存与程序分离
sharedMemReceiver.detach();
return a.exec();
}
界面共享内存Demo
创建者
CMainWindowMemSender.h
#ifndef CMAINWINDOWMEMSENDER_H
#define CMAINWINDOWMEMSENDER_H
#include <QBuffer>
#include <QMainWindow>
#include <QSharedMemory>
namespace Ui {
class CMainWindowMemSender;
}
class CMainWindowMemSender : public QMainWindow
{
Q_OBJECT
public:
explicit CMainWindowMemSender(QWidget *parent = nullptr);
~CMainWindowMemSender();
private:
/** * @brief judgeSize 判断共享内存与传入值的大小比较,若是传入值大于共享内存则弹出指定提示文本 * @param dataSize 传入的数据大小 * @param note 提示文本 * @return 共享内存与传入值的大小比较 */
bool judgeSize(qint64 dataSize, const QString ¬e);
private slots:
/** * @brief on_btnShareData_clicked 数据共享按钮 */
void on_btnShareData_clicked();
/** * @brief on_btnReadData_clicked 数据读取按钮 */
void on_btnReadData_clicked();
/** * @brief on_btnCreateShareSize_clicked 创建共享内存按钮 */
void on_btnCreateShareSize_clicked();
/** * @brief on_btnBindShareKey_clicked 绑定共享内存key按钮 */
void on_btnBindShareKey_clicked();
private:
Ui::CMainWindowMemSender *ui;
QSharedMemory m_sharedMemory; // 共享内存对象
};
#endif // CMAINWINDOWMEMSENDER_H
CMainWindowMemSender.cpp
#include "CMainWindowMemSender.h"
#include "ui_CMainWindowMemSender.h"
#include <QMessageBox>
#include <QImage>
#include <QPixmap>
#include <QDataStream>
#include <QBuffer>
#include <QFileDialog>
#include <QStandardPaths>
CMainWindowMemSender::CMainWindowMemSender(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::CMainWindowMemSender)
{
ui->setupUi(this);
// 设置标题
this->setWindowTitle(u8"共享内存数据发送窗口");
}
CMainWindowMemSender::~CMainWindowMemSender()
{
delete ui;
}
bool CMainWindowMemSender::judgeSize(qint64 dataSize, const QString ¬e)
{
bool ret = m_sharedMemory.size() >= dataSize;
if(!ret) {
QMessageBox::information(this, u8"提示", note);
}
return ret;
}
void CMainWindowMemSender::on_btnShareData_clicked()
{
if(judgeSize(1, u8"共享内存大小为0,请先创建共享内存")) {
// 共享内存锁定
m_sharedMemory.lock();
// 当前为首个tab页签进入(此处是文本类型)
if(0 == ui->tabWidget->currentIndex()) {
// 获取共享的文本
QString str = ui->textEdit->toPlainText();
// 数据转为UTF-8再获取大小(考虑中文所占字节数的情况)
qint64 dataSize = str.toUtf8().size();
// 判断数据是否可以存入共享内存
if(judgeSize(dataSize, u8"数据拷贝失败,共享内存大小小于数据大小")) {
// 拷贝数据
memcpy(m_sharedMemory.data(), str.toStdString().data(), dataSize);
}
}
else {
// 当前共享内容为图片
// 通过文件弹窗获取图片路径
QString imgPath = QFileDialog::getOpenFileName
(this, u8"选择图片"
, QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)
, "*.png *.jpg");
// 判断获取的图片路径是否为空
if(!imgPath.isEmpty()) {
// 创建buffer对象
QBuffer buffer;
// 通过读写方式打开
buffer.open(QIODevice::ReadWrite);
// 创建数据流对象,并将流容器指定为创建的buffer对象
QDataStream writeStream(&buffer);
// 创建图片对象(方便存入buffer)
QImage img(imgPath);
// 将图片数据写入buffer
writeStream << img;
// 获取buffer的大小(相当于图片大小)
qint64 imgSize = buffer.size();
// 判断数据是否可以存入共享内存
if(judgeSize(imgSize, u8"数据拷贝失败,共享内存大小小于数据大小")) {
// 拷贝数据到共享内存
memcpy(m_sharedMemory.data(), buffer.data().data(), imgSize);
// 将图片设置到对应的显示区域
ui->labelImg->setPixmap(QPixmap::fromImage(img));
}
}
}
// 将共享内存解锁
m_sharedMemory.unlock();
}
}
void CMainWindowMemSender::on_btnReadData_clicked()
{
// 将共享内存锁定
m_sharedMemory.lock();
// 获取共享内存常量指针
const char *memData = (char *)m_sharedMemory.constData();
// 获取共享内存大小
qint64 dataSize = m_sharedMemory.size();
// 判断索引值,区分图片还是文本
if(0 == ui->tabWidget->currentIndex()) {
// 直接创建字节数组对象,并将共享内存常量指针和共享内存大小传入
QByteArray data(memData, dataSize);
ui->textEdit->setText(QString(data));
}
else {
// 创建buffer对象
QBuffer buffer;
// 设置数据及数据大小
buffer.setData(memData, dataSize);
// 以读写方式打开buffer
buffer.open(QIODevice::ReadWrite);
// 创建读取的数据流对象
QDataStream readStream(&buffer);
// 创建图片容器
QImage img;
// 将数据写入图片容器
readStream >> img;
// 将读取的图片设置到图片显示位置
ui->labelImg->setPixmap(QPixmap::fromImage(img));
}
// 将共享内存解锁
m_sharedMemory.unlock();
}
void CMainWindowMemSender::on_btnCreateShareSize_clicked()
{
if(m_sharedMemory.key().isEmpty()) {
QMessageBox::information(this, u8"提示", u8"共享内存key为空,创建失败!");
return;
}
int size = ui->spinBoxSize->value();
if(0 < size && 0 == m_sharedMemory.size()) {
if(!m_sharedMemory.create(size)) {
QMessageBox::information(this, u8"提示", u8"共享内存创建失败!");
}
}
else {
QMessageBox::information(this, u8"提示", u8"指定创建内存大小小于0或已创建共享内存");
}
}
void CMainWindowMemSender::on_btnBindShareKey_clicked()
{
// 当前key为空进入
if(m_sharedMemory.key().isEmpty())
{
// 获取需要指定的key,并判断key是否为空
QString keyStr= ui->lineEditKey->text();
if(keyStr.isEmpty()) {
QMessageBox::information(this, u8"提示", u8"指定Key为空!");
ui->labelImg->setPixmap(QPixmap());
return;
}
// 设置key
m_sharedMemory.setKey(keyStr);
}
else {
QMessageBox::information(this, u8"提示", u8"已指定Key!");
}
}
CMainWindowMemSender.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CMainWindowMemSender</class>
<widget class="QMainWindow" name="CMainWindowMemSender">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>417</width>
<height>498</height>
</rect>
</property>
<property name="windowTitle">
<string>CMainWindow</string>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,1">
<item>
<widget class="QLineEdit" name="lineEditKey">
<property name="placeholderText">
<string>请输入指定的共享内存Key</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnBindShareKey">
<property name="text">
<string>绑定共享内存Key</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QSpinBox" name="spinBoxSize">
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>999999999</number>
</property>
<property name="value">
<number>1024</number>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnCreateShareSize">
<property name="text">
<string>创建共享内存</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<widget class="QPushButton" name="btnShareData">
<property name="text">
<string>共享数据</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QPushButton" name="btnReadData">
<property name="text">
<string>读取数据</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>文本数据</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QTextEdit" name="textEdit"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>图片数据</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="labelImg">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>417</width>
<height>23</height>
</rect>
</property>
</widget>
<widget class="QToolBar" name="mainToolBar">
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
</widget>
<widget class="QStatusBar" name="statusBar"/>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>
读取者
CMainWindowMemReceiver.h
#ifndef CMAINWINDOWMEMRECEIVER_H
#define CMAINWINDOWMEMRECEIVER_H
#include <QBuffer>
#include <QMainWindow>
#include <QSharedMemory>
namespace Ui {
class CMainWindowMemReceiver;
}
class CMainWindowMemReceiver : public QMainWindow
{
Q_OBJECT
public:
explicit CMainWindowMemReceiver(QWidget *parent = nullptr);
~CMainWindowMemReceiver();
private:
/** * @brief judgeSize 判断共享内存与传入值的大小比较,若是传入值大于共享内存则弹出指定提示文本 * @param dataSize 传入的数据大小 * @param note 提示文本 * @return 共享内存与传入值的大小比较 */
bool judgeSize(qint64 dataSize, const QString ¬e);
private slots:
/** * @brief on_btnShareData_clicked 数据共享按钮 */
void on_btnShareData_clicked();
/** * @brief on_btnReadData_clicked 数据读取按钮 */
void on_btnReadData_clicked();
/** * @brief on_btnBindShareKey_clicked 绑定共享内存key按钮 */
void on_btnBindShareKey_clicked();
private:
Ui::CMainWindowMemReceiver *ui;
QSharedMemory m_sharedMemory; // 共享内存对象
};
#endif // CMAINWINDOWMEMRECEIVER_H
CMainWindowMemReceiver.cpp
#include "CMainWindowMemReceiver.h"
#include "ui_CMainWindowMemReceiver.h"
#include <QMessageBox>
#include <QImage>
#include <QPixmap>
#include <QDataStream>
#include <QBuffer>
#include <QFileDialog>
#include <QStandardPaths>
CMainWindowMemReceiver::CMainWindowMemReceiver(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::CMainWindowMemReceiver)
{
ui->setupUi(this);
// 设置标题
this->setWindowTitle(u8"共享内存数据接收窗口");
}
CMainWindowMemReceiver::~CMainWindowMemReceiver()
{
if(m_sharedMemory.size() > 0) {
// 将共享内存与程序分离
m_sharedMemory.detach();
}
delete ui;
}
bool CMainWindowMemReceiver::judgeSize(qint64 dataSize, const QString ¬e)
{
bool ret = m_sharedMemory.size() >= dataSize;
if(!ret) {
QMessageBox::information(this, u8"提示", note);
}
return ret;
}
void CMainWindowMemReceiver::on_btnShareData_clicked()
{
// 如果当前进程未挂接共享内存且挂接共享内存失败就进入判断并提示
if(!m_sharedMemory.isAttached() && !m_sharedMemory.attach()) {
QMessageBox::information(this, u8"提示", u8"共享内存未挂接成功!");
return;
}
// 共享内存锁定
m_sharedMemory.lock();
// 当前为首个tab页签进入(此处是文本类型)
if(0 == ui->tabWidget->currentIndex()) {
// 获取共享的文本
QString str = ui->textEdit->toPlainText();
// 数据转为UTF-8再获取大小(考虑中文所占字节数的情况)
qint64 dataSize = str.toUtf8().size();
// 判断数据是否可以存入共享内存
if(judgeSize(dataSize, u8"数据拷贝失败,共享内存大小小于数据大小")) {
// 拷贝数据
memcpy(m_sharedMemory.data(), str.toStdString().data(), dataSize);
}
}
else {
// 当前共享内容为图片
// 通过文件弹窗获取图片路径
QString imgPath = QFileDialog::getOpenFileName
(this, u8"选择图片"
, QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)
, "*.png *.jpg");
// 判断获取的图片路径是否为空
if(!imgPath.isEmpty()) {
// 创建buffer对象
QBuffer buffer;
// 通过读写方式打开
buffer.open(QIODevice::ReadWrite);
// 创建数据流对象,并将流容器指定为创建的buffer对象
QDataStream writeStream(&buffer);
// 创建图片对象(方便存入buffer)
QImage img(imgPath);
// 将图片数据写入buffer
writeStream << img;
// 获取buffer的大小(相当于图片大小)
qint64 imgSize = buffer.size();
// 判断数据是否可以存入共享内存
if(judgeSize(imgSize, u8"数据拷贝失败,共享内存大小小于数据大小")) {
// 拷贝数据到共享内存
memcpy(m_sharedMemory.data(), buffer.data().data(), imgSize);
// 将图片设置到对应的显示区域
ui->labelImg->setPixmap(QPixmap::fromImage(img));
}
}
}
// 将共享内存解锁
m_sharedMemory.unlock();
// 将共享内存与程序分离
m_sharedMemory.detach();
}
void CMainWindowMemReceiver::on_btnReadData_clicked()
{
// 如果当前进程未挂接共享内存且挂接共享内存失败就进入判断并提示
if(!m_sharedMemory.isAttached() && !m_sharedMemory.attach()) {
QMessageBox::information(this, u8"提示", u8"共享内存未挂接成功!");
return;
}
// 将共享内存锁定
m_sharedMemory.lock();
// 获取共享内存常量指针
const char *memData = (char *)m_sharedMemory.constData();
// 获取共享内存大小
qint64 dataSize = m_sharedMemory.size();
// 判断索引值,区分图片还是文本
if(0 == ui->tabWidget->currentIndex()) {
// 直接创建字节数组对象,并将共享内存常量指针和共享内存大小传入
QByteArray data(memData, dataSize);
ui->textEdit->setText(QString(data));
}
else {
// 创建buffer对象
QBuffer buffer;
// 设置数据及数据大小
buffer.setData(memData, dataSize);
// 以读写方式打开buffer
buffer.open(QIODevice::ReadWrite);
// 创建读取的数据流对象
QDataStream readStream(&buffer);
// 创建图片容器
QImage img;
// 将数据写入图片容器
readStream >> img;
// 将读取的图片设置到图片显示位置
ui->labelImg->setPixmap(QPixmap::fromImage(img));
}
// 将共享内存解锁
m_sharedMemory.unlock();
// 将共享内存与程序分离
m_sharedMemory.detach();
}
void CMainWindowMemReceiver::on_btnBindShareKey_clicked()
{
// 当前key为空进入
if(m_sharedMemory.key().isEmpty())
{
// 获取需要指定的key,并判断key是否为空
QString keyStr= ui->lineEditKey->text();
if(keyStr.isEmpty()) {
QMessageBox::information(this, u8"提示", u8"指定Key为空!");
ui->labelImg->setPixmap(QPixmap());
return;
}
// 设置key
m_sharedMemory.setKey(keyStr);
}
else {
QMessageBox::information(this, u8"提示", u8"已指定Key!");
}
}
CMainWindowMemReceiver.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>CMainWindowMemReceiver</class>
<widget class="QMainWindow" name="CMainWindowMemReceiver">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>417</width>
<height>498</height>
</rect>
</property>
<property name="windowTitle">
<string>CMainWindow</string>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="0" colspan="2">
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab">
<attribute name="title">
<string>文本数据</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QTextEdit" name="textEdit"/>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>图片数据</string>
</attribute>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="labelImg">
<property name="text">
<string/>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="btnShareData">
<property name="text">
<string>共享数据</string>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,1">
<item>
<widget class="QLineEdit" name="lineEditKey">
<property name="placeholderText">
<string>请输入指定的共享内存Key</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btnBindShareKey">
<property name="text">
<string>绑定共享内存Key</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="btnReadData">
<property name="text">
<string>读取数据</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>417</width>
<height>23</height>
</rect>
</property>
</widget>
<widget class="QToolBar" name="mainToolBar">
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
</widget>
<widget class="QStatusBar" name="statusBar"/>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>
总结
在界面操作中,还差一部分的提示/功能逻辑(比如说内存大小在共享数据出现之前就创建了,更好的是共享数据出现时正式创建),有兴趣的小伙伴可自行添加;总的来说,共享内存还是非常实用后续会写其他内存共享方式。
相关文章
Qt之进程通信-IPC(QLocalServer,QLocalSocket 含源码+注释)
Qt之进程通信-QProcess(含源码+注释)
友情提示——哪里看不懂可私哦,让我们一起互相进步吧
(创作不易,请留下一个免费的赞叭 谢谢 ^o^/)
注:文章为作者编程过程中所遇到的问题和总结,内容仅供参考,若有错误欢迎指出。
注:如有侵权,请联系作者删除
文章评论