QSingleApplication
? QMutex
? QSharedMemory
? Я ищу что-то, что будет работать гладко в Windows, OSX и Linux (Ubuntu). Используя Qt 4.7.1
Qt: Лучшая практика защиты одного экземпляра приложения
Ответ 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
.
- запускается другой процесс ProcOther с использованием