Подтвердить что ты не робот

Qt: Лучшая практика защиты одного экземпляра приложения

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

4b9b3361

Ответ 1

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

Ответ 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

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

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

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

Ответ 4

для Windows:

РУЧКА g_app_mutex = NULL;

bool check_one_app_instance() {   g_app_mutex =:: CreateMutex (NULL, FALSE, L "8BD290769B404A7816985M9E505CF9AD64" );//это любой другой ключ как строка   if (GetLastError() == ERROR_ALREADY_EXISTS)   {       CloseHandle (g_app_mutex);       return false;   }

return true;

}

Ответ 5

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

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

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

Ответ 6

для 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);

Ответ 7

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

Если восстановление семафора от сбоев в Unix не имеет значения, я думаю, что RunGuard от ответа Дмитрия Сазонова может быть несколько упрощен:

  • Деструктор ~RunGuard() и RunGuard::release() может быть снят, так как QSharedMemory автоматически отсоединяется от сегмента разделяемой памяти при его уничтожении, как в Qt doc для QSharedMemory::~QSharedMemory(): "Деструктор очищает ключ, который заставляет объект разделяемой памяти отсоединяться от основного сегмента разделяемой памяти.".

  • RunGuard::isAnotherRunning() также может быть снят. Целью является эксклюзивное исполнение. Как отметил @Nejat, мы можем просто воспользоваться тем фактом, что в любой момент может быть создано не более одного сегмента разделяемой памяти для данного ключа, как в Qt doc для QSharedMemory::create(): "Если сегмент разделяемой памяти, идентифицированный ключ уже существует, операция attach не выполняется и возвращается false."

  • Если я правильно понимаю, цель "fix" QSharedMemory object в конструкторе - уничтожить сегмент разделяемой памяти, который сохранился из-за предыдущего сбоя процесса, как в Qt doc: "Unix:.. Когда последний поток или процесс, имеющий экземпляр QSharedMemory, прикрепленный к определенному сегменту разделяемой памяти, отделяется от сегмента, уничтожая его экземпляр QSharedMemory, ядро ​​Unix выпускает сегмент разделяемой памяти. Но если этот последний поток или сбой процесса без запуска деструктора QSharedMemory, сегмент разделяемой памяти выживает при аварии". Когда "fix" получает разрушение, неявный detach() должен быть вызван его деструктором, а сегмент оставшихся разделяемых разделов, если он есть, будет выпущен.

  • Не уверен, что 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.

  • Упрощенная версия 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;
    }
    
  • Здесь есть возможное состояние гонки:

    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) находится вне защиты блокировки)

    • запускается другой процесс ProcOther с использованием RunGuard.
    • ProcOther работает до (tag2) и успешно создает общую память.
    • Ошибка ProcOther перед вызовом release() в (tag3).
    • ProcCur продолжает работать с (tag1).
    • ProcCur работает до (tag2) и пытается создать общую память. Однако sharedMem.create() вернет false, потому что ProcOther оставил созданную. Как мы видим в документе QSharedMemory::create(): "Если сегмент разделяемой памяти, идентифицированный ключом, уже существует, операция attach не выполняется и возвращается false."
    • Наконец, RunGuard::tryToRun() в ProcCur вернет false, что не так ожидалось, потому что ProcCur является единственным существующим процессом, использующим RunGuard.