QSingleApplication
? QMutex
? QSharedMemory
?我正在寻找能在Windows,OSX和Linux(Ubuntu)中顺利运行的东西。使用Qt 4.7.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();
}
由于QtSingleApplication
相对过时而且不再维护,我写了一个名为SingleApplication的替代品。
它基于QSharedMemory
并使用QLocalServer
通知父实例正在生成的新实例。它适用于所有平台,并与Qt 5兼容。
完整的代码和文档可用here。
您可以将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
}
我现在正在使用这个解决方案。
然而,它的缺点是程序只能由用户运行一次,即使它们同时从多个位置登录也是如此。
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,你可能想要使用操作系统在进程终止时自动释放的其他锁定机制 - 例如,lockf()
和O_EXLOCK
国旗传递给open()
,在How do I recover a semaphore when the process that decremented it to zero crashes?或flock()
中提到。实际上,如果使用flock()
,则不再需要创建共享内存。简单地使用flock()
足以实现单实例应用程序保护。
如果从Unix中的崩溃恢复信号量无关紧要,我认为RunGuard from Dmitry Sazonov's answer仍然可以稍微简化一下:
~RunGuard()
和RunGuard::release()
可能会被取消,因为QSharedMemory
会在它被破坏时自动从共享内存段中分离出来,就像在Qt的QSharedMemory::~QSharedMemory()
文档中那样:“析构函数清除密钥,强制共享内存对象与其底层共享内存分离分割。”。RunGuard::isAnotherRunning()
也可能被取消。目标是独家执行。正如@Nejat所提到的,我们只能利用以下事实:在任何时候都可以为给定密钥创建最多一个共享内存段,如Qt的QSharedMemory::create()
文档:“如果由密钥标识的共享内存段已存在,不执行附加操作,返回false。“QSharedMemory
对象的目的是破坏由于前一个进程崩溃而幸存的共享内存段,如Qt的doc:“Unix:...当最后一个线程或进程时有一个连接到特定共享内存段的QSharedMemory
实例通过销毁它的QSharedMemory
实例而脱离该段,Unix内核释放共享内存段。但是如果最后一个线程或进程崩溃而没有运行QSharedMemory
析构函数,那么共享内存段幸免于难。“当“修复”被破坏时,应该通过其析构函数调用隐式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)
不在锁定保护范围内)
使用RunGuard
的另一个过程ProcOther开始运行。
ProcOther运行到(tag2)
并成功创建共享内存。
ProcOther在release()
调用(tag3)
之前崩溃了。
ProcCur继续从(tag1)
运行。
ProcCur运行到(tag2)
并尝试创建共享内存。然而,sharedMem.create()
将返回false
,因为ProcOther已经离开了创建的。正如我们在QSharedMemory::create()
的文档中看到的那样:“如果密钥识别的共享内存段已经存在,则不执行附加操作,并返回false。”
最后,ProcCur中的RunGuard::tryToRun()
将返回false
,这不是预期的,因为ProcCur是使用RunGuard
的唯一现有流程。对于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;
}