QT中同一个程序的可执行程序只能运行一个,如何实现?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了QT中同一个程序的可执行程序只能运行一个,如何实现?相关的知识,希望对你有一定的参考价值。

同一个可执行程序,只能执行一个,当打开第二次时,显示该执行程序已经被打开,并返回。QT下如何实现软件唯一性检查。

在windows下互斥体有个唯一的key,通过key去创建互斥体,如果这个互斥体已经存在了,那么通过GetLastError能获取到这个信息,表示程序已经在运行了。互斥体还有个优点就是在程序关闭时,会自动释放掉!

#include "mainwindow.h"

#include <QApplication>

#include <windows.h>

int main(int argc, char *argv[])
QApplication a(argc, argv);
QString strKey = "mykey";
LPCWSTR wstrKey = strKey.toStdWString().c_str();
HANDLE hMetex = CreateMutex(NULL,FALSE,wstrKey);
if(GetLastError() == ERROR_ALREADY_EXISTS)
//如果互斥体存在,说明程序已经有实例在运行了,释放资源然后关闭本实例
if(hMetex)
CloseHandle(hMetex);
hMetex = NULL;

return -1;

MainWindow w;
w.show();
return a.exec();


参考技术A 您好,非常荣幸能在此回答您的问题。以下是我对此问题的部分见解,若有错误,欢迎指出。这种叫单例程序,实现方式非常多,简单的多就是通过在程序之外做一个标记,启动时检查这个标记,如果有就直接退出程序。
可以使用的方式有:
1、写注册表或者文件 缺点:程序异常退出会导致下次无法启动
2、使用操作系统原子变量,简单安全稳定
3、使用操作系统管道通信
4、重新制作一个专门的启动程序
5.、。。。
推荐第二种,一个函数即可解决问题。非常感谢您的耐心观看,如有帮助请采纳,祝生活愉快!谢谢!
参考技术B #ifdef _WIN32
#include <Windows.h>
#endif

#define DEF_NAME "exe name"
int main(int argc, char *argv[])

QApplication a(argc, argv);

MainWindow w;
#ifdef WIN32
CoInitialize(NULL);
QString name = DEF_NAME;
QSystemSemaphore sema(name,1,QSystemSemaphore::Open);
sema.acquire();// 在临界区操作共享内存 SharedMemory
QSharedMemory mem(QString("SystemObject_%1").arg(DEF_NAME));// 全局对象名
if (!mem.create(1))

// 如果全局对象已存在则退出
HWND hWnd = FindWindowA( "QWidget", DEF_NAME ); //需要调用一下SetWindowTextA(this->winId(),DEF_NAME); //windowsAPI 用于查找窗口
if(hWnd != NULL)

if( IsIconic( hWnd ) ) //是否最小化
ShowWindow( hWnd, SW_RESTORE );
SetForegroundWindow( hWnd );//最上层显示

else
//An instance has already been running.


sema.release();// 如果是 Unix 系统,会自动释放。
return 0;

sema.release();// 临界区
#endif

w.show();

return a.exec();

以上利用共享内存实现。
简单满足一下你的需求,还可以QLocalServer实现。

Qt 编写的程序如何只能运行一个实例

Qt 编写的程序如何只能运行一个实例

最近有个小项目,客户要求程序只能运行一个实例。以前没遇到过这种要求,这次特意花了点时间研究了一下。

大概想了一下,有两种思路。一种是直接去找这个程序已经运行的线索。比如:

  1. 查找系统的进程列表,看看有没有同名的进程已经运行了。有的话说明有其他实例在运行。
  2. 查找有没同名的窗体。有的话说明有其他实例在运行。

另一种思路是在程序中创造一种条件,这个条件可以被其他的实例感知。比如:

  1. 程序启动时新建一个文件,退出时删除这个文件。启动时如果有同名的文件了就说明有实例已经运行了。
  2. 启动时网络通讯监听某个特定的端口,我们知道一个端口只能被一个程序监听。所以如果监听失败了,就说明有其他实例在运行。
  3. 创建个共享内存对象,如果有同名的共享内存对象存在,就无法创建成功,说明有其他实例在运行。

查找同名进程

Qt 没有直接提供读取系统中现有进程的信息的方法。我也没找到有什么第三方库可以跨平台的做这个事情。我现在的办法就是调用 WINDOWS 的 API 去获取这些信息。所以这个代码只针对 WINDOWS 有效,不可移植。

为了方便,我写了一个辅助类: WinProcessInfo

这个类的头文件如下:

#ifndef WINPROCESSINFO_H
#define WINPROCESSINFO_H

#include <Windows.h>
#include <Psapi.h>
#include <QStringList>
#include <QVector>

class WinProcessInfo

public:
    WinProcessInfo();
    static QString PIDtoName(DWORD pid);
    static QStringList listNames(bool removeUnknown = true);
    static QVector<DWORD> listPID();
    static QVector<DWORD> nameToPID(QString name);
;


#endif // WINPROCESSINFO_H

类的实现文件如下:

#include "WinProcessInfo.h"

WinProcessInfo::WinProcessInfo()




QString WinProcessInfo::PIDtoName(DWORD processID)

    TCHAR szProcessName[MAX_PATH] = TEXT("<unknown>");

    // Get a handle to the process.
    HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION |
                                   PROCESS_VM_READ,
                                   FALSE, processID );

    // Get the process name.

    if (NULL != hProcess )
    
        HMODULE hMod;
        DWORD cbNeeded;

        if ( EnumProcessModules( hProcess, &hMod, sizeof(hMod), &cbNeeded) )
        
            GetModuleBaseName( hProcess, hMod, szProcessName, sizeof(szProcessName)/sizeof(TCHAR) );
        
    
    CloseHandle( hProcess );
    return QString::fromWCharArray(szProcessName);


QStringList WinProcessInfo::listNames(bool removeUnknown)

    QStringList names;

    // Get the list of process identifiers.
    DWORD aProcesses[1024], cbNeeded, cProcesses;
    if( !EnumProcesses( aProcesses, sizeof(aProcesses), &cbNeeded )  )
    
        return names;
    
    // Calculate how many process identifiers were returned.
    cProcesses = cbNeeded / sizeof(DWORD);
    for (unsigned int i = 0; i < cProcesses; i++ )
    
        if( aProcesses[i] != 0 )
        
            QString n = PIDtoName(aProcesses[i]);
            if(!removeUnknown || n != "<unknown>")
            
                names.append(n);
            
        
    
    return names;


QVector<DWORD> WinProcessInfo::listPID()

    QVector<DWORD> pids;

    // Get the list of process identifiers.
    DWORD aProcesses[1024], cbNeeded, cProcesses;
    if( !EnumProcesses( aProcesses, sizeof(aProcesses), &cbNeeded )  )
    
        return pids;
    
    // Calculate how many process identifiers were returned.
    cProcesses = cbNeeded / sizeof(DWORD);
    for (unsigned int i = 0; i < cProcesses; i++ )
    
        if( aProcesses[i] != 0 )
        
            pids.append(aProcesses[i]);
        
    
    return pids;


QVector<DWORD> WinProcessInfo::nameToPID(QString name)

    QVector<DWORD> pids;
    // Get the list of process identifiers.
    DWORD aProcesses[1024], cbNeeded, cProcesses;
    if( !EnumProcesses( aProcesses, sizeof(aProcesses), &cbNeeded )  )
    
        return pids;
    
    // Calculate how many process identifiers were returned.
    cProcesses = cbNeeded / sizeof(DWORD);
    for (unsigned int i = 0; i < cProcesses; i++ )
    
        if( aProcesses[i] != 0 )
        
            if(name == PIDtoName(aProcesses[i]))
            
                pids.append(aProcesses[i]);
            
        
    
    return pids;

有了这个类,我们就可以判断当前系统中有几个和我们这个程序同名的程序了。

#include "WinProcessInfo.h"
#include <QFileInfo>
#include <QMessageBox>
int main(int argc, char *argv[])

    QApplication a(argc, argv);
    QString name = QFileInfo(a.applicationFilePath()).fileName();
    if (WinProcessInfo::nameToPID(name).size() > 1)
    
        QMessageBox::information(0, a.applicationName(), u8"另一个程序实例已经在运行中,不能同时运行两个实例!");
        exit(0);
    

    MainWindow w;
    w.show();
    return a.exec();

当然,这个程序其实是有隐患的。如果我们的电脑上有个别的软件,刚好和我们的软件重名。那么这个判断就是错误的了。所以这个方法我不推荐。

查找同名的窗口

这个方法也不能跨平台,下面的代码只针对 WINDOWS 平台。而且如果我们的程序就没有界面,那么这个方法就不适用了。下面是个简单的实现,这个代码还有很多可以优化的地方。这里只是示意性的。


BOOL CALLBACK EnumWindowsProc(
  _In_ HWND   hwnd,
  _In_ LPARAM lParam
)

    if(lParam == 0) return false;
    QStringList * pList = (QStringList *)lParam;
    TCHAR lpString[256];
    if(::GetWindowText(hwnd, lpString, 255))
    
        pList->append(QString::fromWCharArray(lpString));
    
    return true;

QStringList WinProcessInfo::listWindows()

    QStringList list;
    ::EnumWindows(EnumWindowsProc, (LPARAM)&list);
    return list;


bool WinProcessInfo::findWindow(QString name)

    QStringList list = listWindows();
    return list.contains(name);

我们的主程序如下,里面的 XXX 要根据我们的MainWindow 的名字来改:

int main(int argc, char *argv[])

    QApplication a(argc, argv);
    if (WinProcessInfo::findWindow("XXX"))
    
        QMessageBox::information(0, a.applicationName(), u8"另一个程序实例已经在运行中,不能同时运行两个实例!");
        exit(0);
    

    MainWindow w;
    w.show();
    return a.exec();

监控文件

这种方法也很简单。每次程序运行的时候就生成一个文件。如果这个文件生成成功了。就说明没有其他实例在运行。
在程序结束之前把这个文件删除掉。不过如果程序中途宕掉了,加锁文件很可能就没删除,导致这个程序无法运行。因此这种方法不推荐。

下面是个简单的示例:

int main(int argc, char *argv[])

    QApplication a(argc, argv);
    QFile file(a.applicationDirPath() + "/lock");
    if( !file.open(QIODevice::NewOnly) )
    
        QMessageBox::information(0, a.applicationName(), u8"另一个程序实例已经在运行中,不能同时运行两个实例!");
        exit(0);
    

    MainWindow w;
    w.show();


    int ret = a.exec();
    file.remove();
    return ret;

在网络端口监听

TCP 或者 UDP 都可以,UDP 比较简单。这个方法的缺点也很明显。首先必须加入 network 组件。
然后监听的那个端口还要保证其他的程序不会占用。比如下面的程序占用 60000 这个端口。我们只能祈祷这个端口没有其他程序在用。

int main(int argc, char *argv[])

    QApplication a(argc, argv);
    QUdpSocket socket;
    if (!socket.bind(QHostAddress::LocalHost, 60000))
    
        QMessageBox::information(0, a.applicationName(), u8"另一个程序实例已经在运行中,不能同时运行两个实例!");
        exit(0);
    

    MainWindow w;
    w.show();
    return a.exec();

共享内存对象

这种方法网上的代码最多,不过网上好多代码写的都比较麻烦。基本都是用 attach() 函数来检测是否有其他实例在运行了。如果没有的话再用 create() 建立一个共享内存块。实际上 attach() 是多余的,只要 create() 成功了,就说明没有其他实例在运行。这里我给一个最精简的写法。

#include <QSharedMemory>
#include <QMessageBox>
int main(int argc, char *argv[])

    QApplication a(argc, argv);
    QSharedMemory singleton(a.applicationName());
    if (!singleton.create(sizeof(int), QSharedMemory::ReadOnly))
    
        QMessageBox::information(0, a.applicationName(), u8"另一个程序实例已经在运行中,不能同时运行两个实例!");
        exit(0);
    

    MainWindow w;
    w.show();
    return a.exec();

以上是关于QT中同一个程序的可执行程序只能运行一个,如何实现?的主要内容,如果未能解决你的问题,请参考以下文章

如何使 setWindowIcon 在独立的可执行文件(Qt5.14 + VS2019)中正常工作?

如何在一个 qt qml 窗口中运行和显示 4 个可执行文件?

Qt 编写的程序如何只能运行一个实例

Qt 编写的程序如何只能运行一个实例

为 Qt 项目创建可执行文件

Qt 部署的可执行文件打开空白应用程序