65.9K
CodeProject 正在变化。 阅读更多。
Home

反服务,持久化,无 System.ServiceModel

starIconstarIconstarIconstarIconstarIcon

5.00/5 (2投票s)

2010 年 9 月 9 日

CPOL

2分钟阅读

viewsIcon

23384

downloadIcon

175

当您希望应用程序在事件(计时器、系统、文件等)触发时运行,但又不想承受服务管理器的开销

引言

我被要求为我所属的一个组织创建一个小型提醒应用程序。它会每小时提示一个引用。对于类似的情况,我的“工作”答案是创建一个 Windows 服务,使其可以自我安装,非常简单。但我想冒险尝试,因为这主要涉及 U/I,而且我不想处理服务用户帐户,如何在没有服务的情况下完成这项工作呢?本文概述了这次冒险……

背景

此项目的主要组成部分是

  • Windows (本例中使用 WinForms,但 WPF 也可以使用), OnClose Timers
  • 用于强制单例实例和“关闭”应用程序的套接字/侦听器
  • InnoSetup 脚本

Using the Code

  • 创建一个新的 Windows 项目(新建项目... Windows)
  • 创建一个计时器。拖放一个计时器对象,设置参数,创建一个计时器处理程序。您的项目可能没有使用计时器,没关系,如果一个事件触发窗口显示/隐藏,这就是你想要的
  • 创建一个“强制关闭”机制,我创建了一个名为“CanClose”的复选框
  • 重写 FormClosing
    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;
                }
            }
        }
        // I really hate catching an exception as logic, but there's no other way
        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;

摘要

所以你所拥有的是一个应用程序,一旦启动,就会保持运行,并且会在每次启动或发生某些事件时尝试显示一个窗口。以我的情况为例,是计时器。希望你喜欢!

历史

  • 2010 年 9 月 9 日:初始帖子
© . All rights reserved.