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"
QSharedMemoryobject в конструкторе - уничтожить сегмент разделяемой памяти, который сохранился из-за предыдущего сбоя процесса, как в 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_HrunGuard.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 с использованием