QSingleApplication
? QMutex
? QSharedMemory
? Я ищу что-то, что будет гладко работать в Windows, OSX и Linux (Ubuntu). Использование Qt 4.7.1
Qt: передовой опыт для защиты приложений с одним экземпляром
Ответы (7)
Простое решение, которое делает то, что вы хотите. Без зависимости от сети (как 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();
}
fix
в конструкторе?
- person bweber; 24.10.2015
QSharedMemory::lock()
вместо отдельного QSystemSemaphore
?
- person bweber; 24.10.2015
QtSingleApplication
может общаться. Или вы можете создать свой механизм на основе QLocalSocket
. 2) Нет, QSharedMemory::lock()
мало, но нужна длинная тема, чтобы описать, почему.
- person Dmitry Sazonov; 26.10.2015
runguard.obj
есть LNK4042: object specified more than once; extras ignored
предупреждение. Любая идея, почему и как его удалить?
- person Mustafa Chelik; 07.09.2016
run qmake
и ребилда пошло предупреждение. Спасибо.
- person Mustafa Chelik; 08.09.2016
Поскольку QtSingleApplication
относительно устарел и больше не поддерживается, я написал замену под названием SingleApplication.
Он основан на QSharedMemory
и использует QLocalServer
для уведомления родительского процесса о порождении нового экземпляра. Он работает на всех платформах и совместим с Qt 5.
Полный код и документация доступны здесь.
Основной пример:
int main(int argc, char *argv[])
{
SingleApplication app( argc, argv );
return app.exec();
}
Расширенный пример
Помимо прочего, он поддерживает отправку сообщений между вновь порожденным экземпляром и основным экземпляром, например:
int main(int argc, char *argv[])
{
SingleApplication app( argc, argv, true );
if( app.isSecondary() ) {
app.sendMessage( app.arguments().join(' ')).toUtf8() );
app.exit( 0 );
}
return app.exec();
}
signal( SIGILL, SingleApplicationPrivate::terminate ); // 4
LOL, ты хоть пробовал? Я полагаю, нет. Эта штука не работает.
- person LtWorf; 09.08.2016
SIGKILL
, но я использовал другой хак, чтобы справиться с этим случаем. Инициализируя, а затем явно удаляя экземпляр QSharedMemory
, ядро очищает блок, если к нему не подключены какие-либо активные процессы.
- person Itay Grudev; 09.08.2016
Вы можете использовать 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
}
для 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;
}
Я использую это решение на данный момент.
Однако у него есть недостаток, заключающийся в том, что программа может быть запущена пользователем только один раз, даже если он входит в систему из нескольких мест одновременно.
одиночный экземпляр.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;
}
для линукса:
//----------------------------------
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);
Согласно документу Qt, полученный QSystemSemaphore
не будет автоматически освобожден, если процесс выйдет из строя без вызова его деструктора в Unix-подобных ОС. Это может быть причиной взаимоблокировки в другом процессе, пытающемся получить тот же семафор. Если вы хотите быть на 100 % уверены, что ваша программа правильно обрабатывает сбои, и если вы не настаиваете на использовании Qt, вы можете захотеть использовать другие механизмы блокировки, которые операционные системы автоматически освобождают при завершении процесса, например, lockf()
и флаг O_EXLOCK
, переданный в open()
, которые упоминаются в Как восстановить семафор, когда процесс, который уменьшил его до нуля, дает сбой? или flock()
. Фактически создание разделяемой памяти больше не требуется, если используется flock()
. Простого использования flock()
достаточно для защиты одного экземпляра приложения.
Если восстановление семафора после сбоев в Unix не имеет значения, я думаю, что RunGuard из ответа Дмитрия Сазонова можно было бы несколько упростить:
Деструкторы
~RunGuard()
иRunGuard::release()
могут быть удалены, так какQSharedMemory
автоматически отсоединится от сегмента разделяемой памяти после его уничтожения, как в документе Qt дляQSharedMemory::~QSharedMemory()
: «Деструктор очищает ключ, что заставляет объект разделяемой памяти отсоединяться от лежащей в его основе разделяемой памяти. сегмент.».RunGuard::isAnotherRunning()
тоже можно снять. Цель - эксклюзивное исполнение. Как уже упоминал @Nejat, мы можем просто воспользоваться тем фактом, что для данного ключа в любое время может быть создано не более одного сегмента общей памяти, как в документе Qt дляQSharedMemory::create()
: «Если сегмент общей памяти идентифицируется ключом уже существует, операция присоединения не выполняется и возвращается false."Если я правильно понимаю, цель «исправить» объект
QSharedMemory
в конструкторе состоит в том, чтобы уничтожить сегмент разделяемой памяти, который выживает из-за сбоя предыдущего процесса, как в документе Qt: «Unix: ... Когда последний поток или процесс, который имеет экземплярQSharedMemory
, присоединенный к конкретному сегменту разделяемой памяти, отсоединяется от сегмента, уничтожая свой экземплярQSharedMemory
, ядро Unix освобождает сегмент разделяемой памяти. выживает в аварии». Когда "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)
находится вне защиты от блокировки)- Another process ProcOther using
RunGuard
starts to run. - ProcOther запускается на
(tag2)
и успешно создает общую память. - ProcOther аварийно завершает работу до вызова
release()
по адресу(tag3)
. - ProcCur продолжает работать с
(tag1)
. - ProcCur переходит к
(tag2)
и пытается создать общую память. ОднакоsharedMem.create()
вернетfalse
, потому что ProcOther оставил созданный. Как мы видим в документеQSharedMemory::create()
: «Если сегмент разделяемой памяти, идентифицированный ключом, уже существует, операция присоединения не выполняется и возвращается false». - Наконец,
RunGuard::tryToRun()
в ProcCur вернетfalse
, что не так, как ожидалось, поскольку ProcCur — единственный существующий процесс, использующийRunGuard
.
- Another process ProcOther using
QSystemSemaphore
используется как мьютекс между процессами. Необходимо защитить от некоторых условий гонки. Это не официант. RunGuard не является потокобезопасным, потому что потокобезопасность здесь не нужна. Но RunGuard должен быть безопасным.
- person Dmitry Sazonov; 28.10.2015
memLock
используется для предотвращения некоторых гонок.
- person Dmitry Sazonov; 28.10.2015
reentrant
означает, что вы можете использовать один экземпляр в разных потоках, но вы должны защищать доступ к объекту с помощью некоторых охранников. Вам необходимо улучшить свои навыки понимания многопоточности и понимания разницы между потоком и процессом.
- person Dmitry Sazonov; 13.03.2016