乐者为王

Do one thing, and do it well.

程序只运行一个实例,并将前一个实例提到前台

wxWidgets提供了一个用来检测是否只有一个实例在运行的wxSingleInstanceChecker类。为了检测程序只运行一个实例,你可以在程序运行之初使用该类创建一个m_check对象,这个对象将存在于程序的整个生命周期。然后就可以在OnInit函数中调用其IsAnotherRunning函数检测是否已经有别的实例在运行。代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
bool MainApp::OnInit()
{
    wxString name = wxString::Format(wxT("MainApp-%s"), wxGetUserId().GetData());
    m_checker = new wxSingleInstanceChecker(name);
    if (m_checker->IsAnotherRunning())
    {
        wxLogError(wxT("Another program instance is already running, aborting."));
        delete m_checker;
        return false;
    }
    // more initializations
    return true;
}

int MainApp::OnExit()
{
    delete m_checker;
    return 0;
}

注意:上面使用了wxGetUserId()来构建实例名,这表示允许不同的用户能同时运行程序的一个实例。如果不这样,那么程序就只能被一个用户运行一次。

但是,如果你想把旧的实例提到前台,或者想使旧的实例打开传递给新的实例的作为命令行参数的文件,该怎么办呢?一般来说,这需要在这两个实例间进行通讯。我们可以使用wxWidgets提供的进程间通讯类来实现。

在下面的实例中,我们将实现程序多个实例间的通讯,以便允许第二个实例请求第一个实例将自己带到前台以提醒用户它已经在运行。下面的代码实现了一个连接类,这个类将被两个实例使用。一个服务器类被旧的实例使用,以便监听新的实例的连接请求。一个客户端类被新的实例使用,以便和旧的实例进行通讯。

1
2
3
4
5
class AppServer : public wxServer
{
public:
    virtual wxConnectionBase* OnAcceptConnection(const wxString& topic);
};
1
2
3
4
5
class AppClient : public wxClient
{
public:
    virtual wxConnectionBase* OnMakeConnection();
};
1
2
3
4
5
6
class AppConnection : public wxConnection
{
public:
    virtual bool OnExecute(const wxString& WXUNUSED(topic), wxChar* WXUNUSED(data),
                          int WXUNUSED(size), wxIPCFormat WXUNUSED(format));
};

当有新的实例(作为Client)进行连接请求时,旧的实例(作为Server)中的OnAcceptConnection函数首先检查旧的实例中没有任何模式对话框。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
wxConnectionBase* AppServer::OnAcceptConnection(const wxString& topic)
{
    if (topic.Lower() == wxT("only-one"))
    {
        wxWindowList::Node* node = wxTopLevelWindows.GetFirst();
        while (node)
        {
            wxDialog* dialog = wxDynamicCast(node->GetData(), wxDialog);
            if (dialog && dialog->IsModal())
            {
                return false;
            }
            node = node->GetNext();
        }
        return new AppConnection();
    }
    else
    {
        return NULL;
    }
}

OnExecute函数是一个回调函数,在新的实例对其连接对象(由AppConnection创建的对象)调用Execute函数时被调用。OnExecute函数可以有一个空的参数,这表示它只要将自己提到前台就可以了。

1
2
3
4
5
6
7
8
9
10
11
bool AppConnection::OnExecute(const wxString& WXUNUSED(topic), wxChar* WXUNUSED(data),
                              int WXUNUSED(size), wxIPCFormat WXUNUSED(format))
{
    wxFrame* frame = wxDynamicCast(wxGetApp().GetTopWindow(), wxFrame);
    if (frame)
    {
        frame->Restore();    // 必须要有这句,不然当主窗口最小化时,就不能被提到前台
        frame->Raise();
    }
    return true;
}

接下来我们还需要修改OnInit()函数。当没有别的实例在运行时,这个实例需要将自己设置为Server,等待别的实例的连接请求,如果已经有实例在运行,那么就创建一个和那个实例的连接,请求那个实例将程序的主窗口提到前台。下面的修改后的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
bool MainApp::OnInit()
{
    wxString name = wxString::Format(wxT("MainApp-%s"), wxGetUserId().GetData());
    m_checker = new wxSingleInstanceChecker(name);
    if (!m_checker->IsAnotherRunning())
    {
        m_server = new AppServer();
        if (!m_server->Create(wxT("wxMainApp")))
        {
            wxLogDebug(wxT("Failed to create an IPC service."));
            return false;
        }
    }
    else
    {
        AppClient* client = new AppClient();
        wxString hostName = wxT("localhost");
        wxConnectionBase* conn = client->MakeConnection(hostName, wxT("wxMainApp"), wxT("only-one"));
        if (conn)
        {
            conn->Execute(wxEmptyString);
            conn->Disconnect();
            delete conn;
        }
        else
        {
            wxString msg = wxT("Sorry, the existing instance may be too busy to respond.\nPlease close any open dialogs and retry.");
            wxMessageBox(msg, wxT("wxMainApp"), wxICON_INFORMATION | wxOK);
        }
        delete client;    // 如果没有这句,在运行Debug版本时就会显示如下图的警告
        return false;
    }
    // more initializations
    return true;
}

释放实例资源

1
2
3
4
5
6
int MainApp::OnExit()
{
    delete m_checker;
    delete m_server;
    return 0;
}

遇到的问题:

1、调用wxGetApp时编译错误

在调用wxGetApp()时可能会有编译错误,提示说“identifier not found”。这可以通过在App类后加上一行DECLARE_APP(XXXApp)来解决。

2、引入ipc.h和wx.h时的顺序问题

运行第二个实例的时候,发现它总是会挂起在MakeConnection处,查看进程可以看到有两个实例在运行。在网上找了n久,只在wxWidgets Forum上发现有提到这个问题(Windows service using wxWidgets ipc),可是也没有提到如何解决。只能靠自己啦,经过对程序的一步步排除,终于发现是因为引入头文件时将ipc.h放在wx.h之前的原因,掉换引入头文件的顺序后问题被解决。

代码下载

Comments