Qt软件做到唯一单例的显示



  • 0_1526438042282_1.png
    使用技术手段让Qt开发的软件做到唯一单例的显示,方法有很多。目前总结起来有几种:

    1. 启动的时候写入一个临时文件,并且在关闭程序的时候删除之。如果有第二个实例启动了,那么一旦检测到文件的存在,就会删除。这个方法是很多软件常用的方法。问题在于,一旦程序异常退出了,临时文件就没法得到删除,导致新的实例无法启动。
    2. 使用QLocalSocket来做。具体就是监听端口,一旦有第二个实例启动的话,那么会显示端口被占用,这样也可以达到单例的显示效果。
    3. 使用共享内存来做,目前的操作暂时不了解

    Qt提供了QtSingleApplication这个类,但是似乎已经失传了?github上或许可以看到一些端倪。不过现在第三方开发者已经将其升级到更加新的版本了,虽然也叫QtSingleApplication,但是要复杂,能够解决不同异常。它的下载地址是:

    https://github.com/itay-grudev/SingleApplication

    首先我们来看看别人是如何使用QtSingleApplication的吧。从网上摘抄一些经典的内容:

    @jiangcaiyangQt软件做到唯一单例的显示 中说:

    心得1:
    单例执行一般有两种需求,1种是在本机上,这个程序最多只能运行一个实例,1种是,本机上该exe文件对应的程序只能运行一个实例,但是如果别的位置还有该exe的实例,另外别的该exe实例也可以运行。如果需求是前者,那么就用一个固定的字符串作为标记,无论本机上有多少个该应用程序副本,同时最多也只能有一个实例在运行。如果是需求2,则使用exe对应的路径作为APP_ID。
    需要说明的是,实现单例运行的逻辑是根据APP_ID,在临时目录下存在一个对应于APP_ID的文件。如果开启了一个实例,然后删除对应的文件,然后就可以再次开启一个实例。
    特别需要说明的是,如果是在linux开发,涉及到局域网内远程计算机的执行该程序,无论是使用固定字符串还是文件路径作为APP_ID做为标记都会导致本机、远程计算机同时只能运行一个实例。但有时需求并不是这样,而是一个终端可以有一个实例运行,那么这时, 把APP_ID的参数加上远程访问的计算机IP信息加上。
    心得2:
    上面的例子简单粗暴的处理,如果已存在运行的实例,就退出。实际应用中往往需要执行的是如果有实例在运行,要么给出提示信息,要么直接把正在运行的实例的窗口显示出来。
    QtSingleApplication提供了一个public slots: bool sendMessage(const QString& message, int timeout = 5000);和一个信号void messageReceived(const QString& message);
    那么逻辑就应该是这样的,
    a. 判断是否有实例在运行 b.如果有,则发送一条信息 c.主窗体有个槽函数与QtSingleApplication的messageReceived的信号连接起来,当有程序运行时发现已经有实例运行时,发动了信息,已运行的实例接收到信息就会发送messageReceived的信号,从而去实行主窗体的槽函数。
    我在具体使用的时候,会把程序运行的路径作为信息传递,这样主窗体的槽函数可以对这个路径参数判断,判断是否和自己所在实例的路径一致,如果一致则主窗体前端显示,否则不一致,说明是别的路径下的执行程序,然后给出提示信息。
    另外,主窗体对应的槽函数如果是简单的show(),主窗体会被激活,并显示出来,但是可能不是会在最前端显示。我一般会临时更改主窗体的windowsflag属性,添加最前端显示的flag,然后显示,然后取消掉最前端显示的flag,然后再显示。这样就会保证窗体是在最前端显示。特别说明的是,取消最前端显示的flag后,必须再执行show,否则窗体会隐藏不显示.代码示例如下:

    void FrmMain::sltMessageReceived(const QString &msg)
    {
    Qt::WindowFlags flags = windowFlags();
    flags |= Qt::WindowStaysOnTopHint;
    setWindowFlags(flags);
    show();
    flags &= ~Qt::WindowStaysOnTopHint;
    setWindowFlags(flags);
    showNormal();
    if(msg != qApp->applicationFilePath()) {
    const QString& warnStr(tr("Only on instance is allowed. Close running application first."));
    QMessageBox::warning(this, tr("Warn"), warnStr, QMessageBox::Ok);
    }
    }
    


  • @jiangcaiyang 有种windows方法

    bool checkOnly(){
         //创建互斥量
        HANDLE m_hMutex = CreateMutex(NULL,FALSE,TEXT("label_233"));
        //检查错误代码
        if(GetLastError() == ERROR_ALREADY_EXISTS){
            //如果已有互斥量存在则释放句柄并复位互斥量
            CloseHandle(m_hMutex);
            m_hMutex = NULL;
            //程序退出
            return false;
        }
        else{
            return true;
        }
    }
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
    
        QDir::setCurrent(a.applicationDirPath());
    
        //检查程序是否已经启动过
        if(checkOnly()==false){
            QMessageBox::information(0,"Title","程序已经启动了",QMessageBox::Ok);
            return 0;
        }
    
        Widget w;
        w.show();
    
        return a.exec();
    }
    


  • 这也是一个较好的方法吧,不过Windows平台相关的,不太利于跨平台,所以最好这段代码封装一下。😊 😊 😇



  • dtk中有单例的实现

    /**
     * @brief DApplication::setSingleInstance marks this application to be single instanced.
     * @param key is used as the unique ID of every application.
     *
     * It should be in form of dde-dock, dde-desktop or dde-control-center etc.
     *
     * You can use dbus implement if you use an sandbox like flatpak and so on, just
     * build with DTK_DBUS_SINGLEINSTANCE
     *
     * @return true if succeed, otherwise false.
     */
    bool DApplication::setSingleInstance(const QString &key)
    {
        return setSingleInstance(key, SystemScope);
    }
    
    bool DApplication::setSingleInstance(const QString &key, SingleScope singleScope)
    {
        D_D(DApplication);
    
        QString k = key;
    
    #ifdef Q_OS_UNIX
        switch (singleScope) {
        case DApplication::UserScope:
            k += QString("_%1").arg(getuid());
            break;
        default:
            break;
        }
    #endif
    
    #ifdef Q_OS_UNIX
        if (qgetenv("DTK_USE_DBUS_SINGLEINSTANCE") == "TRUE") {
            return d->setSingleInstanceByDbus(k);
        }
    #endif
        return d->setSingleInstanceBySemaphore(k);
    }
    


  • 
    bool DApplicationPrivate::setSingleInstanceBySemaphore(const QString &key)
    {
        static QSystemSemaphore ss(key, 1, QSystemSemaphore::Open);
        static bool singleInstance = false;
    
        if (singleInstance) {
            return true;
        }
    
        Q_ASSERT_X(ss.error() == QSystemSemaphore::NoError, "DApplicationPrivate::setSingleInstanceBySemaphore:", ss.errorString().toLocal8Bit().constData());
    
        singleInstance = tryAcquireSystemSemaphore(&ss);
    
        if (singleInstance) {
            QtConcurrent::run([this] {
                QPointer<DApplication> that = q_func();
    
                while (ss.acquire() && singleInstance)
                {
                    if (!that) {
                        return;
                    }
    
                    if (that->startingUp() || that->closingDown()) {
                        break;
                    }
    
                    ss.release(1);
    
                    if (that) {
                        Q_EMIT that->newInstanceStarted();
                    }
                }
            });
    
            auto clean_fun = [] {
                ss.release(1);
                singleInstance = false;
            };
    
            qAddPostRoutine(clean_fun);
            atexit(clean_fun);
        }
    
        return singleInstance;
    }
    
    #ifdef Q_OS_UNIX
    /**
    * \brief DApplicationPrivate::setSingleInstanceByDbus will check singleinstance by
    * register dbus service
    * \param key is the last of dbus service name, like "com.deepin.SingleInstance.key"
    * \return
    */
    bool DApplicationPrivate::setSingleInstanceByDbus(const QString &key)
    {
        auto basename = "com.deepin.SingleInstance.";
        QString name = basename + key;
        auto sessionBus = QDBusConnection::sessionBus();
        if (!sessionBus.registerService(name)) {
            qDebug() << "register service failed:" << sessionBus.lastError();
            return  false;
        }
        return true;
    }
    #endif
    
    




  • @大黄老鼠 看来使用其它的技术QDbusConnection来实现的啊。


 

走马观花

最近的回复

关注我们

微博
QQ群











召唤伊斯特瓦尔