48 votes

Qt : Meilleure pratique pour une protection d'application à instance unique

QSingleApplication ? QMutex ? QSharedMemory ? Je recherche quelque chose qui fonctionnera sans problème sous Windows, OSX et Linux (Ubuntu). Utiliser Qt 4.7.1

90voto

Dmitry Sazonov Points 2276

Solution simple, qui fait ce que vous voulez. Sans dépendance au réseau (comme QtSingleApplication ) et sans surcharge.

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

2voto

Nejat Points 5967

Vous pouvez utiliser QSharedMemory avec une clé spécifique et vérifier si la mémoire partagée avec cette clé a pu être créée ou non. S'il n'est pas en mesure 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
}

1voto

andreyDev Points 21

Pour les fenêtres:

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

0voto

LtWorf Points 2847

J'utilise cette solution pour le moment.

Cependant, il présente l'inconvénient que le programme ne peut être exécuté qu'une seule fois par l'utilisateur, même s'il se connecte à partir de plusieurs emplacements en même temps.

instance unique.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

instance unique.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;
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X