Qt: Лучшая практика для защиты приложений в одном экземпляре - PullRequest
38 голосов
/ 15 февраля 2011

QSingleApplication? QMutex? QSharedMemory? Я ищу то, что будет работать гладко в Windows, OSX и Linux (Ubuntu). Использование Qt 4.7.1

Ответы [ 7 ]

62 голосов
/ 27 января 2015

Простое решение, которое делает то, что вы хотите. Без сетевой зависимости (как 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 голосов
/ 14 января 2016

Поскольку QtSingleApplication относительно устарел и больше не поддерживается, я написал замену под названием SingleApplication .

Он основан на QSharedMemory и использует QLocalServer для уведомленияродительский процесс нового экземпляра порождается.Он работает на всех платформах и совместим с Qt 5.

Полный код и документация доступны здесь .

2 голосов
/ 22 ноября 2014

Вы можете использовать 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 голосов
/ 25 сентября 2017

для 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);
0 голосов
/ 24 августа 2016

Я сейчас использую это решение.

Однако у него есть недостаток, заключающийся в том, что программа может быть запущена пользователем только один раз, даже если он входит в систему из нескольких мест одновременно.

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 голосов
/ 28 февраля 2016

для 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;
}
0 голосов
/ 25 октября 2015

Согласно документу Qt, полученный QSystemSemaphore не будет автоматически освобожден, если процесс завершится аварийно, без вызова деструктора в Unix-подобных ОС.Что может быть причиной тупика в другом процессе, пытающемся получить тот же семафор.Если вы хотите быть на 100% уверены, что ваша программа правильно обрабатывает сбои, и если вы не настаиваете на использовании Qt, вы можете использовать другие механизмы блокировки, которые операционные системы автоматически отключают, когда процесс умирает - например, lockf() и флаг O_EXLOCK передан open(), которые упоминаются в Как мне восстановить семафор, когда процесс, который уменьшил его до нуля, падает? или flock().Фактически, создание общей памяти больше не требуется, если используется flock().Простого использования flock() достаточно для защиты приложения в одном экземпляре.

Если восстановление семафора после сбоев в Unix не имеет значения, я думаю, что 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() at (tag3).
    4. ProcCur продолжает работать с (tag1).
    5. ProcCur работает с (tag2) и пытается создатьОбщая память.Однако sharedMem.create() вернет false, потому что ProcOther оставил созданный.Как мы видим из документа QSharedMemory::create(): «Если сегмент общей памяти, идентифицируемый ключом, уже существует, операция присоединения не выполняется и возвращается false.»
    6. Наконец, RunGuard::tryToRun() в ProcCur вернет false, что не так, как ожидалось, потому что ProcCur - единственный существующий процесс, использующий RunGuard.
...