Qt: les Meilleures pratiques pour une seule instance de l'application de la protection

QSingleApplication ? QMutex ? QSharedMemory ? Je suis à la recherche de quelque chose qui va fonctionner en douceur sous Windows, OSX et Linux (Ubuntu). En Utilisant Qt 4.7.1

34
demandé sur Nejat 2011-02-15 19:40:26
la source

7 ответов

solution Simple, qui fait ce que vous voulez. Sans dépendance de réseau (comme QtSingleApplication ) et sans frais généraux.

Utilisation:

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();
}
54
répondu Dmitry Sazonov 2015-10-29 14:11:53
la source

Vous pouvez utiliser QSharedMemory avec une clé spécifique et vérifier si la mémoire partagée avec cette clé pourrait être créée ou non. Si elle n'est pas capable de le créer, alors une instance est déjà exécutée:

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
répondu Nejat 2018-09-05 14:13:54
la source

Comme QtSingleApplication est relativement obsolète et n'est plus maintenu, j'ai écrit un remplacement, appelé SingleApplication .

il est basé sur QSharedMemory et utilise un QLocalServer pour notifier le processus parent de la nouvelle instance étant spawn. Il fonctionne sur toutes les plateformes et est compatible avec Qt 5.

le code complet et la documentation sont disponibles ici .

2
répondu Itay Grudev 2016-07-15 14:05:02
la source

j'utilise cette solution pour le moment.

il a Cependant l'inconvénient que le programme ne peut être exécuté qu'une seule fois par l'utilisateur, même si ils se connecter à partir de plusieurs endroits en même temps.

par exemple.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

par exemple.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
répondu LtWorf 2016-08-24 10:31:34
la source

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

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

et pour 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
répondu aminM 2017-09-25 08:09:44
la source

pour 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
répondu andreyDev 2018-09-05 14:20:02
la source

selon le doc de Qt, un acquis QSystemSemaphore ne sera pas automatiquement libéré si le processus s'effondre sans appeler son destructeur sous des os de type Unix. Ce qui pourrait être la cause d'une impasse dans un autre processus d'essayer d'acquérir le même sémaphore. Si vous voulez être sûr à 100% que votre programme gère correctement les collisions et si vous n'insistez pas sur l'utilisation de Qt, vous pouvez vouloir utiliser les autres mécanismes de verrouillage que les systèmes d'exploitation libèrent automatiquement lorsque le processus dies-par exemple, lockf() et le drapeau O_EXLOCK passé à open() qui sont mentionnés dans Comment puis-je récupérer un sémaphore lorsque le processus qui l'a décrété à zéro se brise? ou flock() . En fait, la création de la mémoire partagée n'est plus nécessaire si flock() est utilisé. Il suffit d'utiliser flock() pour créer une protection d'application à une seule instance.

Si Récupération du Sémaphore d'un crash dans Unix peu importe, je pense que RunGuard de la réponse de Dmitry Sazonov pourrait encore être quelque peu simplifié:

  1. Le destructeur ~RunGuard() et RunGuard::release() peut être décollé depuis QSharedMemory va automatiquement se détacher du segment de mémoire partagée au moment de sa destruction, comme dans la doc de Qt pour QSharedMemory::~QSharedMemory() : "Le destructeur efface la clé, ce qui force la mémoire partagée de l'objet à se détacher de ses sous-jacent partagé segment de mémoire.".

  2. RunGuard::isAnotherRunning() peut aussi être décollé. L'objectif est d'exécution exclusif. Comme @Nejat l'a mentionné, nous pouvons simplement profiter du fait qu'il pourrait y avoir tout au plus un segment de mémoire partagée créé pour une clé donnée à tout moment, comme dans le doc de Qt pour QSharedMemory::create() : "si un segment de mémoire partagée identifié par la clé existe déjà, l'opération d'attache n'est pas effectuée et false est retourné."

  3. si je comprends bien, le but de "fix" QSharedMemory objet dans le constructeur est de détruire le segment de mémoire partagée qui survit en raison de la précédente panne de processus, comme dans le doc de Qt: "Unix: ... Lorsque le dernier thread ou processus qui a une instance de QSharedMemory attachée à un segment de mémoire partagée particulier se détache du segment en détruisant son instance de QSharedMemory , le noyau Unix libère le segment de mémoire partagée. Mais si cette dernière fil ou processus s'écrase sans l'exécution du destructeur QSharedMemory , le segment de mémoire partagée survit au crash.". Lorsque "fix" est détruit, un detach() implicite doit être appelé par son destructeur et le segment de mémoire partagée survivant, le cas échéant, sera libéré.

  4. Je ne sais pas si QSharedMemory est sûr ou non. Dans le cas contraire, le code memLock peut être retiré si la sécurité du fil est assurée. intérieurement par QSharedMemory . D'autre part, fix devrait également être protégé par memLock si la sécurité est un problème:

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

    parce qu'un attach() explicite et un detach() implicite sont appelés autour de fix .

  5. la version simplifiée de RunGuard est la suivante:

    Utilisation:

    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. il y a une condition de race possible ici:

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

    envisager le scénario:

    quand le processus actuel ProcCur va à (tag1) les cas suivants se produisent: (notez que (tag1) est à l'extérieur de la protection de la serrure)

    1. un Autre processus ProcOther utilisant RunGuard commence à courir.
    2. ProcOther s'exécute à (tag2) et crée avec succès la mémoire partagée.
    3. ProcOther se bloque avant qu'il appelle release() à (tag3) .
    4. ProcCur continue à partir de (tag1) .
    5. ProcCur fonctionne (tag2) et tente de créer une mémoire partagée. Cependant, sharedMem.create() retournera false parce que ProcOther ont laissé un créé. Comme nous pouvons le voir dans le doc de QSharedMemory::create() : "si un segment de mémoire partagée identifié par la clé existe déjà, l'opération d'attache n'est pas effectuée et false est retourné."
    6. Enfin, RunGuard::tryToRun() dans ProcCur sera de retour false , ce qui n'est pas comme prévu, car le Le procédé est le seul procédé existant utilisant le procédé RunGuard .
-1
répondu Justin 2016-07-15 15:06:56
la source

Autres questions sur