0%

QT_(十一)-共享内存

原理

  1. 互斥锁:

    • 创建一个互斥锁,用于控制对共享内存的访问,确保在同一时间只有一个进程或线程能够访问共享内存,从而避免数据竞争和损坏。
  2. 创建共享内存对象:

    • 使用系统调用(如 CreateFileMapping)创建一个共享内存对象。这是一个可由多个进程共享的内存区域,通常基于操作系统的分页文件。
  3. 映射共享内存:

    • 将共享内存对象映射到进程的地址空间,使用 MapViewOfFile 方法。这样,进程就可以通过一个指针(如 buff)来访问这个共享内存区域。
  4. 访问和操作共享内存:

    • 在对共享内存进行任何写入或读取操作之前,首先需要获取互斥锁(锁定),确保没有其他线程或进程正在访问它。
    • 使用 memcpy 或其他内存操作函数将数据写入共享内存。由于使用了互斥锁,数据写入操作是安全的。
  5. 释放资源:

    • 操作完成后,释放互斥锁(解锁),并在不再需要共享内存时解除映射和释放共享内存对象。

Share.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#pragma once

#include <QString>
#include <QMutex>
#include <cstdint> // 对于 uint32_t
#include <string> // 包含 std::wstring

const uint32_t SHARED_MEM_SIZE = 1024 * 768 * 4;
const QString SHARED_MEM_NAME = "SMTLKJ";

class Share
{
public:
Share();
~Share();

public:
bool InitShareMem();
bool DestroyShareMem();

bool LockMem();
bool UnLockMem();

void WriteData(const char* buff, int len);

public:
void* m_hFileMap; // 创建共享内存,成功返回
char* m_pSharedMem; // 映射共享内存的指针,映射到页空间,当前进程可访问

void* m_sharedLock; // 共享内存的互斥锁
bool m_bhasLock; // 判断是否已经上锁
};

Share.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
#include "Share.h"
#include <Windows.h>


Share::Share()
{
m_hFileMap = NULL; //创建共享内存成功返回
m_pSharedMem = NULL; //映射共享内存的指针

m_sharedLock = NULL;
m_bhasLock = false;
}

Share::~Share()
{
UnLockMem();
DestroyShareMem();
}

bool Share::InitShareMem()
{
// 打开一个名为“SMTCIR_Mutex”的互斥体
m_sharedLock = OpenMutex(NULL, false, TEXT("SMTLKJ_Mutex"));
if (m_sharedLock == NULL) // 如果打开失败
{
// 创建一个新的互斥体
m_sharedLock = CreateMutex(NULL, FALSE, TEXT("SMTLKJ_Mutex"));
if (!m_sharedLock)
{
return false; // 创建失败
}
}

void* buff = NULL;
// 创建文件映射对象,共享内存映射到进程空间,使用buff指向它
if (NULL == m_hFileMap)
{
// 创建一个共享内存对象,使用系统分页文件进行存储
m_hFileMap = CreateFileMappingW(
(HANDLE)INVALID_HANDLE_VALUE, // 使用页文件作为共享内存
NULL, // 安全描述符
PAGE_READWRITE, // 读写权限
0, // 最大大小的高位
SHARED_MEM_SIZE, // 最大大小的低位
(LPCWSTR)SHARED_MEM_NAME.utf16() // 共享内存的名称
);

// 共享内存指针,将共享内存映射到进程的地址空间
buff = MapViewOfFile(
m_hFileMap, // 文件映射对象的句柄
FILE_MAP_READ | FILE_MAP_WRITE,// 读写权限
0, 0, 0 // 偏移量和映射大小(这里映射整个共享内存)
);
}
else
{
buff = MapViewOfFile(
m_hFileMap, // 文件映射对象的句柄
FILE_MAP_READ | FILE_MAP_WRITE,// 读写权限
0, 0, 0 // 偏移量和映射大小(这里映射整个共享内存)
);
}

// 检查映射是否成功
if (buff == (void*)-1)
{
// 解除文件映射,关闭内存映射文件对象句柄
::UnmapViewOfFile(buff);
::CloseHandle(m_hFileMap);
m_hFileMap = NULL;
return FALSE;
}

m_pSharedMem = (char*)buff;
return TRUE;
}

bool Share::DestroyShareMem()
{
if (m_pSharedMem)
{
::UnmapViewOfFile(m_pSharedMem);
m_pSharedMem = nullptr;
}
if (m_hFileMap != nullptr)
{
m_hFileMap = nullptr;
}
return TRUE;
}

bool Share::LockMem()
{
// 判断互斥锁是否存在
if (m_sharedLock)
{
DWORD timeout = 1; // 设置超时时间
DWORD waitres;
// 阻塞当前进程,尝试获取互斥锁,并设定超时时间
waitres = WaitForSingleObject(m_sharedLock, timeout);
if (waitres == WAIT_OBJECT_0) // 指定对象的状态已发出信号
{
m_bhasLock = TRUE;
return TRUE;
}
if (waitres == WAIT_TIMEOUT) // 超时间隔已过,并且对象的状态未对齐。
{
m_bhasLock = FALSE;
return FALSE;
}
return TRUE;
}

m_bhasLock = FALSE;
return FALSE;
}

bool Share::UnLockMem()
{
if (m_sharedLock)
{
ReleaseMutex(m_sharedLock);
m_bhasLock = FALSE;
}

return TRUE;
}

void Share::WriteData(const char* buff, int len)
{
memcpy(m_pSharedMem, buff, len);
}

使用例子

MainWindow.cpp

1
2
3
4
5
6
#include "Share.h"

class LKJ : public QWidget
{
Share* m_pShare; // 共享内存
}

构造函数new创建,初始化

1
2
3
4
5
6
LKJ::LKJ(QWidget* parent)
: QWidget(parent)
{
m_pShare = new Share();
m_pShare->InitShareMem();
}

析构函数中销毁

1
2
3
4
5
LKJ::~LKJ()
{
m_pShare->DestroyShareMem();
delete m_pShare;
}

paintEvent实现

主要的内容在paintEvent中,花费的时间有点长,第一次打断点就是空白的,

如果是直接将图片保存为固定模式,如png等,似乎会出现错误,所以我是直接使用image.bits()获得的对应像素点,然后复制的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
void LKJ::paintEvent(QPaintEvent* event)
{
// 创建一个与窗口相同大小的QImage
QImage image(size(), QImage::Format_RGBA8888);
image.fill(Qt::transparent); // 填充透明背景

// 使用 QPainter 在 QImage 上绘制
QPainter painter(&image);
this->render(&painter);

// 获取图像宽度和高度
int width = image.width();
int height = image.height();
std::vector<unsigned char> colorBuff(width * height * 4); // RGBA 每个像素 4 字节 红色 (R)、绿色 (G)、蓝色 (B) 和透明度 (A)

// 第一次就是空白的,之后才是绘制的数据
// 使用 image.bits() 拷贝数据
memcpy(colorBuff.data(), image.bits(), colorBuff.size());

// 处理 colorBuff 数据,比如发送到共享内存
m_pShare->LockMem();
if (m_pShare != NULL && m_pShare->m_bhasLock)
{
bool isAllZero = true;
for (const auto& value : colorBuff)
{
if (value != 0) {
isAllZero = false;
break;
}
}

if (!isAllZero)
{
// 一直在刷新,一次有,一次空。
// 所以判断数据是否为空,当为空时,不写入
m_pShare->WriteData((const char*)(colorBuff.data()), colorBuff.size());
//m_pShare->WriteData((const char*)(colorBuff.data()), colorBuff.size());
//// 创建一个 QImage,用于存储 RGBA 数据
//QImage outputImage(width, height, QImage::Format_RGBA8888);

//// 将 colorBuff 数据复制到 outputImage 中
//memcpy(outputImage.bits(), colorBuff.data(), colorBuff.size());

//// 保存输出图像以验证
//QString outputFilePath = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation) + "/4.png";
//outputImage.save(outputFilePath);
}
}
m_pShare->UnLockMem();
}

-------------本文结束感谢您的阅读-------------