Qt:单一实例应用程序保护的最佳做法

QSingleApplicationQMutexQSharedMemory ? 我正在寻找能在Windows,OSX和Linux(Ubuntu)上顺利运行的东西。 使用Qt 4.7.1

简单的解决scheme,这就是你想要的。 没有networking依赖(如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(); } 

您可以使用QSharedMemory和特定的密钥,并检查是否可以创build与该密钥的共享内存。 如果它不能创build它,则已经运行一个实例:

 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 } 

由于QtSingleApplication相对过时,不再维护,我写了一个名为SingleApplication的替代品。

它基于QSharedMemory并使用QLocalServer来通知正在产生的新实例的父进程。 它适用于所有平台,并与Qt 5兼容。

完整的代码和文档可在这里find 。

对于Windows:

HANDLE g_app_mutex = NULL;

bool check_one_app_instance(){g_app_mutex = :: CreateMutex(NULL,FALSE,L“8BD290769B404A7816985M9E505CF9AD64”); //这个任何不同的键作为stringif(GetLastError()== ERROR_ALREADY_EXISTS){CloseHandle(g_app_mutex); 返回false; }

 return true; 

}

我现在正在使用这个解决scheme。

但是它的缺点是程序只能由用户运行一次,即使他们同时从多个地点login。

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

对于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); 

根据Qt的文档,如果进程崩溃而不是在类Unix操作系统下调用它的析构函数,那么获得的QSystemSemaphore将不会被自动释放。 这可能是另一个尝试获取相同信号量的进程陷入僵局的原因。 如果你想100%确定你的程序正确地处理了崩溃,并且如果你不坚持使用Qt,你可能需要使用操作系统在进程死亡时自动释放的其他locking机制 – 例如lockf()和传递给open()O_EXLOCK标志在我如何恢复一个信号量的过程中减less到零崩溃? 或flock() 。 实际上,如果使用flock()则不再需要创build共享内存。 只需使用flock()就足以使单实例应用程序保护。

如果从Unix中的崩溃中恢复信号量并不重要,我认为来自Dmitry Sazonov的答案的RunGuard仍然可以被简化:

  1. 析构函数~RunGuard()RunGuard::release()可能会被取消,因为QSharedMemory会在其被销毁时自动从共享内存段中分离出来,就像在Qt的QSharedMemory::~QSharedMemory()的文档中QSharedMemory::~QSharedMemory() :“析构函数清除键,这迫使共享内存对象从其底层共享内存段中分离出来。“

  2. RunGuard::isAnotherRunning()也可能被取消。 目标是独家执行。 正如@Nejat所提到的那样,我们只能利用这个事实,就是在任何时候最多只能为一个给定的键创build一个共享内存段,就像Qt的QSharedMemory::create()的文档:“如果一个共享内存由密钥标识的段已经存在,则不执行附加操作,并返回false。

  3. 如果我理解正确,在构造函数中“修复” QSharedMemory对象的目的是为了销毁由于之前的进程崩溃而存活的共享内存段,如在Qt的doc中:“Unix:…当最后一个线程或进程有一个连接到特定共享内存段的QSharedMemory实例,通过销毁它的实例QSharedMemory ,Unix内核释放共享内存段,但是如果最后一个线程或进程崩溃而没有运行QSharedMemory析构函数,共享内存段幸存下来“。 当“修复”被破坏时,隐含的detach()应该被析构函数调用,而幸存的共享内存段(如果有的话)将被释放。

  4. 不确定QSharedMemory是否是线程安全的/进程安全的。 否则,如果线程安全性由QSharedMemory内部处理, memLock可能会进一步删除与memLock相关的代码。 另一方面,如果安全是一个问题, 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

  5. 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; } 
  6. 这里有一个可能的竞争条件:

     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)在locking保护之外)

    1. 使用RunGuard另一个进程ProcOther开始运行。
    2. ProcOther运行到(tag2) tag2 (tag2)并成功创build共享内存。
    3. (tag3)调用release()之前, ProcOther崩溃。
    4. ProcCur(tag1)继续运行。
    5. ProcCur运行到(tag2) tag2 (tag2)并尝试创build共享内存。 但是, sharedMem.create()将返回false因为ProcOther已经创build了一个。 正如我们在QSharedMemory::create()的文档中所看到的QSharedMemory::create() :“如果由密钥标识的共享内存段已经存在,则不执行附加操作,并返回false。
    6. 最后, RunGuard::tryToRun()将返回false ,这不是预期的,因为ProcCur是使用RunGuard的唯一现有进程。