wxWidgets提供了一个用来检测是否只有一个实例(instance)在运行的wxSingleInstanceChecker类。为了检测程序只运行一个实例,你可以在程序运行之初使用该类创建一个m_check对象,这个对象将存在于程序的整个生命周期。然后就可以在OnInit函数中调用其IsAnotherRunning函数检测是否已经有别的实例在运行。代码如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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
36
|
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.n"
)
wxT(
"Please 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之前的原因,掉换引入头文件的顺序后问题被解决。