Qt 提供了四种进程间通信的方式:
本节我们主要介绍共享内存(shared memory)交互:
Qt 使用QSharedMemory类操作共享内存段。我们可以把QSharedMemory看做一种指针,这种指针指向分配出来的一个共享内存段。而这个共享内存段是由底层的操作系统提供,可以供多个线程或进程使用。因此,QSharedMemory可以看做是专供 Qt 程序访问这个共享内存段的指针。同时,QSharedMemory还提供了单一线程或进程互斥访问某一内存区域的能力。当我们创建了QSharedMemory实例后,可以使用其create()函数请求操作系统分配一个共享内存段。如果创建成功(函数返回true),Qt 会自动将系统分配的共享内存段连接(attach)到本进程。
前面我们说过,IPC 离不开平台特性。作为 IPC 的实现之一的共享内存也遵循这一原则。有关共享内存段,各个平台的实现也有所不同:
//!!! Qt5
class QSharedMemory;
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = 0);
~MainWindow();
private:
QSharedMemory *sharedMemory;
};
头文件中,我们将MainWindow添加一个sharedMemory属性。这就是我们的共享内存段。接下来得实现文件中:
const char *KEY_SHARED_MEMORY = "Shared";
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent),
sharedMemory(new QSharedMemory(KEY_SHARED_MEMORY, this))
{
QWidget *mainWidget = new QWidget(this);
QVBoxLayout *mainLayout = new QVBoxLayout(mainWidget);
setCentralWidget(mainWidget);
QPushButton *saveButton = new QPushButton(tr("Save"), this);
mainLayout->addWidget(saveButton);
QLabel *picLabel = new QLabel(this);
mainLayout->addWidget(picLabel);
QPushButton *loadButton = new QPushButton(tr("Load"), this);
mainLayout->addWidget(loadButton);
构造函数初始化列表中我们将sharedMemory成员变量进行初始化。注意我们给出一个键(Key),前面说过,我们可以把QSharedMemory看做是指向系统共享内存段的指针,而这个键就可以看做指针的名字。多个线程或进程使用同一个共享内存段时,该键值必须相同。接下来是两个按钮和一个标签用于界面显示,这里不再赘述。
下面来看加载图片按钮的实现:
connect(saveButton, &QPushButton::clicked, [=]() {
if (sharedMemory->isAttached()) {
sharedMemory->detach();
}
QString filename = QFileDialog::getOpenFileName(this);
QPixmap pixmap(filename);
picLabel->setPixmap(pixmap);
QBuffer buffer;
QDataStream out(&buffer);
buffer.open(QBuffer::ReadWrite);
out << pixmap;
int size = buffer.size();
if (!sharedMemory->create(size)) {
qDebug() << tr("Create Error: ") << sharedMemory->errorString();
} else {
sharedMemory->lock();
char *to = static_cast<char *>(sharedMemory->data());
const char *from = buffer.data().constData();
memcpy(to, from, qMin(size, sharedMemory->size()));
sharedMemory->unlock();
}
});
点击加载按钮之后,如果sharedMemory已经与某个线程或进程连接,则将其断开(因为我们就要向共享内存段写入内容了)。然后使用QFileDialog选择一张图片,利用QBuffer将图片数据作为char *格式。在即将写入共享内存之前,我们需要请求系统创建一个共享内存段(QSharedMemory::create()函数),创建成功则开始写入共享内存段。需要注意的是,在读取或写入共享内存时,都需要使用QSharedMemory::lock()函数对共享内存段加锁。共享内存段就是一段普通内存,所以我们使用 C 语言标准函数memcpy()复制内存段。不要忘记之前我们对共享内存段加锁,在最后需要将其解锁。
接下来是加载按钮的代码
connect(loadButton, &QPushButton::clicked, [=]() {
if (!sharedMemory->attach()) {
qDebug() << tr("Attach Error: ") << sharedMemory->errorString();
} else {
QBuffer buffer;
QDataStream in(&buffer);
QPixmap pixmap;
sharedMemory->lock();
buffer.setData(static_cast<const char *>(sharedMemory->constData()), sharedMemory->size());
buffer.open(QBuffer::ReadWrite);
in >> pixmap;
sharedMemory->unlock();
sharedMemory->detach();
picLabel->setPixmap(pixmap);
}
});
如果共享内存段已经连接,还是用QBuffer读取二进制数据,然后生成图片。注意我们在操作共享内存段时还是要先加锁再解锁。最后在读取完毕后,将共享内存段断开连接。
注意,如果某个共享内存段不是由 Qt 创建的,我们也是可以在 Qt 应用程序中使用。不过这种情况下我们必须使用QSharedMemory::setNativeKey()来设置共享内存段。使用原始键(native key)时,QSharedMemory::lock()函数就会失效,我们必须自己保护共享内存段不会在多线程或进程访问时出现问题。
IPC 使用共享内存通信是一个很常用的开发方法。多个进程间得通信要比多线程间得通信少一些,不过在某一族的应用情形下,比如 QQ 与 QQ 音乐、QQ 影音等共享用户头像,还是非常有用的。
转载于: https://www.devbean.net/2013/11/qt-study-road-2-ipc/