45

QSingleApplication? QMutex? QSharedMemory? 我正在寻找可以在 Windows、OSX 和 Linux (Ubuntu) 中顺利运行的东西。使用 Qt 4.7.1

4

7 回答 7

83

简单的解决方案,这就是你想要的。没有网络依赖(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();
}
于 2015-01-27T13:53:13.563 回答
8

由于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();
}
于 2016-01-14T20:56:34.110 回答
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
}
于 2014-11-22T06:34:52.093 回答
1

对于 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;
}
于 2016-02-28T12:51:55.890 回答
0

对于 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);
于 2017-09-25T05:09:44.233 回答
0

我现在正在使用这个解决方案。

然而,它的缺点是程序只能由用户运行一次,即使他们同时从多个位置登录也是如此。

单实例.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;
}
于 2016-08-24T07:31:34.083 回答
-1

根据 Qt 的文档,QSystemSemaphore如果进程在未在类 Unix 操作系统下调用其析构函数的情况下崩溃,则不会自动释放获取的内容。这可能是另一个试图获取相同信号量的进程出现死锁的原因。如果您想 100% 确定您的程序正确处理崩溃,并且如果您不坚持使用 Qt,您可能需要使用操作系统在进程终止时自动释放的其他锁定机制 - 例如,lockf()当将信号量减为零的进程崩溃时,如何恢复信号量中提到的O_EXLOCK传递的标志?或。事实上,如果使用,则不再需要创建共享内存。只需使用open()flock()flock()flock()足以使单实例应用程序保护。

如果从 Unix 的崩溃中恢复信号量无关紧要,我认为Dmitry Sazonov 的答案中的 RunGuard仍然可以稍微简化:

  1. 析构函数~RunGuard()andRunGuard::release()可能会被取消,因为QSharedMemory它会在销毁时自动从共享内存段中分离,如 Qt 的文档中的QSharedMemory::~QSharedMemory():“析构函数清除密钥,这会强制共享内存对象与其底层共享内存段分离。”。

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

  3. 如果我理解正确,构造函数中“修复”QSharedMemory对象的目的是破坏由于先前进程崩溃而幸存的共享内存段,如 Qt 的文档中所示:“Unix:...当最后一个线程或进程具有附加到特定共享内存段的实例QSharedMemory通过销毁其实例与段分离QSharedMemory,Unix内核释放共享内存段。但是如果最后一个线程或进程在没有运行QSharedMemory析构函数的情况下崩溃,则共享内存段在崩溃中幸存下来。 ”。detach()当“修复”被破坏时,它的析构函数应该调用一个隐式,并且如果有的话,幸存的共享内存段将被释放。

  4. 不确定是否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

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

    1. ProcOther使用的另一个进程RunGuard开始运行。
    2. ProcOther运行到(tag2)并成功创建共享内存。
    3. ProcOtherrelease()在调用(tag3).
    4. ProcCur继续从 运行(tag1)
    5. ProcCur运行(tag2)并尝试创建共享内存。但是,sharedMem.create()将返回,false因为ProcOther留下了一个已创建的。正如我们在文档中看到的QSharedMemory::create():“如果key标识的共享内存段已经存在,则不执行attach操作,返回false。”
    6. 最后,RunGuard::tryToRun()ProcCur中将返回false,这与预期不同,因为ProcCur是唯一使用RunGuard.
于 2015-10-25T14:01:53.163 回答