QT应用单例——qtsingleapplication
背景
实现一个qt程序,保证其运行时单例,即在该程序运行时无法再次启动该程序。
qtsingleapplication项目是qt-solutions仓库的一个子项目,它提供了一个基于QT的支持应用单例的框架。
原理
根据是否是GUI程序,提供了QtSingleCoreApplication和QtSingleApplication,代码都比较简短,下面就粗略地贴上。
- QtSingleCoreApplication
继承自QtSingleApplication,非GUI
class QtSingleCoreApplication : public QCoreApplication
{
Q_OBJECT
public:
// 创建一个QtSingleCoreApplication对象,其应用id默认为QCoreApplication::applicationFilePath()
QtSingleCoreApplication(int &argc, char **argv);
// 创建一个QtSingleCoreApplication对象,其应用id为输入的参数id
QtSingleCoreApplication(const QString &id, int &argc, char **argv);
// 判断是否已经有该程序的一个实例在运行
bool isRunning();
// 返回应用程序标识符。两个进程相同标识符将被视为同一应用程序的实例
QString id() const;
public Q_SLOTS:
// 尝试发送消息给正在运行的程序实例
bool sendMessage(const QString &message, int timeout = 5000);
Q_SIGNALS:
// 当当前实例从该应用程序的另一个实例接收到消息时,会发出此信号。
void messageReceived(const QString &message);
private:
// 用于程序实例之间通信的backend
QtLocalPeer* peer;
};
QtSingleCoreApplication::QtSingleCoreApplication(int &argc, char **argv)
: QCoreApplication(argc, argv)
{
peer = new QtLocalPeer(this);
connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&)));
}
QtSingleCoreApplication::QtSingleCoreApplication(const QString &appId, int &argc, char **argv)
: QCoreApplication(argc, argv)
{
peer = new QtLocalPeer(this, appId);
connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&)));
}
bool QtSingleCoreApplication::isRunning()
{
return peer->isClient();
}
bool QtSingleCoreApplication::sendMessage(const QString &message, int timeout)
{
return peer->sendMessage(message, timeout);
}
QString QtSingleCoreApplication::id() const
{
return peer->applicationId();
}
- QtSingleApplication
继承自QApplication
class QT_QTSINGLEAPPLICATION_EXPORT QtSingleApplication : public QApplication
{
Q_OBJECT
public:
QtSingleApplication(int &argc, char **argv, bool GUIenabled = true);
QtSingleApplication(const QString &id, int &argc, char **argv);
#if QT_VERSION < 0x050000
QtSingleApplication(int &argc, char **argv, Type type);
# if defined(Q_WS_X11)
QtSingleApplication(Display* dpy, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0);
QtSingleApplication(Display *dpy, int &argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE cmap= 0);
QtSingleApplication(Display* dpy, const QString &appId, int argc, char **argv, Qt::HANDLE visual = 0, Qt::HANDLE colormap = 0);
# endif // Q_WS_X11
#endif // QT_VERSION < 0x050000
bool isRunning();
QString id() const;
// 将此应用程序的激活窗口设置为 aw
void setActivationWindow(QWidget* aw, bool activateOnMessage = true);
// 如果已设置,则返回应用程序激活窗口调用setActivationWindow(),否则返回0。
QWidget* activationWindow() const;
// Obsolete:
void initialize(bool dummy = true)
{ isRunning(); Q_UNUSED(dummy) }
public Q_SLOTS:
bool sendMessage(const QString &message, int timeout = 5000);
// 取消最小化、拉起并激活此应用程序的激活窗口。如果未设置激活窗口,则此功能不执行任何操作。
void activateWindow();
Q_SIGNALS:
void messageReceived(const QString &message);
private:
void sysInit(const QString &appId = QString());
QtLocalPeer *peer;
QWidget *actWin;
};
void QtSingleApplication::sysInit(const QString &appId)
{
actWin = 0;
peer = new QtLocalPeer(this, appId);
connect(peer, SIGNAL(messageReceived(const QString&)), SIGNAL(messageReceived(const QString&)));
}
QtSingleApplication::QtSingleApplication(int &argc, char **argv, bool GUIenabled)
: QApplication(argc, argv, GUIenabled)
{
sysInit();
}
QtSingleApplication::QtSingleApplication(const QString &appId, int &argc, char **argv)
: QApplication(argc, argv)
{
sysInit(appId);
}
bool QtSingleApplication::isRunning()
{
return peer->isClient();
}
bool QtSingleApplication::sendMessage(const QString &message, int timeout)
{
return peer->sendMessage(message, timeout);
}
QString QtSingleApplication::id() const
{
return peer->applicationId();
}
void QtSingleApplication::setActivationWindow(QWidget* aw, bool activateOnMessage)
{
actWin = aw;
if (activateOnMessage)
connect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow()));
else
disconnect(peer, SIGNAL(messageReceived(const QString&)), this, SLOT(activateWindow()));
}
QWidget* QtSingleApplication::activationWindow() const
{
return actWin;
}
void QtSingleApplication::activateWindow()
{
if (actWin) {
actWin->setWindowState(actWin->windowState() & ~Qt::WindowMinimized);
actWin->raise();
actWin->activateWindow();
}
}
要理解他们是如何实现程序单例的,核心在了解QtLocalPeer* peer;
这个backend是如何实现程序实例之间的感知和通信。
- QtLocalPeer
class QtLocalPeer : public QObject
{
Q_OBJECT
public:
QtLocalPeer(QObject *parent = 0, const QString &appId = QString());
bool isClient();
bool sendMessage(const QString &message, int timeout);
QString applicationId() const
{ return id; }
Q_SIGNALS:
void messageReceived(const QString &message);
protected Q_SLOTS:
void receiveConnection();
protected:
QString id;
QString socketName;
QLocalServer* server;
QtLP_Private::QtLockedFile lockFile;
private:
static const char* ack;
};
const char* QtLocalPeer::ack = "ack";
QtLocalPeer::QtLocalPeer(QObject* parent, const QString &appId)
: QObject(parent), id(appId)
{
...
server = new QLocalServer(this);
QString lockName = QDir(QDir::tempPath()).absolutePath()
+ QLatin1Char('/') + socketName
+ QLatin1String("-lockfile");
lockFile.setFileName(lockName);
lockFile.open(QIODevice::ReadWrite);
}
bool QtLocalPeer::isClient()
{
if (lockFile.isLocked())
return false;
if (!lockFile.lock(QtLP_Private::QtLockedFile::WriteLock, false))
return true;
bool res = server->listen(socketName);
...
QObject::connect(server, SIGNAL(newConnection()), SLOT(receiveConnection()));
return false;
}
bool QtLocalPeer::sendMessage(const QString &message, int timeout)
{
if (!isClient())
return false;
QLocalSocket socket;
bool connOk = false;
...
socket.connectToServer(socketName);
connOk = socket.waitForConnected(timeout/2);
...
if (!connOk)
return false;
QByteArray uMsg(message.toUtf8());
QDataStream ds(&socket);
ds.writeBytes(uMsg.constData(), uMsg.size());
bool res = socket.waitForBytesWritten(timeout);
if (res) {
res &= socket.waitForReadyRead(timeout); // wait for ack
if (res)
res &= (socket.read(qstrlen(ack)) == ack);
}
return res;
}
void QtLocalPeer::receiveConnection()
{
QLocalSocket* socket = server->nextPendingConnection();
...
QDataStream ds(socket);
QByteArray uMsg;
quint32 remaining;
ds >> remaining;
uMsg.resize(remaining);
...
QString message(QString::fromUtf8(uMsg));
...
emit messageReceived(message); //### (might take a long time to return)
}
为了实现相互感知,使用QtLockedFile
class QT_QTLOCKEDFILE_EXPORT QtLockedFile : public QFile
{
public:
enum LockMode { NoLock = 0, ReadLock, WriteLock };
QtLockedFile();
QtLockedFile(const QString &name);
~QtLockedFile();
bool open(OpenMode mode);
bool lock(LockMode mode, bool block = true);
bool unlock();
bool isLocked() const;
LockMode lockMode() const;
private:
#ifdef Q_OS_WIN
Qt::HANDLE wmutex;
Qt::HANDLE rmutex;
QVector<Qt::HANDLE> rmutexes;
QString mutexname;
Qt::HANDLE getMutexHandle(int idx, bool doCreate);
bool waitMutex(Qt::HANDLE mutex, bool doBlock);
#endif
LockMode m_lock_mode;
};
进一步地,如何实现对文件加锁,Linux和Windows有各自地实现,在此不再赘述。
总结
从上面的代码可以看出有两个关键点:
- 通过文件的读写锁判断是否有其他实例正在运行
- 通过QLocalServer即本地套接字实现实例之间的消息通信
例子
int main(int argc, char **argv)
{
QtSingleApplication app(argc, argv);
if (app.isRunning())
return !app.sendMessage(someDataString);
MyMainWidget mmw;
app.setActivationWindow(&mmw);
mmw.show();
return app.exec();
}
参考
qtsingleapplication