很多時候,我們希望服務程式可以直接執行,或者可以響應一些引數,這時候,混合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 }