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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/5006547/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 17:09:05  来源:igfitidea点击:

Qt: Best practice for a single instance app protection

c++qtqmutexqsharedmemory

提问by JasonGenX

QSingleApplication? QMutex? QSharedMemory? I'm looking for something that will work smoothly in Windows, OSX and Linux (Ubuntu). Using Qt 4.7.1

QSingleApplication? QMutex? QSharedMemory? 我正在寻找可以在 Windows、OSX 和 Linux (Ubuntu) 中顺利运行的东西。使用 Qt 4.7.1

回答by Dmitry Sazonov

Simple solution, that does what you want. Without network dependency (as QtSingleApplication) and without any overhead.

简单的解决方案,可以满足您的需求。没有网络依赖(as QtSingleApplication)并且没有任何开销。

Usage:

用法:

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();
}

回答by Itay Grudev

As QtSingleApplicationis relatively obsolete and not maintained anymore, I wrote a replacement, called SingleApplication.

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

It is based on QSharedMemoryand uses a QLocalServerto notify the parent process of the new instance being spawn. It works on all platforms and is compatible with Qt 5.

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

The full code and documentation are available here.

完整的代码和文档可在此处获得

回答by Nejat

You can use QSharedMemorywith a specific key and check if the shared memory with that key could be created or not. If it is nor able to create it, then an instance is already run:

您可以使用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
}

回答by andreyDev

for Windows:

对于 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;
}

回答by LtWorf

I am using this solution for now.

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

However it has the drawback that the program can only be run once by the user, even if they login from multiple locations at the same time.

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

singleinstance.h

单实例.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

单实例.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;
}

回答by aminM

for linux:

对于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);
}

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

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

and for Windows:

对于 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);

回答by Justin

According to Qt's doc, an acquired QSystemSemaphorewon't be automatically released if the process crashes without calling its destructor under Unix-like OSes. Which could be the cause of a deadlock in another process trying to acquire the same semaphore. If you want to be 100% sure that your program properly handles crashes and if you don't insist on using Qt, you may want to use the other locking mechanisms which the operating systems do automatically release when the process dies - for example, lockf()and the O_EXLOCKflag passed to open()which are mentioned in How do I recover a semaphore when the process that decremented it to zero crashes?or flock(). In fact, creating of shared memory is no longer needed if flock()is used. Simply using flock()is enough to make single instance app protection.

根据 Qt 的文档,QSystemSemaphore如果在类 Unix 操作系统下进程崩溃而不调用其析构函数,则不会自动释放已获取的对象。这可能是另一个试图获取相同信号量的进程中死锁的原因。如果您想 100% 确定您的程序正确处理崩溃,并且如果您不坚持使用 Qt,您可能需要使用操作系统在进程终止时自动释放的其他锁定机制 - 例如,lockf()当将信号量递减为零的进程崩溃时如何恢复信号量中提到的O_EXLOCK传递给open()它的标志flock()。事实上,如果flock()使用,则不再需要创建共享内存。简单地使用flock()足以使单实例应用程序保护。

If recovering semaphore from crashes in Unix doesn't matter, I think that RunGuard from Dmitry Sazonov's answercould still be somewhat simplified:

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

  1. The destructor ~RunGuard()and RunGuard::release()may be taken off since QSharedMemorywill automatically detach from the shared memory segment upon its destruction, as in Qt's doc for QSharedMemory::~QSharedMemory(): "The destructor clears the key, which forces the shared memory object to detach from its underlying shared memory segment.".

  2. RunGuard::isAnotherRunning()may also be taken off, too. The goal is exclusive execution. As @Nejat has mentioned, we can merely take advantage of the fact there could be at most one shared memory segment being created for a given key at any time, as in Qt's doc for QSharedMemory::create(): "If a shared memory segment identified by the key already exists, the attach operation is not performed and false is returned."

  3. If I understand correctly, the purpose of "fix" QSharedMemoryobject in the constructor is to destroy the shared memory segment which survives due to the previous process crash, as in Qt's doc: "Unix: ... When the last thread or process that has an instance of QSharedMemoryattached to a particular shared memory segment detaches from the segment by destroying its instance of QSharedMemory, the Unix kernel release the shared memory segment. But if that last thread or process crashes without running the QSharedMemorydestructor, the shared memory segment survives the crash.". When "fix" gets destructed, an implicit detach()should be called by its destructor and the surviving shared memory segment, if any, will be released.

  4. Not sure if QSharedMemoryis thread-safe/process-safe or not. Otherwise, the code related to memLockmay be further removed if thread-safety is handled internally by QSharedMemory. On the other hand, fixshould also be protected by memLockif the safety is an issue:

    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();
    }
    

    because an explicit attach()and an implicit detach()are called around fix.

  5. The simplified version of RunGuardis as follows:

    Usage:

    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. There is a possible race condition here:

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

    Consider the scenario:

    When the current process ProcCurruns to (tag1)the following happens: (note that (tag1)is outside of lock protection)

    1. Another process ProcOtherusing RunGuardstarts to run.
    2. ProcOtherruns to (tag2)and successfully creates the shared memory.
    3. ProcOthercrashes before it calls release()at (tag3).
    4. ProcCurcontinues running from (tag1).
    5. ProcCurruns to (tag2)and attempts to create shared memory. However, sharedMem.create()will return falsebecause ProcOtherhave left a created one. As we can see in the doc of QSharedMemory::create(): "If a shared memory segment identified by the key already exists, the attach operation is not performed and false is returned."
    6. Finally, RunGuard::tryToRun()in ProcCurwill return false, which is not as expected because ProcCuris the only existing process using RunGuard.
  1. 析构函数~RunGuard()RunGuard::release()可能会被删除,因为它QSharedMemory会在销毁时自动与共享内存段分离,如 Qt 的文档中所述QSharedMemory::~QSharedMemory():“析构函数清除密钥,这会强制共享内存对象与其底层共享内存段分离。”。

  2. RunGuard::isAnotherRunning()也可能会被脱掉。目标是独占执行。正如@Nejat 所提到的,我们只能利用这样一个事实,即在任何时候都可以为给定的键创建最多一个共享内存段,如 Qt 的文档中所述QSharedMemory::create():“如果由键识别的共享内存段已经存在,则不执行附加操作并返回 false。”

  3. 如果我理解正确,QSharedMemory构造函数中“修复”对象的目的是销毁由于前一个进程崩溃而幸存的共享内存段,如 Qt 的文档:“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)是在锁保护之外)

    1. ProcOther使用的另一个进程RunGuard开始运行。
    2. ProcOther运行(tag2)并成功创建共享内存。
    3. ProcOther崩溃它调用之前release()(tag3)
    4. ProcCur继续从(tag1).
    5. ProcCur运行(tag2)并尝试创建共享内存。但是,sharedMem.create()将返回,false因为ProcOther留下了一个创建的。正如我们在 doc 中看到的QSharedMemory::create():“如果由密钥标识的共享内存段已经存在,则不执行附加操作并返回 false。”
    6. 最后,RunGuard::tryToRun()ProcCur中将返回false,这与预期不同,因为ProcCur是唯一使用 的现有进程RunGuard