Reputation: 2540
I am trying to write a SingleApplication
class that will only allow one instance of the program to be running. I am implementing this using QSharedMemory
The program works fine, unless I use a key with the value "42"
. Is there something wrong I am doing? Is this undefined behavior?
Main.cpp
int main(int argc, char *argv[])
{
//QApplication a(argc, argv);
SingleApplication a(argc, argv, "42"); //Does not work with '42'. Will work for any other value.
MainWindow w;
w.show();
return a.exec();
}
SingleApplication.h
class SingleApplication : public QApplication
{
Q_OBJECT
public:
SingleApplication(int &argc, char *argv[], const QString uniqueKey);
bool alreadyExists() const{ return bAlreadyExists; }
bool isMasterApp() const { return !alreadyExists(); }
bool sendMessage(const QString &message);
public slots:
//void checkForMessages();
signals:
//void messageAvailable(const QStringList& messages);
private:
bool bAlreadyExists;
QSharedMemory sharedMemory;
};
SingleApplication.cpp
SingleApplication::SingleApplication(int &argc, char *argv[], const QString uniqueKey) : QApplication(argc, argv){
sharedMemory.setKey(uniqueKey);
//Create if one does not exist already
if(sharedMemory.create(5000))
{
qDebug() << "Created!";
bAlreadyExists = false;
}
else{
if(sharedMemory.error() == QSharedMemory::AlreadyExists){
qWarning() << "Program is already running!";
}
}
}
Upvotes: 2
Views: 3300
Reputation: 53215
I would drop the whole own single application concept implemented by you from scratch personally. The qt-solutions repository has contained the QtSingleApplication
class which was ported to Qt 5, too. You ought to be able to use that. There is little to no point in reinventing the wheel in my humble opinion.
Should you still insist on doing it on your own, while your idea seems to be a little strange at first about passing the key to the constructor and not to manage that inside the class transparently, this could be a workaround for your case to make the solution more robust:
SingleApplication::SingleApplication(int &argc, char *argv[], const QString uniqueKey) : QApplication(argc, argv)
{
sharedMemory.setKey(uniqueKey);
if (!sharedMemory.create(5000)) {
while (sharedMemory.error() == QSharedMemory::AlreadyExists) {
// Set a new key after some string manipulation
// This just a silly example to have a placeholder here
// Best would be to increment probably, and you could still use
// a maximum number of iteration just in case.
sharedMemory.setKey(sharedMemory.key() + QLatin1String("0"));
// Try to create it again with the new key
sharedMemory.create(5000);
}
if (sharedMemory.error() != QSharedMemory::NoError)
qDebug() << "Could not create the shared memory:" << sharedMemory.errorString();
else
{
qDebug() << "Created!";
bAlreadyExists = false;
}
}
}
Disclaimer: this is just pseudo code and I have never tested it, actually, not even tried to compile it myself!
Upvotes: 1
Reputation: 9014
I propose you next solution. It's tested, but it's doesn't support sending messages between instances. And it resolves some bugs of your solution. Because it is not enough just to test memory. You need to guard a creation of shared memory.
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 )
{
QSharedMemory fix( sharedmemKey ); // Fix for *nix: http://habrahabr.ru/post/173281/
fix.attach();
}
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();
}
Upvotes: 2