Qt:单实例应用程序保护的最佳实践

问题描述 投票:37回答:7

QSingleApplicationQMutexQSharedMemory?我正在寻找能在Windows,OSX和Linux(Ubuntu)中顺利运行的东西。使用Qt 4.7.1

c++ qt qmutex qsharedmemory
7个回答
62
投票

简单的解决方案,做你想要的。没有网络依赖(如QtSingleApplication),没有任何开销。

用法:

int main()
{
    RunGuard guard( "some_random_key" );
    if ( !guard.tryToRun() )
        return 0;

    QAppplication a(/*...*/);
    // ...
}

RunGuard.h

#ifndef RUNGUARD_H
#define RUNGUARD_H

#include <QObject>
#include <QSharedMemory>
#include <QSystemSemaphore>


class RunGuard
{

public:
    RunGuard( const QString& key );
    ~RunGuard();

    bool isAnotherRunning();
    bool tryToRun();
    void release();

private:
    const QString key;
    const QString memLockKey;
    const QString sharedmemKey;

    QSharedMemory sharedMem;
    QSystemSemaphore memLock;

    Q_DISABLE_COPY( RunGuard )
};


#endif // RUNGUARD_H

RunGuard.cpp

#include "RunGuard.h"

#include <QCryptographicHash>


namespace
{

QString generateKeyHash( const QString& key, const QString& salt )
{
    QByteArray data;

    data.append( key.toUtf8() );
    data.append( salt.toUtf8() );
    data = QCryptographicHash::hash( data, QCryptographicHash::Sha1 ).toHex();

    return data;
}

}


RunGuard::RunGuard( const QString& key )
    : key( key )
    , memLockKey( generateKeyHash( key, "_memLockKey" ) )
    , sharedmemKey( generateKeyHash( key, "_sharedmemKey" ) )
    , sharedMem( sharedmemKey )
    , memLock( memLockKey, 1 )
{
    memLock.acquire();
    {
        QSharedMemory fix( sharedmemKey );    // Fix for *nix: http://habrahabr.ru/post/173281/
        fix.attach();
    }
    memLock.release();
}

RunGuard::~RunGuard()
{
    release();
}

bool RunGuard::isAnotherRunning()
{
    if ( sharedMem.isAttached() )
        return false;

    memLock.acquire();
    const bool isRunning = sharedMem.attach();
    if ( isRunning )
        sharedMem.detach();
    memLock.release();

    return isRunning;
}

bool RunGuard::tryToRun()
{
    if ( isAnotherRunning() )   // Extra check
        return false;

    memLock.acquire();
    const bool result = sharedMem.create( sizeof( quint64 ) );
    memLock.release();
    if ( !result )
    {
        release();
        return false;
    }

    return true;
}

void RunGuard::release()
{
    memLock.acquire();
    if ( sharedMem.isAttached() )
        sharedMem.detach();
    memLock.release();
}

5
投票

由于QtSingleApplication相对过时而且不再维护,我写了一个名为SingleApplication的替代品。

它基于QSharedMemory并使用QLocalServer通知父实例正在生成的新实例。它适用于所有平台,并与Qt 5兼容。

完整的代码和文档可用here


2
投票

您可以将QSharedMemory与特定密钥一起使用,并检查是否可以创建具有该密钥的共享内存。如果它也无法创建它,那么已经运行了一个实例:

QSharedMemory sharedMemory;
sharedMemory.setKey("MyApplicationKey");

if (!sharedMemory.create(1))
{
    QMessageBox::warning(this, tr("Warning!"), tr("An instance of this application is running!") );

    exit(0); // Exit already a process running
}

0
投票

我现在正在使用这个解决方案。

然而,它的缺点是程序只能由用户运行一次,即使它们同时从多个位置登录也是如此。

singleinstance.h

#ifndef SINGLEINSTANCE_H
#define SINGLEINSTANCE_H

typedef enum {
    SYSTEM,
    SESSION,
} scope_t;

class SingleInstance
{
public:
    static bool unique(QString key, scope_t scope);
};

#endif // SINGLEINSTANCE_H

singleinstance.cpp

#include <QLockFile>
#include <QProcessEnvironment>

#include "singleinstance.h"

/**
 * @brief filename
 * @param key
 * @param scope
 * @return a fully qualified filename
 *
 * Generates an appropriate filename for the lock
 */
static QString filename(QString key, scope_t scope) {

    QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
    QString tmp = env.value("TEMP", "/tmp") + "/";
    QString user = env.value("USER", "alfio");


    QString r;                                                                                                                                                                         
    switch (scope) {                                                                                                                                                                   
        case SYSTEM:                                                                                                                                                                   
            r = tmp;                                                                                                                                                                   
            break;
        case SESSION:
            //FIXME this will prevent trabucco to run in multiple X11 sessions
            r = env.value("XDG_RUNTIME_DIR", tmp + user) + "/";
            break;
    }
    return r + key + ".lock";
}

/**
 * @brief SingleInstance::unique
 * @param key the unique name of the program
 * @param scope wether it needs to be system-wide or session-wide
 * @return true if this is the only instance
 *
 * Make sure that this instance is unique.
 */
bool SingleInstance::unique(QString key, scope_t scope) {
    QLockFile* lock = new QLockFile(filename(key, scope));
    bool r = lock->tryLock();
    if (!r)
        delete lock;
    return r;
}

0
投票

对于linux:

//----------------------------------

QProcess *m_prSystemCall;
m_prSystemCall = new QProcess();

QString Commnd = "pgrep  " + qApp->applicationDisplayName();
m_prSystemCall->start(Commnd);
m_prSystemCall->waitForFinished(8000);
QString output(m_prSystemCall->readAllStandardOutput());
QStringList AppList = output.split("\n", QString::SkipEmptyParts);
qDebug() <<"pgrep out:"<<AppList;
for(int i=0;i<AppList.size()-1;i++)
{
    Commnd = "kill " + AppList.at(i);
    m_prSystemCall->start(Commnd);
    m_prSystemCall->waitForFinished(8000);
}

//-------------------------------------------------------

对于Windows:

#include <tlhelp32.h>
#include <comdef.h>

QString pName = qApp->applicationDisplayName();
pName += ".exe";
PROCESSENTRY32 entry;
entry.dwSize = sizeof(PROCESSENTRY32);

HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);

if (Process32First(snapshot, &entry) == TRUE)
{
    DWORD myPID =  GetCurrentProcessId();
    while (Process32Next(snapshot, &entry) == TRUE)
    {
        const WCHAR* wc = entry.szExeFile ;
        _bstr_t b(wc);
        const char* c = b;

        if (stricmp(c, pName.toStdString().c_str()) == 0)
        {
            HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, entry.th32ProcessID);

            qDebug() <<"myPID: "<< myPID << "entry.th32ProcessID" << entry.th32ProcessID;
            if(myPID != entry.th32ProcessID)
                TerminateProcess(hProcess,0);
            QThread::msleep(10);
            CloseHandle(hProcess);
        }
    }

}

CloseHandle(snapshot);

-1
投票

根据Qt的文档,如果进程崩溃而没有在类Unix操作系统下调用它的析构函数,那么获取的QSystemSemaphore将不会被自动释放。这可能是另一个尝试获取相同信号量的进程中死锁的原因。如果你想100%确定你的程序正确处理崩溃,如果你不坚持使用Qt,你可能想要使用操作系统在进程终止时自动释放的其他锁定机制 - 例如,lockf()O_EXLOCK国旗传递给open(),在How do I recover a semaphore when the process that decremented it to zero crashes?flock()中提到。实际上,如果使用flock(),则不再需要创建共享内存。简单地使用flock()足以实现单实例应用程序保护。

如果从Unix中的崩溃恢复信号量无关紧要,我认为RunGuard from Dmitry Sazonov's answer仍然可以稍微简化一下:

  1. 析构函数~RunGuard()RunGuard::release()可能会被取消,因为QSharedMemory会在它被破坏时自动从共享内存段中分离出来,就像在Qt的QSharedMemory::~QSharedMemory()文档中那样:“析构函数清除密钥,强制共享内存对象与其底层共享内存分离分割。”。
  2. RunGuard::isAnotherRunning()也可能被取消。目标是独家执行。正如@Nejat所提到的,我们只能利用以下事实:在任何时候都可以为给定密钥创建最多一个共享内存段,如Qt的QSharedMemory::create()文档:“如果由密钥标识的共享内存段已存在,不执行附加操作,返回false。“
  3. 如果我理解正确,在构造函数中“修复”QSharedMemory对象的目的是破坏由于前一个进程崩溃而幸存的共享内存段,如Qt的doc:“Unix:...当最后一个线程或进程时有一个连接到特定共享内存段的QSharedMemory实例通过销毁它的QSharedMemory实例而脱离该段,Unix内核释放共享内存段。但是如果最后一个线程或进程崩溃而没有运行QSharedMemory析构函数,那么共享内存段幸免于难。“当“修复”被破坏时,应该通过其析构函数调用隐式detach(),并释放幸存的共享内存段(如果有)。
  4. 不确定QSharedMemory是否是线程安全/过程安全的。否则,如果memLock在内部处理线程安全性,则可以进一步删除与QSharedMemory相关的代码。另一方面,如果安全问题,fix也应该受到memLock的保护: RunGuard::RunGuard( const QString& key ) : key( key ) , memLockKey( generateKeyHash( key, "_memLockKey" ) ) , sharedMemKey( generateKeyHash( key, "_sharedMemKey" ) ) , sharedMem( sharedMemKey ) , memLock( memLockKey, 1 ) { memLock.acquire(); { QSharedMemory fix( sharedMemKey ); // Fix for *nix: http://habrahabr.ru/post/173281/ fix.attach(); } memLock.release(); } 因为在attach()周围有一个明确的detach()和一个隐含的fix
  5. RunGuard的简化版如下: 用法: int main() { RunGuard guard( "some_random_key" ); if ( !guard.tryToRun() ) return 0; QAppplication a(/*...*/); // ... } runGuard.h: #ifndef RUNGUARD_H #define RUNGUARD_H #include <QObject> #include <QSharedMemory> #include <QSystemSemaphore> class RunGuard { public: RunGuard( const QString& key ); bool tryToRun(); private: const QString key; const QString memLockKey; const QString sharedMemKey; QSharedMemory sharedMem; QSystemSemaphore memLock; Q_DISABLE_COPY( RunGuard ) }; #endif // RUNGUARD_H runGuard.cpp: #include "runGuard.h" #include <QCryptographicHash> namespace { QString generateKeyHash( const QString& key, const QString& salt ) { QByteArray data; data.append( key.toUtf8() ); data.append( salt.toUtf8() ); data = QCryptographicHash::hash( data, QCryptographicHash::Sha1 ).toHex(); return data; } } RunGuard::RunGuard( const QString& key ) : key( key ) , memLockKey( generateKeyHash( key, "_memLockKey" ) ) , sharedMemKey( generateKeyHash( key, "_sharedMemKey" ) ) , sharedMem( sharedMemKey ) , memLock( memLockKey, 1 ) { QSharedMemory fix( sharedMemKey ); // Fix for *nix: http://habrahabr.ru/post/173281/ fix.attach(); } bool RunGuard::tryToRun() { memLock.acquire(); const bool result = sharedMem.create( sizeof( quint64 ) ); memLock.release(); if ( !result ) return false; return true; }
  6. 这里有可能的竞争条件: bool RunGuard::tryToRun() { if ( isAnotherRunning() ) // Extra check return false; // (tag1) memLock.acquire(); const bool result = sharedMem.create( sizeof( quint64 ) ); // (tag2) memLock.release(); if ( !result ) { release(); // (tag3) return false; } return true; } 考虑一下场景: 当前过程ProcCur运行到(tag1)时会发生以下情况:(注意(tag1)不在锁定保护范围内) 使用RunGuard的另一个过程ProcOther开始运行。 ProcOther运行到(tag2)并成功创建共享内存。 ProcOther在release()调用(tag3)之前崩溃了。 ProcCur继续从(tag1)运行。 ProcCur运行到(tag2)并尝试创建共享内存。然而,sharedMem.create()将返回false,因为ProcOther已经离开了创建的。正如我们在QSharedMemory::create()的文档中看到的那样:“如果密钥识别的共享内存段已经存在,则不执行附加操作,并返回false。” 最后,ProcCur中的RunGuard::tryToRun()将返回false,这不是预期的,因为ProcCur是使用RunGuard的唯一现有流程。

-1
投票

对于Windows:

HANDLE g_app_mutex = NULL;

bool check_one_app_instance()
{
    g_app_mutex = ::CreateMutex(NULL, FALSE, L"8BD290769B404A7816985M9E505CF9AD64"); // this any different key as string
    if(GetLastError() == ERROR_ALREADY_EXISTS)
    {
        CloseHandle(g_app_mutex);
        return false;
    }

    return true;
}
© www.soinside.com 2019 - 2024. All rights reserved.