QT程序实现单例启动
现实应用中在很多场合下,要求程序只能启动一个实例。比如,Windows PC端只允许启动一个任务管理器。
-
目前使QT运行一个实例有以下几种方式:
-
1.QSharedMemory。
使用共享内存,当第二个进程启动时,判断内存区数据是否建立,如有,则退出; 这种方式有弊端,在程序发生崩溃时,未及时清除共享区数据,导致程序不能正常启动.
-
2.QtSingleApplication。
-
使用QT扩展库QtSingleApplication,能很好的解决这个问题. 详见: http://qt.nokia.com/products/qt-addons/solutions-archive.
但是QtSingleApplication并非QT官方自带的,需要额外自行下载。 -
3.利用QLocalServer实现。
本文将示范这种用法。
-
4.利用文件锁。
在程序运行的时候就在目录下创建一个文件,当程序运行时就判断这个文件是否存在,如果存在说明程序已经在运行。其本质与QSharedMemory相同。在程序发生崩溃时,未及时清除文件,导致程序不能正常启动。
======================================================================================================
本机环境 Win10 + vs2015 + qt 5.6.12.
利用QLocalServer实现单例启动的原理很简单。
当第1次启动程序时,在MyApplication类的构造函数中会尝试以本地客户端去连接一个标记为serverName的localServer。因为此时该localServer并未创建,并不存在,所以连接失败(Can’t connect to server)。于是,新建一个server(build a server),并绑定监听serverName( m_server->listen(serverName) )。此时依然保持有m_isRunning = false,故程序可以启动:
if (app.isRunning())//返回的是m_isRunning = false
{
QMessageBox::critical(nullptr, QString("错误"), "检测到程序已经启动!");
return 0;
}
当第2次启动程序时,标记为serverName的localServer已创建。同样地,在MyApplication类的构造函数中会尝试以本地客户端去连接一个标记为serverName的localServer。显然,连接成功(执行m_isRunning = true),随后直接完成MyApplication的构造函数。
所以在main函数中可以进行单例判定,阻止程序再次启动:
if (app.isRunning())//返回的是m_isRunning = true
{
QMessageBox::critical(nullptr, QString("错误"), "检测到程序已经启动!");
return 0;
}
新建类MyApplication继承于QApplication。
MyApplication.h
#pragma once
#include <QtWidgets/QApplication>
#include <QtNetwork/QLocalServer>
class MyApplication : public QApplication
{
Q_OBJECT
public:
MyApplication(int argc, char **argv);
bool isRunning();
private slots:
void newLocalConnection();
private:
QLocalServer *m_server;
bool m_isRunning;
};
MyApplication.cpp
#include "MyApplication.h"
#include <QtNetwork/QLocalSocket>
#include <QFile>
MyApplication::MyApplication(int argc, char **argv) :QApplication(argc, argv)
{
m_isRunning = false;
QCoreApplication::setApplicationName(QCoreApplication::applicationName()+"-localserver");
QString serverName = QCoreApplication::applicationName();
QLocalSocket socket;
socket.connectToServer(serverName);
if (socket.waitForConnected(500))
{
QTextStream stream(&socket);
QStringList args = QCoreApplication::arguments();
if (args.count()>1)
{
stream << args.last();
}
else
{
stream << QString();
}
stream.flush();
//qDebug() << "Connected server,program will quit";
socket.waitForBytesWritten();
/**
*qApp->quit(); 此代码是用来退出事件循环的;在构造函数中,事件循环
*尚未启动,因此就无法退出. 最好就是设置标志判断在外部判断
*/
m_isRunning = true;
return;
}
//qDebug() << "Can't connect to server,build a server";
m_server = new QLocalServer(this);
connect(m_server, SIGNAL(newConnection()), this, SLOT(newLocalConnection()));
if (m_server->listen(serverName))
{
//防止程序崩溃时,残留进程服务,移除它
if (m_server->serverError() == QAbstractSocket::AddressInUseError&&QFile::exists(m_server->serverName()))
{
QFile::remove(m_server->serverName());
m_server->listen(serverName);
}
}
}
//槽函数
void MyApplication::newLocalConnection()
{
QLocalSocket *socket = m_server->nextPendingConnection();
if (!socket)
return;
socket->waitForReadyRead(1000);
////显示传入参数值
//QTextStream in(socket);
//QString vl;
//in >> vl;
////qDebug() << "The value is: " << vl;
delete socket;
}
bool MyApplication::isRunning()
{
return m_isRunning;
}
在main.cpp中使用的方式
int main(int argc, char *argv[])
{
MyApplication app(argc, argv);
if (app.isRunning())//单例启动
{
QMessageBox::critical(nullptr, QString("错误"), "检测到程序已经启动!");
return 0;
}
MyUI w;
w.show();
return app.exec();
}