Qt软件做到唯一单例的显示



  • 0_1526438042282_1.png
    使用技术手段让Qt开发的软件做到唯一单例的显示,方法有很多。目前总结起来有几种:

    1. 启动的时候写入一个临时文件,并且在关闭程序的时候删除之。如果有第二个实例启动了,那么一旦检测到文件的存在,就会删除。这个方法是很多软件常用的方法。问题在于,一旦程序异常退出了,临时文件就没法得到删除,导致新的实例无法启动。
    2. 使用QLocalSocket来做。具体就是监听端口,一旦有第二个实例启动的话,那么会显示端口被占用,这样也可以达到单例的显示效果。
    3. 使用共享内存来做,目前的操作暂时不了解

    Qt提供了QtSingleApplication这个类,但是似乎已经失传了?github上或许可以看到一些端倪。不过现在第三方开发者已经将其升级到更加新的版本了,虽然也叫QtSingleApplication,但是要复杂,能够解决不同异常。它的下载地址是:

    https://github.com/itay-grudev/SingleApplication

    首先我们来看看别人是如何使用QtSingleApplication的吧。从网上摘抄一些经典的内容:

    @jiangcaiyangQt软件做到唯一单例的显示 中说:

    心得1:
    单例执行一般有两种需求,1种是在本机上,这个程序最多只能运行一个实例,1种是,本机上该exe文件对应的程序只能运行一个实例,但是如果别的位置还有该exe的实例,另外别的该exe实例也可以运行。如果需求是前者,那么就用一个固定的字符串作为标记,无论本机上有多少个该应用程序副本,同时最多也只能有一个实例在运行。如果是需求2,则使用exe对应的路径作为APP_ID。
    需要说明的是,实现单例运行的逻辑是根据APP_ID,在临时目录下存在一个对应于APP_ID的文件。如果开启了一个实例,然后删除对应的文件,然后就可以再次开启一个实例。
    特别需要说明的是,如果是在linux开发,涉及到局域网内远程计算机的执行该程序,无论是使用固定字符串还是文件路径作为APP_ID做为标记都会导致本机、远程计算机同时只能运行一个实例。但有时需求并不是这样,而是一个终端可以有一个实例运行,那么这时, 把APP_ID的参数加上远程访问的计算机IP信息加上。
    心得2:
    上面的例子简单粗暴的处理,如果已存在运行的实例,就退出。实际应用中往往需要执行的是如果有实例在运行,要么给出提示信息,要么直接把正在运行的实例的窗口显示出来。
    QtSingleApplication提供了一个public slots: bool sendMessage(const QString& message, int timeout = 5000);和一个信号void messageReceived(const QString& message);
    那么逻辑就应该是这样的,
    a. 判断是否有实例在运行 b.如果有,则发送一条信息 c.主窗体有个槽函数与QtSingleApplication的messageReceived的信号连接起来,当有程序运行时发现已经有实例运行时,发动了信息,已运行的实例接收到信息就会发送messageReceived的信号,从而去实行主窗体的槽函数。
    我在具体使用的时候,会把程序运行的路径作为信息传递,这样主窗体的槽函数可以对这个路径参数判断,判断是否和自己所在实例的路径一致,如果一致则主窗体前端显示,否则不一致,说明是别的路径下的执行程序,然后给出提示信息。
    另外,主窗体对应的槽函数如果是简单的show(),主窗体会被激活,并显示出来,但是可能不是会在最前端显示。我一般会临时更改主窗体的windowsflag属性,添加最前端显示的flag,然后显示,然后取消掉最前端显示的flag,然后再显示。这样就会保证窗体是在最前端显示。特别说明的是,取消最前端显示的flag后,必须再执行show,否则窗体会隐藏不显示.代码示例如下:

    void FrmMain::sltMessageReceived(const QString &msg)
    {
    Qt::WindowFlags flags = windowFlags();
    flags |= Qt::WindowStaysOnTopHint;
    setWindowFlags(flags);
    show();
    flags &= ~Qt::WindowStaysOnTopHint;
    setWindowFlags(flags);
    showNormal();
    if(msg != qApp->applicationFilePath()) {
    const QString& warnStr(tr("Only on instance is allowed. Close running application first."));
    QMessageBox::warning(this, tr("Warn"), warnStr, QMessageBox::Ok);
    }
    }
    


  • @jiangcaiyang 有种windows方法

    bool checkOnly(){
         //创建互斥量
        HANDLE m_hMutex = CreateMutex(NULL,FALSE,TEXT("label_233"));
        //检查错误代码
        if(GetLastError() == ERROR_ALREADY_EXISTS){
            //如果已有互斥量存在则释放句柄并复位互斥量
            CloseHandle(m_hMutex);
            m_hMutex = NULL;
            //程序退出
            return false;
        }
        else{
            return true;
        }
    }
    
    int main(int argc, char *argv[])
    {
        QApplication a(argc, argv);
    
        QDir::setCurrent(a.applicationDirPath());
    
        //检查程序是否已经启动过
        if(checkOnly()==false){
            QMessageBox::information(0,"Title","程序已经启动了",QMessageBox::Ok);
            return 0;
        }
    
        Widget w;
        w.show();
    
        return a.exec();
    }
    


  • 这也是一个较好的方法吧,不过Windows平台相关的,不太利于跨平台,所以最好这段代码封装一下。😊 😊 😇



  • dtk中有单例的实现

    /**
     * @brief DApplication::setSingleInstance marks this application to be single instanced.
     * @param key is used as the unique ID of every application.
     *
     * It should be in form of dde-dock, dde-desktop or dde-control-center etc.
     *
     * You can use dbus implement if you use an sandbox like flatpak and so on, just
     * build with DTK_DBUS_SINGLEINSTANCE
     *
     * @return true if succeed, otherwise false.
     */
    bool DApplication::setSingleInstance(const QString &key)
    {
        return setSingleInstance(key, SystemScope);
    }
    
    bool DApplication::setSingleInstance(const QString &key, SingleScope singleScope)
    {
        D_D(DApplication);
    
        QString k = key;
    
    #ifdef Q_OS_UNIX
        switch (singleScope) {
        case DApplication::UserScope:
            k += QString("_%1").arg(getuid());
            break;
        default:
            break;
        }
    #endif
    
    #ifdef Q_OS_UNIX
        if (qgetenv("DTK_USE_DBUS_SINGLEINSTANCE") == "TRUE") {
            return d->setSingleInstanceByDbus(k);
        }
    #endif
        return d->setSingleInstanceBySemaphore(k);
    }
    


  • 
    bool DApplicationPrivate::setSingleInstanceBySemaphore(const QString &key)
    {
        static QSystemSemaphore ss(key, 1, QSystemSemaphore::Open);
        static bool singleInstance = false;
    
        if (singleInstance) {
            return true;
        }
    
        Q_ASSERT_X(ss.error() == QSystemSemaphore::NoError, "DApplicationPrivate::setSingleInstanceBySemaphore:", ss.errorString().toLocal8Bit().constData());
    
        singleInstance = tryAcquireSystemSemaphore(&ss);
    
        if (singleInstance) {
            QtConcurrent::run([this] {
                QPointer<DApplication> that = q_func();
    
                while (ss.acquire() && singleInstance)
                {
                    if (!that) {
                        return;
                    }
    
                    if (that->startingUp() || that->closingDown()) {
                        break;
                    }
    
                    ss.release(1);
    
                    if (that) {
                        Q_EMIT that->newInstanceStarted();
                    }
                }
            });
    
            auto clean_fun = [] {
                ss.release(1);
                singleInstance = false;
            };
    
            qAddPostRoutine(clean_fun);
            atexit(clean_fun);
        }
    
        return singleInstance;
    }
    
    #ifdef Q_OS_UNIX
    /**
    * \brief DApplicationPrivate::setSingleInstanceByDbus will check singleinstance by
    * register dbus service
    * \param key is the last of dbus service name, like "com.deepin.SingleInstance.key"
    * \return
    */
    bool DApplicationPrivate::setSingleInstanceByDbus(const QString &key)
    {
        auto basename = "com.deepin.SingleInstance.";
        QString name = basename + key;
        auto sessionBus = QDBusConnection::sessionBus();
        if (!sessionBus.registerService(name)) {
            qDebug() << "register service failed:" << sessionBus.lastError();
            return  false;
        }
        return true;
    }
    #endif
    
    




  • @大黄老鼠 看来使用其它的技术QDbusConnection来实现的啊。


Log in to reply
 

走马观花

最近的回复

  • Qt for MCUs

    搭建Qt for MCUs PC端开发环境。qt for mcus提供了一个完整的图形框架和工具包,包含了在MCUs上设计、开发和部署gui所需的一切。它允许您在裸机或实时操作系统上运行应用程序。

    先决条件

    开发主机环境支持仅限于Windows 10

    MSVC compiler v19.16 (Visual Studio 2017 15.9.9 or newer) x64

    CMake v3.13 or newer (you can install it using the Qt Online installer) x64

    使用Qt联机安装程序安装Qt for MCUs,该安装程序可通过Qt帐户下载

    安装Qt 5.14和Qt Creator 4.11 or higher

    安装链接

    › Qt: https://account.qt.io/downloads
    › CMake: https://cmake.org/download/
    › Python 2.7 32-bit: https://www.python.org/downloads/release/python-2716/
    › Arm GCC: https://developer.arm.com/tools-and-software/open-source-software/developer-tools/gnutoolchain/gnu-rm/downloads
    › J-Link Software Pack: https://www.segger.com/downloads/jlink/JLink_Windows.exe
    › J-Link OpenSDA Firmware: https://www.segger.com/downloads/jlink/OpenSDA_MIMXRT1050-EVKHyperflash
    › STM32CubeProgrammer: https://www.st.com/en/development-tools/stm32cubeprog.html
    › STM32 ST-LINK Utility: https://www.st.com/en/development-tools/stsw-link004.html​​​​​​​

    Qt Creator设置 启用Qt Creator插件 选择“帮助>关于插件”,然后从列表中选择“MCU支持(实验性)”插件,重新启动Qt Creator以应用更改
    替代文字 为MCU创建Qt工具包

    选择工具>选项>设备>MCU

    选择Qt for MCUs-Desktop 32bpp作为目标

    如果尚未设置,请提供Qt for MCUs安装目录的路径。

    单击Apply应用。

    替代文字

    替代文字
    替代文字

    注意:

    编译器要选X64,Qt版本要选64bit,CMake Tool选x64

    打开恒温器项目demo

    选择文件>打开文件或项目。。。

    打开CMakefiles.txt文件来自thermo文件夹的文件。

    选择Qt作为MCU-桌面32bpp套件。

    单击“配置项目”以完成。

    替代文字

    问题

    开发主机环境支持仅限于Windows 10

    C++编译失败,文本大字体.pixelSize.

    文本类型无法正确呈现需要复杂文本布局的unicode序列。对复杂文本使用StaticText

    read more
  • H

    hi 有问题请教你,方便加个联系方式吗

    read more
  • boost.asio是一个很棒的网络库,这回儿我也开始系统地学习起来了。想想当年接触boost,也有八年多了。这次开始接触boost,觉得既熟悉又陌生。熟悉的是小写字母+下划线的命名方式、晦涩的模板、很慢的编译速度以及较大的程序体积,陌生的是asio的各种概念:io服务、接收器、套接字等等:我之前对网络编程不是非常了解。

    于是根据我的理解,参考《Boost.Asio C++网络编程》实现了这样一个简单的客户端和服务端通信的例子,例子非常简单,还不完善,但是幸运的是,可以在本机上互通了。
    下面是客户端的代码:

    #include <iostream> #include <boost/asio.hpp> #include <boost/proto/detail/ignore_unused.hpp> using namespace std; using namespace boost::asio; using namespace boost::system; using namespace boost::proto::detail;// 提供ignore_unused方法 void writeHandler( const boost::system::error_code& ec, size_t bytesTransferred ) { if ( ec ) { cout << "Write data error, code: " << ec.value( ) << "transferred: " << bytesTransferred << endl; } else { cout << "OK! " << bytesTransferred << "bytes written. " << endl; } } int main(int argc, char *argv[]) { ignore_unused( argc ); ignore_unused( argv ); io_service service; ip::tcp::socket sock( service ); ip::tcp::endpoint ep( ip::address::from_string( "127.0.0.1" ), 6545 ); boost::system::error_code ec; sock.connect( ep, ec ); if ( ec ) { cout << "Connect error, code: " << ec.value( ) << ", We will exit." << endl; return ec.value( ); } else { char buf[1024] = "Hello world!"; sock.async_write_some( buffer( buf ), writeHandler ); sock.close( ); } return service.run( ); }

    下面是服务端的代码:

    #include <iostream> #include <boost/asio.hpp> #include <boost/proto/detail/ignore_unused.hpp> using namespace std; using namespace boost::asio; using namespace boost::system; using namespace boost::proto::detail;// 提供ignore_unused方法 void acceptHandle( const boost::system::error_code& code ) { cout << "Accepted." << endl; } int main(int argc, char *argv[]) { ignore_unused( argc ); ignore_unused( argv ); io_service service; ip::tcp::endpoint ep( ip::address::from_string( "127.0.0.1" ), 6545 ); boost::system::error_code ec; ip::tcp::socket sock( service ); ip::tcp::acceptor acceptor( service, ep ); acceptor.async_accept( sock, acceptHandle ); if ( ec ) { cout << "There is an error in server. code: " << ec.value( ) << endl; } return service.run( );// 阻塞运行 }

    运行结果是这样的:
    78448d7b-b3ae-42fc-9e2e-4dd2fbdac2c2-image.png

    我对boost.asio中几个概念的理解:

    io_service,这就是一个类似事件循环的东西,它为io设备提供服务,故名。不管是套接字、文件还是串口设备,都要使用它的服务。它的run()函数相当于启动了一个事件循环。一旦有消息了,即进行响应。这也是实现异步编程的重要基础。 socket,这个类则是套接字,可以处理TCP或者是UDP请求。有同步以及异步的处理方式,也有带异常以及不带异常的处理方式。 acceptor,接收器,仅仅是服务端使用。相当于其余框架中的listener,作接收用的。

    比较浅显,如果有不当之处,敬请指正。

    read more
  • 843143141.jpg
    闲下来了,我又开始大规模地学习了。
    最近开始学习内存模型和无锁结构。因为这个是和操作系统密切相关的,懂得这些对于编写C++服务端应用程序
    有着非常好的帮助。之前我对内存模型以及无锁结构几乎没有什么了解,我就询问群里的大佬看看有没有可以参考的资料。
    大佬很高兴,并且推荐了我一本名为《Memory Model》的电子书。这本电子书虽然页数不多,但是从起源到发展,
    从源码到汇编,都给我们详细地介绍了。看了一遍,不是非常理解,但是依然尝试将自己的理解写下来,以便日后翻阅。
    首先因为多核处理器成为主流,多线程的程序已经非常常见,因此我们不可避免地要处理多线程程序的同步问题。
    然后,因为编译器默认都对源码进行了优化,在单核处理器中这通常不是什么问题,但是在多核处理器中,就会因为编译器
    对其进行了乱序处理而导致程序出现问题。由此深入地探讨内存模型。
    内存模型主要分为:
    载-载 顺序(load-load order)
    载-存 顺序(load-store order)
    存-载 顺序(store-load order)
    存-存 顺序(store-store order)
    依赖载入顺序(dependent loads order)

    通过内存栅栏(memory barrier)能够避免编译器对指令的乱序。Linux中有

    READ_ONCE( x, value ) WRITE_ONCE( x )

    避免这些读写被编译器乱序或者是优化掉。

    这里谈到volatile关键字。在另外一篇博客上说,volatile具有“易变性、不可优化性、顺序性”。简单说,由于
    被volatile声明的变量,指令须从内存读取,并且不能被编译器乱序以及优化。在Java(语言扩展)和MSVC(系统兼容)上,
    还附带了Accquire()和Release()语义,因此可部分用于多线程环境。但多数情况下,还是慎用volatile,
    因为不同架构的处理器,它的内存模型是千变万化的,不能一而概之。

    至于C++11,它提供了std::atomic<T>这个模板类,相当于提供了很多方式来实现不同内存模型的原子操作。
    它的load()和store()方法,第二个参数有以下几个选项:

    std::memory_order_relaxed std::memory_order_seq_cst std::memory_order_acq_rel std::memory_order_acquire std::memory_order_release std::memory_order_consume

    我们最常用来实现RCpc(Release Consistency、Processor Consistency)是使用

    std::memory_order_acquire std::memory_order_release

    这两对。

    作为例子,在实现自旋锁时使用std::atomic<T>是这样的:

    struct SpinLock2 { void lock( ) { for ( ; ; ) { while ( lock_.load( std::memory_order_relaxed ) ); if ( !lock_.exchange( true, std::memory_order_acquire ) ) break; } } void unlock( ) { lock_.store( false, std::memory_order_release ); } std::atomic<bool> lock_ = { false }; };

    read more

关注我们

微博
QQ群