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

时间:2011-02-15 16:40:26

标签: c++ qt qmutex qsharedmemory

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

7 个答案:

答案 0 :(得分:61)

简单的解决方案,可以满足您的需求。没有网络依赖(如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();
}

答案 1 :(得分:4)

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

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

完整的代码和文档可用here

答案 2 :(得分: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
}

答案 3 :(得分: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;
}

答案 4 :(得分:0)

for 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);

答案 5 :(得分:-1)

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

如果在Unix中从崩溃中恢复信号量并不重要,我认为flock()仍然可以稍微简化一下:

  1. 析构函数~RunGuard()RunGuard::release()可能会被取消,因为QSharedMemory会在销毁后自动从共享内存段中分离出来,就像在Qt的doc中一样QSharedMemory::~QSharedMemory():&#34;析构函数清除密钥,强制共享内存对象与其底层共享内存段分离。&#34;。

  2. RunGuard::isAnotherRunning()也可能被取消。目标是独家执行。正如@Nejat所提到的,我们只能利用这样一个事实,即在任何时候都可以为给定密钥创建最多一个共享内存段,如Qt的QSharedMemory::create()文档:&# 34;如果密钥标识的共享内存段已存在,则不执行附加操作,并返回false。&#34;

  3. 如果我理解正确,&#34;修复&#34;的目的构造函数中的QSharedMemory对象是销毁由于前一个进程崩溃而幸存的共享内存段,如Qt的doc:&#34; Unix:...当最后一个线程或进程时通过销毁QSharedMemory的实例,附加到特定共享内存段的QSharedMemory实例与段分离,Unix内核释放共享内存段。但是如果最后一个线程或进程在没有运行QSharedMemory析构函数的情况下崩溃,那么共享内存段将在崩溃中幸存下来。&#34;。当&#34;修复&#34;如果被破坏,则它的析构函数应该调用一个隐式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)不在锁定保护范围内)

    1. 使用RunGuard的另一个进程 ProcOther 开始运行。
    2. ProcOther 运行至(tag2)并成功创建共享内存。
    3. ProcOther release()致电(tag3)之前崩溃。
    4. ProcCur 继续从(tag1)开始运行。
    5. ProcCur 运行到(tag2)并尝试创建共享内存。但是,sharedMem.create()将返回false,因为 ProcOther 已经创建了一个。正如我们在QSharedMemory::create()的文档中看到的那样:&#34;如果由密钥标识的共享内存段已经存在,则不执行附加操作并返回false。&#34;
    6. 最后, ProcCur 中的RunGuard::tryToRun()将返回false,这不符合预期,因为 ProcCur 是使用{{1>的唯一现有流程}}。

答案 6 :(得分:-1)

for 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;
}