C#开发一个混合Windows服务和Windows窗体的程序
很多时候,我们希望服务程序可以直接运行,或者可以响应一些参数,这时候,混合Windows服务和Windows窗体的程序就排上用场了。要实现同时支持Windows服务和Windows窗体,需要在启动的第一步时判断当前运行环境是否为服务模式,可以从以下几个方面进行判断:
- 会话ID:Process.SessionId,获取当前进程的SessionId,为0则可以是服务模式(谢谢1楼提醒);
- 当前用户名称: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.SystemDirectory, "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(MainWorker._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 }