TAG

首都機能移轉 (2) 歌詞 (2) 靠北文 (40) 戲言 (30) 糟糕 (7) ACG (23) Assembly (2) Boost (2) C (31) C++ (69) CMake (4) CSIE (67) Debian (34) Design_Pattern (2) Django (1) Eclipse (1) en_US (13) FFmpeg (3) FoolproofProject (26) FreeBSD (2) Git (4) GNU_Linux (65) IDE (5) Java (11) JavaScript (19) KDE (15) Khopper (16) KomiX (3) Kubuntu (18) Life (1) Lighttpd (2) Mac_OS_X (2) Opera (1) PHP (2) PicKing (2) Programing (21) Prolog (1) Python (7) QSnapshot (2) Qt (30) Qt_Jambi (1) Regular_Expression (1) Shell_Script (7) Talk (98) VirtualBox (7) Visual_Studio (13) Windows (18) zh_TW (36)

2011年4月23日 星期六

Single Instance of Qt

某些時候你會希望你的程式在同一時間內只會被執行一次,這需要使用 IPC 的技巧實作:第一個被執行的實體先留下某個溝通的管道,第二個以後被執行的實體就去連結這個管道,並自行決定要留下哪個實體。
最方便的方法就是用 Qt Solutions 內的元件:QtSingleApplication。但 Qt 己經不再繼續更新 Qt Solutions 了,且最近我使用它時碰到了一些詭異問題。另外要一提的是,去年我寫的 Export the symbols of QtSingleApplication 一文,現在其取得方式及編譯方式都有變動。由於其細節與本文無關,請自行參照該文。
QtSingleApplication 的使用方式如下:
#include <QtSingleApplication>

int main( int argc, char * argv[] ) {
    QtSingleApplication a( argc, argv );

    if( a.isRunning() ) {
        // popup an error message and exit
        return 1;
    }

    // your application part

    return a.exec();
}
你也可以利用 QtSingleApplication::sendMessage 和 QtSingleApplication::messageReceived 來讓兩個實體間做溝通。
第二種方法是使用 QLocalServer 和 QLocalSocket 實作;事實上這也是目前 QtSingleApplication 內部的實作方式。想法很簡單:先決定一個獨特的關鍵字做為溝通的識別字,以這個識別字先連結到 local server,如果連結失敗即代表還沒人開始這個連結,那麼便自己啟動這個 local server。如此一來接著執行的其他程式都會成功連接到這個 local server,從而得知對方的存在。
使用方式也很簡單:
#include <QtNetwork/QLocalServer>
#include <QtNetwork/QLocalSocket>
#include <QtGui/QApplication>

int main( int argc, char * argv[] ) {
    QApplication a( argc, argv );

    QLocalSocket c;
    c.connectToServer( "KeyWord" );
    if( c.waitForConnected( 100 ) ) {    // 0.1 秒後逾時,因為是連到本地端,其實用不了 0.1 秒那麼久
        // 回傳 true 代表連結成功,己有其他實體存在
        c.disconnectFromServer();
        return 1;
    }

    QLocalServer s;
    while( !s.listen( "KeyWord" ) ) {
        // hack for unix-like system
        if( s.serverError() == QAbstractSocket::AddressInUseError && QLocalServer::removeServer( "KeyWord" ) ) {
            continue;
        }
        // error on listening
        return 1;
    }

    // your application part

    // please call s.close() before application exits.
    return a.exec();
}
這個方法的好處為因為是 Server/Client 架構,可以很方便地和多個程式同時溝通。
但要注意的是 QLocalServer 和 QLocalSocket 使用的實作方式其實是 named pipe,而在 UNIX-link 系統下,若程式沒有清理掉 named pipe 就結束的話,系統不會自動幫你清除,就導致下次執行時可能有錯誤結果。這是為什麼有上面那段醜醜的 code 片段的原因。
而 Windows 的問題則是同一個 named pipe 可以同時有數個程式去監聽它,但因為上述的使用方式在同一時間內只會有一個實體開啟 local server ,所以倒不是什麼大問題。
另外,雖然 named pipe 好像跟網路無關, QLocalServer 和 QLocalSocket 依然被歸類在 QtNetwork 模組內。
第三種方式,就是使用 QSharedMemory。基本上這是一個比較難駕馭的方法,因為並沒有比較有效的方式去偵測是否有新的程序要溝通。簡單的流程如下:使用一個獨特的關鍵字當識別字,向系統要求一塊 shared memory 並寫入值,如果失敗的話就代表己經有其他程序正在使用。但如果要進一步做溝通,接下來的部分就麻煩了。
單純的作法:
#include <QtCore/QSharedMemory>
#include <QtGui/QApplication>

int main( int argc, char * argv[] ) {
    QApplication a( argc, argv );

    QSharedMemory s( "KeyWord" );
    if( s.attach() ) {
        // shared memory already exists
        return 1;
    }
    if( !s.create( 1 ) ) {
        // shared memory creation failed
        return 1;
    }

    // your application part

    return a.exec();
}
因為 QSharedMemory 本身並沒有任何檢查 shared memory 是否有變動的機制,因此若要溝通可能就需要 QTimer 去做定期更新。讀寫時也請記得配合 QSharedMemory::lock 和 QSharedMemory::unlock 使用,否則會有競速問題。
雖然 QSharedMemory 在解構時會幫你釋放 shared memory,但若是因為某些原因而沒有執行到解構子,在 UNIX-like 系統下一樣不會幫你回收,你必須要自行想辦法回收。
以上提到的方法各位可以依自己的需求選用。我個人目前是使用第二種方法,並在連結成立時送出 magic number 來識別是否連接到錯誤的程式。

2 則留言:

  1. 其实libunique不错…还帮你处理了启动反馈等等杂事……

    不过应该和qt不和……

    回覆刪除
  2. well, 畢竟主題是 Qt 囉
    libunique 不只使用了 GLib 還用上了 Gtk+
    也許哪天我想不開去寫個 Gtk+ 應用就會用上了吧

    回覆刪除