QT中实现应用程序的单例化

26 篇文章 4 订阅
8 篇文章 1 订阅

一介绍

通过编写一个QSingleApplication类,来实现Qt程序的单例化,原文的作者是在Windows Vista + Qt4.4 下实现的,不过应用在其他平台上是没问题的。(本文是我在http://www.qtcentre.org/wiki/index.php?title=SingleApplication上看到的)

 

二代码

方案一:使用Qt中的QSharedMemory,QLocalServer和QLocalSocket实现(不过需要在你的.pro里加上QT += network)

别的没翻译,就是大概说了一下,直接来代码吧:

// "single_application.h"

#ifndef SINGLE_APPLICATION_H
#define SINGLE_APPLICATION_H

#include <QApplication>
#include <QSharedMemory>
#include <QLocalServer>

class SingleApplication : public QApplication

{
	Q_OBJECT
public:

	SingleApplication(int &argc, char *argv[], const QString uniqueKey);
	bool isRunning();
	bool sendMessage(const QString &message);
public slots:
	void receiveMessage();
signals:
	void messageAvailable(QString message);
private:
	bool _isRunning;
	QString _uniqueKey;
	QSharedMemory sharedMemory;
	QLocalServer *localServer;
	static const int timeout = 1000;
};

#endif // SINGLE_APPLICATION_H

// "single_application.cpp"
#include <QLocalSocket>
#include "single_application.h"

SingleApplication::SingleApplication(int &argc, char *argv[], const QString uniqueKey) : QApplication(argc, argv), _uniqueKey(uniqueKey)

{
	sharedMemory.setKey(_uniqueKey);
	if (sharedMemory.attach())
		_isRunning = true;
	else
	{
		_isRunning = false;
		// create shared memory.
		if (!sharedMemory.create(1))
		{
			qDebug("Unable to create single instance.");
			return;
		}

		// create local server and listen to incomming messages from other instances.
		localServer = new QLocalServer(this);
		connect(localServer, SIGNAL(newConnection()), this, SLOT(receiveMessage()));
		localServer->listen(_uniqueKey);
	}
}

// public slots.
void SingleApplication::receiveMessage()
{
	QLocalSocket *localSocket = localServer->nextPendingConnection();
	if (!localSocket->waitForReadyRead(timeout))
	{
		qDebug(localSocket->errorString().toLatin1());
		return;
	}

	QByteArray byteArray = localSocket->readAll();
	QString message = QString::fromUtf8(byteArray.constData());
	emit messageAvailable(message);
	localSocket->disconnectFromServer();
}

// public functions.
bool SingleApplication::isRunning()
{
	return _isRunning;
}

bool SingleApplication::sendMessage(const QString &message)
{
	if (!_isRunning)
		return false;
	QLocalSocket localSocket(this);
	localSocket.connectToServer(_uniqueKey, QIODevice::WriteOnly);
	if (!localSocket.waitForConnected(timeout))
	{
		qDebug(localSocket.errorString().toLatin1());
		return false;
	}

	localSocket.write(message.toUtf8());
	if (!localSocket.waitForBytesWritten(timeout))
	{
		qDebug(localSocket.errorString().toLatin1());
		return false;
	}

	localSocket.disconnectFromServer();
	return true;

方案二:使用Qt中的QSharedMemory,和QTimert实现,别的也没翻译,还是直接来代码吧:

// "single_application.h"
#ifndef SINGLE_APPLICATION_H
#define SINGLE_APPLICATION_H

#include <QApplication>
#include <QSharedMemory>

class SingleApplication : public QApplication
{
	Q_OBJECT
public:
	SingleApplication(int &argc, char *argv[], const QString uniqueKey);
	bool isRunning();
	bool sendMessage(const QString &message);
public slots:
	void checkForMessage();
signals:
	void messageAvailable(QString message);
private:
	bool _isRunning;
	QSharedMemory sharedMemory;
};

#endif // SINGLE_APPLICATION_H

// "single_application.cpp"
#include <QTimer>
#include <QByteArray>
#include "single_application.h"

SingleApplication::SingleApplication(int &argc, char *argv[], const QString uniqueKey) : QApplication(argc, argv)
{
	sharedMemory.setKey(uniqueKey);
	if (sharedMemory.attach())
		_isRunning = true;
	else
	{
		_isRunning = false;
		// attach data to shared memory.
		QByteArray byteArray("0"); // default value to note that no message is available.
		if (!sharedMemory.create(byteArray.size()))
		{
			qDebug("Unable to create single instance.");
			return;
		}
		sharedMemory.lock();
		char *to = (char*)sharedMemory.data();
		const char *from = byteArray.data();
		memcpy(to, from, qMin(sharedMemory.size(), byteArray.size()));
		sharedMemory.unlock();

                // start checking for messages of other instances.
		QTimer *timer = new QTimer(this);
		connect(timer, SIGNAL(timeout()), this, SLOT(checkForMessage()));
		timer->start(1000);
	}
}

// public slots.
void SingleApplication::checkForMessage()
{
	sharedMemory.lock();
	QByteArray byteArray = QByteArray((char*)sharedMemory.constData(), sharedMemory.size());
	sharedMemory.unlock();
	if (byteArray.left(1) == "0")
		return;
	byteArray.remove(0, 1);
	QString message = QString::fromUtf8(byteArray.constData());
	emit messageAvailable(message);

        // remove message from shared memory.
	byteArray = "0";
	sharedMemory.lock();
	char *to = (char*)sharedMemory.data();
	const char *from = byteArray.data();
	memcpy(to, from, qMin(sharedMemory.size(), byteArray.size()));
	sharedMemory.unlock();
}

// public functions.
bool SingleApplication::isRunning()
{
	return _isRunning;
}

bool SingleApplication::sendMessage(const QString &message)
{
	if (!_isRunning)
		return false;

	QByteArray byteArray("1");
	byteArray.append(message.toUtf8());
	byteArray.append('/0'); // < should be as char here, not a string!
	sharedMemory.lock();
	char *to = (char*)sharedMemory.data();
	const char *from = byteArray.data();
	memcpy(to, from, qMin(sharedMemory.size(), byteArray.size()));
	sharedMemory.unlock();
	return true;
}

方案三:

这里仅仅提供思路:使用文件锁QLockFile和,QLocalServer来实现。和方案一类似,只是用文件锁QLockFile代替QSharedMemory来确保唯一性。

 

方案四:

如果不需要进程间通信,可以只使用QSharedMemory或QLockFile即可。(真正使用时,c++中建议封装到类中,在析构时确保锁释放,以便下次正常启动)。以QSharedMemory做个简单例子:

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    QSharedMemory *shareMem = new QSharedMemory(QString("SingleInstanceIdentify"));

    /* if the sharedmemory has not been created, it returns false, otherwise true.
     * But if the application exit unexpectedly, the sharedmemory will not detach.
     * So, we try twice.
     */
    volatile short i = 2;
    while (i--)
    {
        if (shareMem->attach(QSharedMemory::ReadOnly)) /* no need to lock, bcs it's read only */
        {
            shareMem->detach();
        }
    }

    if (shareMem->create(1))

    {
        MainWindow w;
        w.show();
        a.exec();
        
        if (shareMem->isAttached())
            shareMem->detach();
        delete shareMem;
    }

    return 0;
}

linux下以文件锁实现单例化应用的简单举例说明(真正使用时,c++中建议封装到类中,在析构时确保锁释放)


#include <string>
#include <iostream>
// linux系统
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/file.h>

/**
 * @brief linux下应用单例化方法之文件锁
 * @param processName 进程名,用于标记唯一
 * @return -1,异常;0,已经有一个实例在运行;1,可以启动新实例
 * @note
 *      此处仅仅介绍文件锁的用法,真实使用时,c++中最好封装到类中,在析构函数中进行解锁,以免系统异常来不及解锁
 */
int isRunning(const std::string &processName)
{
    // 某些场景不用区分用户时,可以不用下面用户信息
    uid_t uid = getuid();

    std::string pid_file = std::string("/run/user/") + std::to_string(uid) + "/" + processName + ".pid";
    /*
    int open(const char *pathname, int oflag, ... );
    对于 open 函数来说,第三个参数(...)仅当创建新文件时才使用,用于指定文件的访问权限位(access permission bits)。
            pathname 是待打开/创建文件的路径名(如 C:/cpp/a.cpp);oflag 用于指定文件的打开/创建模式,这个参数可由以下常量(定义于 fcntl.h)通过逻辑或构成。
    O_RDONLY  只读模式
    O_WRONLY  只写模式
    O_RDWR  读写模式

    打开/创建文件时,至少得使用上述三个常量中的一个。以下常量是选用的:
    O_APPEND  每次写操作都写入文件的末尾
    O_CREAT  如果指定文件不存在,则创建这个文件
    O_EXCL  如果要创建的文件已存在,则返回 -1,并且修改 errno 的值
    O_TRUNC  如果文件存在,并且以只写/读写方式打开,则清空文件全部内容
    O_NOCTTY  如果路径名指向终端设备,不要把这个设备用作控制终端。
    O_NONBLOCK  如果路径名指向 FIFO/块文件/字符文件,则把文件的打开和后继 I/O设置为非阻塞模式(nonblocking mode)

    以下三个常量同样是选用的,它们用于同步输入输出
    O_DSYNC  等待物理 I/O 结束后再 write。在不影响读取新写入的数据的前提下,不等待文件属性更新。
    O_RSYNC  read 等待所有写入同一区域的写操作完成后再进行
    O_SYNC  等待物理 I/O 结束后再 write,包括更新文件属性的 I/O

    open 返回的文件描述符一定是最小的未被使用的描述符。
    */
    int pid_file_fd = open(pid_file.data(), O_CREAT | O_TRUNC | O_RDWR, 0666);
    if (0 > pid_file_fd) {
        std::cerr << "Can not open pid file: " << pid_file << std::endl;
        return -1;
    }

    // 调用系统API:linux系统使用flock函数来获得文件锁;windows系统OpenFile打开文件时设置为独占模式OF_SHARE_DENY_WRITE|OF_SHARE_DENY_READ即可实现
    /*
    头文件  #include<sys/file.h>
    定义函数  int flock(int fd,int operation);
    函数说明  flock()会依参数operation所指定的方式对参数fd所指的文件做各种锁定或解除锁定的动作。此函数只能锁定整个文件,无法锁定文件的某一区域。
    参数  operation有下列四种情况:
        LOCK_SH 建立共享锁定。多个进程可同时对同一个文件作共享锁定。
        LOCK_EX 建立互斥锁定。一个文件同时只有一个互斥锁定。
        LOCK_UN 解除文件锁定状态。
        LOCK_NB 无法建立锁定时,此操作可不被阻断,马上返回进程。通常与LOCK_SH或LOCK_EX 做OR(|)组合。
    单一文件无法同时建立共享锁定和互斥锁定,而当使用dup()或fork()时文件描述词不会继承此种锁定。
    返回值  返回0表示成功,若有错误则返回-1,错误代码存于errno。
    flock只要在打开文件后,需要对文件读写之前flock一下就可以了,用完之后再flock一下,前面加锁(LOCK_EX),后面解锁(LOCK_UN)
    注意:c++中最好封装到类中,在析构函数中进行解锁,以免系统异常来不及解锁
    */
    int file_lock = flock(pid_file_fd, LOCK_EX | LOCK_NB);
    if (0 > file_lock) {
        std::cerr << pid_file << " is running " << std::endl;
        return 0;
    }

    // 将进程id写入文件中
    __pid_t pid = getpid();
    write(pid_file_fd, std::to_string(pid).data(), pid_file.size());
    // fdatasync效率更高
    fsync(pid_file_fd);
    return 1;
}

方案五:

使用命名的互斥量或命名的信号量、事件等来确保应用的唯一性。

互斥量Mutex 是用于同步的线程或进程的互斥体。互斥量Multex 分为两种:未命名的局部互斥体和命名的系统互斥体。前者用于线程间的互斥,后者用于系统进程间的互斥。本文讨论的进程单例的实现将使用“命名的系统互斥体”。

信号量Semaphore 是用于同步的线程或进程的信号量。信号量Semaphore 也分为两种:未命名的局部信号量和命名的系统信号量。前者用于线程间的同步,后者用于系统进程间的同步。本文的进程实例数量的控制的实现将使用“命名的系统信号量”。信号量的一个优点就是可以控制进程实例数量,不只是单例。

方案五中的这几种方法都需要系统api支撑。

linux系统:

/* 
   命名信号量不带内存共享,编译时要带库文件-lpthread或-lrt 
   int sem_wait(sem_t *sem); //P操作,若是信号量大于零则减一,否则阻塞在该函数位置等待. 
   int sem_post(sem_t *sem); //V操作,信号量加一 
   sem_t *sem_open(const char *name, int oflag);//打开信号量,flag参数与打开普通文件的标记一样 
   sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value); 
 //创建并打开信号量,value参数指的是信号量的初始值 
int sem_unlink(const char *name);//删除系统创建的信号量 
int sem_close(sem_t *sem);//关闭彻底销毁信号量 
 */ 

 

windows下使用命名互斥体实现应用单例举例:

/*
创建互斥体实现一个程序只允许允许一个实例运行
*/
 
#include<process.h>
#include<windows.h>
#include<stdio.h>
 
int  main()
{
	HANDLE  hMutex   = CreateMutex(NULL, FALSE, L"我是互斥体");
	if (GetLastError() == ERROR_ALREADY_EXISTS)
	{
		printf("程序已经运行了,退出!\n");
		getchar();
 
		CloseHandle(hMutex);
		return  0;
	}
 
	printf("第一次运行程序!\n");
	getchar();
 
	return 0;
}

 

三使用

// "main.cpp"
#include "single_application.h"
int main(int argc, char *argv[])
{
	SingleApplication app(argc, argv, "some unique key string");
	if (app.isRunning())
	{
		app.sendMessage("message from other instance.");
		return 0;
	}

	MainWindow *mainWindow = new MainWindow();

        // connect message queue to the main window.
	QObject::connect(&app, SIGNAL(messageAvailable(QString)), mainWindow, SLOT(receiveMessage(QString)));

        // show mainwindow.
	mainWindow->show();
	return app.exec();

}

 

参考:

https://www.cnblogs.com/lidabo/p/10248531.html

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值