C#開發一個混合Windows服務和Windows窗體的程式

【一路向东】發表於2024-07-05

很多時候,我們希望服務程式可以直接執行,或者可以響應一些引數,這時候,混合Windows服務和Windows窗體的程式就排上用場了。要實現同時支援Windows服務和Windows窗體,需要在啟動的第一步時判斷當前執行環境是否為服務模式,可以從以下幾個方面進行判斷:

  • 當前使用者名稱稱:Environment.UserName,如果為SYSTEM則可以是服務模式
  • 是否使用者互動模式:Environment.UserInteractive,為false時也可以認為是服務模式
  • 自定義啟動引數:建立服務時新增一個特定的啟動引數,比如[/s],然後程式碼中檢查啟動引數args[0] == "/s"

如果上述條件都不成立,就進入窗體模式,或者是響應一些其他的命令列引數,比如安裝[/i]、解除安裝服務[/u]等。

專案需要新增下面的元件引用:

  • System.Configuration.Install
  • System.ServiceModel
  • System.ServiceProcess

專案包含的類檔案:

  • Program.cs:根據執行模式執行服務或者窗體,或者響應命令列引數;
  • MainService.cs:服務類,實現與窗體類一致的功能;
  • MainForm.cs:窗體類,實現與服務類一致的功能,還可以新增一些安裝和解除安裝服務,啟動和停止服務的管理功能;
  • MainWorker.cs:實現功能的類,服務類與窗體類都呼叫該類;
  • ServiceInstaller.cs:服務安裝類,可以呼叫框架的InstallUtil.exe工具安裝解除安裝服務。

各個類的程式碼如下:

  • Program.cs
  1 using System;
  2 using System.Collections.Generic;
  3 using System.Diagnostics;
  4 using System.IO;
  5 using System.Runtime.InteropServices;
  6 using System.ServiceProcess;
  7 using System.Windows.Forms;
  8 
  9 namespace WindowsServiceFormHybridSample
 10 {
 11     internal static class Program
 12     {
 13         public const string SERVICE_NAME = "WindowsServiceFormHybridSample";
 14 
 15         [STAThread]
 16         static void Main(string[] args)
 17         {
 18             //本應用程式為Windows服務和Windows窗體混合的模式
 19             //如果啟動引數為/S,則進入Windows服務模式,否則進入Windows窗體模式
 20             //如果當前賬戶名稱為SYSTEM,則進入Windows服務模式,否則進入Windows窗體模式
 21 
 22             //var serviceMode = args.Length > 0 && args[0].Equals("/S", StringComparison.OrdinalIgnoreCase);
 23             var serviceMode = Environment.UserName.Equals("SYSTEM", StringComparison.OrdinalIgnoreCase);
 24  25             try
 26             {
 27                 if (serviceMode)
 28                 {
 29                     //開啟Windows服務模式,切勿彈出訊息框
 30                     ServiceBase.Run(new MainService());
 31                     return;
 32                 }
 33 
 34                 //開啟Windows窗體模式
 35                 Application.EnableVisualStyles();
 36                 Application.SetCompatibleTextRenderingDefault(false);
 37 
 38                 if (args.Length == 0)
 39                 {
 40                     //開啟窗體
 41                     using (var form = new MainForm())
 42                     {
 43                         Application.Run(form);
 44                     }
 45 
 46                     return;
 47                 }
 48 
 49                 //處理命令列引數
 50                 Program.ParseArgs(args);
 51             }
 52             catch (Exception ex)
 53             {
 54                 if (serviceMode)
 55                 {
 56                     //寫入錯誤日誌
 57                 }
 58                 else
 59                 {
 60                     MessageBox.Show(ex.ToString());
 61                 }
 62             }
 63         }
 64 
 65         private static void ParseArgs(string[] args)
 66         {
 67             var argInstall = 0;
 68             var argUninstall = 0;
 69             var argSilent = 0;
 70             var argOthers = 0;
 71 
 72             foreach (var arg in args)
 73             {
 74                 var temp = arg.Replace('/', '-').ToUpper();
 75 
 76                 switch (temp)
 77                 {
 78                     case "-I":
 79                         argInstall = 1;
 80                         break;
 81                     case "-U":
 82                         argUninstall = 1;
 83                         break;
 84                     case "-S":
 85                         argSilent = 1;
 86                         break;
 87                     default:
 88                         argOthers = 1;
 89                         break;
 90                 }
 91             }
 92 
 93             if (argOthers == 1)
 94             {
 95                 MessageBox.Show(Program.SERVICE_NAME + "支援的命令列引數:\r\n\r\n/i\t安裝更新服務\r\n/u{2}解除安裝更新服務\r\n/s\t靜默模式");
 96             }
 97             else
 98             {
 99                 int value = argInstall + argUninstall;
100 
101                 switch (value)
102                 {
103                     case 0:
104                         MessageBox.Show("需要指定[/i]或者[/u]引數。");
105                         break;
106                     case 2:
107                         MessageBox.Show("不能同時指定[/i]和[/u]引數。");
108                         break;
109                     default:
110                         if (argInstall == 1)
111                         {
112                             Program.InstallServiceA(false, argSilent == 1);
113                         }
114                         else
115                         {
116                             Program.InstallServiceB(true, argSilent == 1);
117                         }
118 
119                         break;
120                 }
121             }
122         }
123 
124         /// <summary>
125         /// 呼叫.NET Framework框架的InstallUtil.exe工具安裝解除安裝服務,需要專案中包含服務安裝類ServiceInstaller.cs
126         /// </summary>
127         private static void InstallServiceA(bool uninstall = false, bool slient = false)
128         {
129             try
130             {
131                 var fileName = Path.Combine(RuntimeEnvironment.GetRuntimeDirectory(), "InstallUtil.exe");
132                 var args = string.Format("{0}\"{1}\"", uninstall ? "/U " : string.Empty, Application.ExecutablePath);
133 
134                 using (var process = Process.Start(new ProcessStartInfo(fileName, args) { WindowStyle = ProcessWindowStyle.Hidden }))
135                 {
136                     process.WaitForExit(10000);
137                 }
138 
139                 if (uninstall)
140                 {
141                     return;
142                 }
143 
144                 fileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "sc.exe");
145                 args = string.Format("start \"{0}\"", Program.SERVICE_NAME);
146 
147                 using (var process = Process.Start(new ProcessStartInfo(fileName, args) { WindowStyle = ProcessWindowStyle.Hidden }))
148                 {
149                     process.WaitForExit(10000);
150                 }
151             }
152             catch (Exception ex)
153             {
154                 MessageBox.Show(ex.ToString());
155             }
156         }
157 
158         /// <summary>
159         /// 呼叫作業系統的sc.exe工具安裝解除安裝服務
160         /// </summary>
161         private static void InstallServiceB(bool uninstall = false, bool slient = false)
162         {
163             try
164             {
165                 var fileName = Path.Combine(Environment.SystemDirectory, "sc.exe");
166                 var argsList = new List<string>();
167 
168                 if (!uninstall)
169                 {
170                     argsList.Add(string.Format("create {0} binPath= \"{1}\" start= auto", Program.SERVICE_NAME, Application.ExecutablePath));
171                     argsList.Add(string.Format("start {0}", Program.SERVICE_NAME));
172                 }
173                 else
174                 {
175                     argsList.Add(string.Format("stop {0}", Program.SERVICE_NAME));
176                     argsList.Add(string.Format("delete {0}", Program.SERVICE_NAME));
177                 }
178 
179                 foreach (var args in argsList)
180                 {
181                     using (var process = Process.Start(new ProcessStartInfo(fileName, args) { WindowStyle = ProcessWindowStyle.Hidden }))
182                     {
183                         process.WaitForExit(10000);
184                     }
185                 }
186             }
187             catch (Exception ex)
188             {
189                 MessageBox.Show(ex.ToString());
190             }
191         }
192     }
193 }
  • MainService.cs
 1 using System;
 2 using System.ServiceProcess;
 3 
 4 namespace WindowsServiceFormHybridSample
 5 {
 6     internal class MainService : ServiceBase
 7     {
 8         public MainService()
 9         {
10             base.ServiceName = Program.SERVICE_NAME;
11         }
12 
13         protected override void OnStart(string[] args)
14         {
15             try
16             {
17                 //這裡最好執行非同步的方法
18                 //否則會導致Windows的服務啟動超時
19 
20                 //與MainForm執行相同的方法
21                 MainWorker.Start();
22             }
23             catch (Exception ex)
24             {
25                 //寫入錯誤日誌
26             }
27         }
28 
29         protected override void OnStop()
30         {
31             try
32             {
33                 MainWorker.Stop();
34             }
35             catch (Exception ex)
36             {
37                 //寫入錯誤日誌
38             }
39         }
40     }
41 }
  • MainForm.cs
 1 using System;
 2 using System.Windows.Forms;
 3 
 4 namespace WindowsServiceFormHybridSample
 5 {
 6     public partial class MainForm : Form
 7     {
 8         public MainForm()
 9         {
10             this.InitializeComponent();
11             this.button1.Text = "啟動服務";
12         }
13 
14         private void button1_Click(object sender, EventArgs e)
15         {
16             //與MainService執行相同的方法
17             try
18             {
19                 if (this.button1.Text == "啟動服務")
20                 {
21                     MainWorker.Start();
22                     this.button1.Text = "停止服務";
23                     return;
24                 }
25 
26                 MainWorker.Stop();
27                 this.button1.Text = "啟動服務";
28             }
29             catch (Exception ex)
30             {
31                 MessageBox.Show(ex.ToString());
32             }
33         }
34     }
35 }
  • MainWorker.cs
 1 using System;
 2 using System.Threading;
 3 using System.Threading.Tasks;
 4 
 5 namespace WindowsServiceFormHybridSample
 6 {
 7     internal class MainWorker
 8     {
 9         private static MainWorker _instance;
10         private static CancellationTokenSource _cancellationTokenSource;
11 
12         private bool _isBusy;
13 
14         public bool IsBusy { get => this._isBusy; }
15 
16         static MainWorker()
17         {
18             MainWorker._instance = new MainWorker();
19         }
20 
21         private async void DoWork(CancellationToken cancellationToken)
22         {
23             this._isBusy = true;
24 
25             while (!cancellationToken.IsCancellationRequested)
26             {
27                 await Task.Delay(1000);
28 
29                 //其他耗時任務
30             }
31 
32             this._isBusy = false;
33         }
34 
35         public static void Start()
36         {
37             if (MainWorker._instance.IsBusy)
38             {
39                 throw new Exception("服務正在執行中。");
40             }
41 
42             MainWorker._cancellationTokenSource = new CancellationTokenSource();
43             MainWorker._instance.DoWork(_cancellationTokenSource.Token);
44         }
45 
46         public static void Stop()
47         {
48             if (MainWorker._cancellationTokenSource != null)
49             {
50                 MainWorker._cancellationTokenSource.Cancel();
51             }
52         }
53     }
54 }
  • ServiceInstaller.cs
 1 using System.ComponentModel;
 2 using System.Configuration.Install;
 3 using System.ServiceProcess;
 4 
 5 namespace WindowsServiceFormHybridSample
 6 {
 7     [RunInstaller(true)]
 8     public class ServiceInstaller : Installer
 9     {
10         public ServiceInstaller()
11         {
12             var ServiceProcessInstaller = new ServiceProcessInstaller()
13             {
14                 Account = ServiceAccount.LocalSystem
15             };
16 
17             var ServiceInstaller = new System.ServiceProcess.ServiceInstaller
18             {
19                 ServiceName = Program.SERVICE_NAME,
20                 StartType = ServiceStartMode.Automatic
21             };
22 
23             base.Installers.AddRange(new Installer[] { ServiceProcessInstaller, ServiceInstaller });
24         }
25     }
26 }

相關文章