QSingleApplication
? QMutex
? QSharedMemory
? 我正在寻找可以在 Windows、OSX 和 Linux (Ubuntu) 中顺利运行的东西。使用 Qt 4.7.1
7 回答
简单的解决方案,这就是你想要的。没有网络依赖(as 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
并使用 aQLocalServer
来通知父进程新实例正在生成。它适用于所有平台,并且兼容支持 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();
}
您可以使用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;
}
对于 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);
我现在正在使用这个解决方案。
然而,它的缺点是程序只能由用户运行一次,即使他们同时从多个位置登录也是如此。
单实例.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
单实例.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;
}
根据 Qt 的文档,QSystemSemaphore
如果进程在未在类 Unix 操作系统下调用其析构函数的情况下崩溃,则不会自动释放获取的内容。这可能是另一个试图获取相同信号量的进程出现死锁的原因。如果您想 100% 确定您的程序正确处理崩溃,并且如果您不坚持使用 Qt,您可能需要使用操作系统在进程终止时自动释放的其他锁定机制 - 例如,lockf()
和当将信号量减为零的进程崩溃时,如何恢复信号量中提到的O_EXLOCK
传递的标志?或。事实上,如果使用,则不再需要创建共享内存。只需使用open()
flock()
flock()
flock()
足以使单实例应用程序保护。
如果从 Unix 的崩溃中恢复信号量无关紧要,我认为Dmitry Sazonov 的答案中的 RunGuard仍然可以稍微简化:
析构函数
~RunGuard()
andRunGuard::release()
可能会被取消,因为QSharedMemory
它会在销毁时自动从共享内存段中分离,如 Qt 的文档中的QSharedMemory::~QSharedMemory()
:“析构函数清除密钥,这会强制共享内存对象与其底层共享内存段分离。”。RunGuard::isAnotherRunning()
也可以脱掉。目标是独家执行。正如@Nejat 所提到的,我们只能利用这样一个事实,即在任何时候都可以为给定的键创建最多一个共享内存段,如 Qt 的文档中所示QSharedMemory::create()
:“如果一个由键标识的共享内存段已经存在,则不执行附加操作并返回false。”如果我理解正确,构造函数中“修复”
QSharedMemory
对象的目的是破坏由于先前进程崩溃而幸存的共享内存段,如 Qt 的文档中所示:“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)
是在锁保护之外)- ProcOther使用的另一个进程
RunGuard
开始运行。 - ProcOther运行到
(tag2)
并成功创建共享内存。 - ProcOther
release()
在调用(tag3)
. - ProcCur继续从 运行
(tag1)
。 - ProcCur运行
(tag2)
并尝试创建共享内存。但是,sharedMem.create()
将返回,false
因为ProcOther留下了一个已创建的。正如我们在文档中看到的QSharedMemory::create()
:“如果key标识的共享内存段已经存在,则不执行attach操作,返回false。” - 最后,
RunGuard::tryToRun()
在ProcCur中将返回false
,这与预期不同,因为ProcCur是唯一使用RunGuard
.
- ProcOther使用的另一个进程