当前位置: 首页 > article >正文

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有各自地实现,在此不再赘述。

总结

从上面的代码可以看出有两个关键点:

  1. 通过文件的读写锁判断是否有其他实例正在运行
  2. 通过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


http://www.kler.cn/a/456176.html

相关文章:

  • ASP.NET Core - IStartupFilter 与 IHostingStartup
  • JSON 文本的多层嵌套格式
  • Sprint Boot教程之五十八:动态启动/停止 Kafka 监听器
  • 机器学习06-正则化
  • [Linux]Docker快速上手操作教程
  • (三)c#中const、static、readonly的区别
  • 设计模式之模板方法模式:咖啡,茶,和代码
  • 经典问题——华测
  • OpenCV相机标定与3D重建(37)计算两幅图像之间单应性矩阵(Homography Matrix)的函数findHomography()的使用
  • 【Unity3D】ECS入门学习(十一)ComponentSystem、JobComponentSystem
  • information_schema是什么?
  • Python小括号( )、中括号[ ]和大括号{}代表什么
  • 仓颉语言实战——2.名字、作用域、变量、修饰符
  • 在C#中实现事件的订阅和解除订阅
  • C++ OCR 文字识别
  • Redis——数据淘汰策略
  • 关于启动vue项目,出现:Error [ERR_MODULE_NOT_FOUND]: Cannot find module ‘xxx‘此类错误
  • Java与SQL Server数据库连接的实践与要点
  • web服务器之云主机、物理机租用、服务器托管的区别
  • sql server index
  • SQL 实战:字符串处理函数 – 数据清洗与文本格式化
  • CSS系列(41)-- Logical Properties详解
  • 数据结构课程设计/校园导游程序及通信线路设计 #3
  • 银河麒麟操作系统安装达梦数据库(超详细)
  • 路径规划之启发式算法之二十四:爬山算法(Hill Climbing Algorithm,HCA)
  • 《揭秘Mask R-CNN:开启智能视觉新征程》