在Windows平台上开发应用程序时,经常需要实现一些后台运行、定时执行任务或保持单实例的应用。通常情况下,这需要创建一个Windows服务来实现。然而,Windows服务涉及到服务账户、权限管理等一系列复杂的问题,而且对于UI(用户界面)交互较多的应用来说,使用服务并不是一个理想的选择。本文将介绍一种无需创建Windows服务,即可实现类似功能的方法。
本项目的基本组件包括:
创建一个新的Windows项目(New Project... Windows)。
创建一个定时器。拖放一个定时器对象,设置参数,创建一个定时器处理程序。项目可能不需要定时器,没关系,如果一个事件触发窗口显示/隐藏,那就足够了。
创建一个“强制关闭”机制,创建了一个名为"CanClose"的复选框。
private void TheMessage_FormClosing(object sender, FormClosingEventArgs e)
{
e.Cancel = !CanClose.Checked;
this.Hide();
}
创建一个单独的ForceClose()方法,设置CanClose并调用Close。
public void ForceClose()
{
CanClose.Checked = true;
Close();
}
套接字和线程。这是最有趣的部分。所以希望应用程序的行为是单实例的。有几种好方法可以做到这一点,CodeProject上的互斥体就很好。但是,还需要它能够干净地安装/卸载,这意味着它需要强制自己关闭。为此,使用了专用端口上的套接字监听器/客户端。听起来违反直觉,但首先创建了客户端(是鸡还是蛋?!)
public void Main(string[] args)
{
bool killStream = args.Length > 0 && args[0] == "-kill";
try
{
using (TcpClient clnt = new TcpClient())
{
clnt.Connect("localhost", 7231);
if (clnt.Connected)
{
clnt.GetStream().Write(new byte[] { (byte)(killStream ? 0 : 1) }, 0, 1);
// 0 - kill or 1 - show
clnt.Close();
return;
}
}
}
catch (SocketException) { }
// nothing listening, not already running
if (killStream)
// if it's a kill... don't start
return;
}
最后是监听器...
TcpListener lstn = new TcpListener(System.Net.IPAddress.Parse("127.0.0.1"), 7231);
lstn.Start();
// So, we start a long loop listening for incoming messages
while (true)
{
using (TcpClient clnt = lstn.AcceptTcpClient())
{
byte[] buff = new byte[1];
if (1 == clnt.GetStream().Read(buff, 0, 1))
{
switch (buff[0])
{
case 0:
// Kill request
msgWindow.ForceClose();
return;
case 1:
// Show request
msgWindow.Show();
continue;
}
}
}
}
最后,需要一个InnoSetup脚本,在尝试重新安装/卸载之前调用它,如下所示...
[Files]
Source: "{Your Project}\bin\Release\AntiService.exe"; DestDir: "{app}";
Flags: ignoreversion;
BeforeInstall: StopLBApp
...
[Code]
procedure StopLBApp();
var
FileName : String;
ResultCode: Integer;
begin
Log('Asking any existing processes to stop now');
FileName := ExpandConstant('{app}\AntiService.exe');
if FileExists(FileName) then
begin
if not ShellExec('', FileName, '-kill', '',
SW_SHOWNORMAL, ewNoWait, ResultCode) then
MsgBox('DeinitializeSetup:' #13#13 'Execution of ''' +
FileName + ''' failed. ' + SysErrorMessage(ResultCode) + '.',
mbError, MB_OK);
end;
end;