QSingleApplication
? QMutex
? QSharedMemory
?我正在寻找能在Windows,OSX和Linux(Ubuntu)中顺利运行的东西。使用Qt 4.7.1
答案 0 :(得分:61)
简单的解决方案,可以满足您的需求。没有网络依赖(如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();
}
答案 1 :(得分:4)
由于QtSingleApplication
相对过时且不再维护,我写了一个名为SingleApplication的替代品。
它基于QSharedMemory
并使用QLocalServer
通知父进程正在生成的新实例。它适用于所有平台,并与Qt 5兼容。
完整的代码和文档可用here。
答案 2 :(得分: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 :(得分:0)
我现在正在使用此解决方案。
然而它的缺点是程序只能由用户运行一次,即使他们同时从多个位置登录。
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;
}
答案 4 :(得分:0)
for 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);
答案 5 :(得分:-1)
根据Qt的文档,如果进程崩溃而没有在类Unix操作系统下调用它的析构函数,那么获得的QSystemSemaphore
将被自动释放。这可能是另一个尝试获取相同信号量的进程中死锁的原因。如果你想100%确定你的程序正确处理崩溃,如果你不坚持使用Qt,
您可能希望使用操作系统在进程终止时自动释放的其他锁定机制 - 例如,lockf()
和O_EXLOCK
标记传递给open()
,这些都在{{{ 3}}或How do I recover a semaphore when the process that decremented it to zero crashes?。实际上,如果使用flock()
,则不再需要创建共享内存。只需使用flock()
即可实现单实例应用程序保护。
如果在Unix中从崩溃中恢复信号量并不重要,我认为flock()
仍然可以稍微简化一下:
析构函数~RunGuard()
和RunGuard::release()
可能会被取消,因为QSharedMemory
会在销毁后自动从共享内存段中分离出来,就像在Qt的doc中一样QSharedMemory::~QSharedMemory()
:&#34;析构函数清除密钥,强制共享内存对象与其底层共享内存段分离。&#34;。
RunGuard::isAnotherRunning()
也可能被取消。目标是独家执行。正如@Nejat所提到的,我们只能利用这样一个事实,即在任何时候都可以为给定密钥创建最多一个共享内存段,如Qt的QSharedMemory::create()
文档:&# 34;如果密钥标识的共享内存段已存在,则不执行附加操作,并返回false。&#34;
如果我理解正确,&#34;修复&#34;的目的构造函数中的QSharedMemory
对象是销毁由于前一个进程崩溃而幸存的共享内存段,如Qt的doc:&#34; Unix:...当最后一个线程或进程时通过销毁QSharedMemory
的实例,附加到特定共享内存段的QSharedMemory
实例与段分离,Unix内核释放共享内存段。但是如果最后一个线程或进程在没有运行QSharedMemory
析构函数的情况下崩溃,那么共享内存段将在崩溃中幸存下来。&#34;。当&#34;修复&#34;如果被破坏,则它的析构函数应该调用一个隐式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 开始运行。(tag2)
并成功创建共享内存。release()
致电(tag3)
之前崩溃。(tag1)
开始运行。(tag2)
并尝试创建共享内存。但是,sharedMem.create()
将返回false
,因为 ProcOther 已经创建了一个。正如我们在QSharedMemory::create()
的文档中看到的那样:&#34;如果由密钥标识的共享内存段已经存在,则不执行附加操作并返回false。&#34; RunGuard::tryToRun()
将返回false
,这不符合预期,因为 ProcCur 是使用{{1>的唯一现有流程}}。答案 6 :(得分:-1)
for 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;
}