很多时候,我们希望服务程序可以直接运行,或者可以响应一些参数,这时候,混合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 }