C#开发一个混合Windows服务和Windows窗体的程序
很多时候,我们希望服务程序可以直接运行,或者可以响应一些参数,这时候,混合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 }
C#开发一个混合Windows服务和Windows窗体的程序的更多相关文章
- VS2013创建Windows服务 || VS2015+Windows服务简易教程
转自:https://www.cnblogs.com/no27/p/4849123.htmlhttps://blog.csdn.net/ly416/article/details/78860522 V ...
- [转帖]以Windows服务方式运行.NET Core程序
以Windows服务方式运行.NET Core程序 原作者blog:https://www.cnblogs.com/guogangj/p/10093102.html 里面使用了NSSM 工具 但是自己 ...
- 连表查询都用Left Join吧 以Windows服务方式运行.NET Core程序 HTTP和HTTPS的区别 ASP.NET SignalR介绍 asp.net—WebApi跨域 asp.net—自定义轻量级ORM C#之23中设计模式
连表查询都用Left Join吧 最近看同事的代码,SQL连表查询的时候很多时候用的是Inner Join,而我觉得对我们的业务而言,99.9%都应该使用Left Join(还有0.1%我不知道在 ...
- 玩转Windows服务系列——Windows服务小技巧
伴随着研究Windows服务,逐渐掌握了一些小技巧,现在与大家分享一下. 将Windows服务转变为控制台程序 由于默认的Windows服务程序,编译后为Win32的窗口程序.我们在程序启动或运行过程 ...
- 玩转Windows服务系列——Windows服务启动超时时间
最近有客户反映,机房出现断电情况,服务器的系统重新启动后,数据库服务自启动失败.第一次遇到这种情况,为了查看是不是断电情况导致数据库文件损坏,从客户的服务器拿到数据库的日志,进行分析. 数据库工作机制 ...
- Java魔法堂:以Windows服务的形式运行Java程序
一.前言 由于防止维护人员误操作关闭Java控制台程序,因此决定将其改造为以Windows服务的形式运行.弄了一个上午总算搞定了,下面记录下来,以供日后查阅. 二.Java Service Wrapp ...
- 玩转Windows服务系列——Windows服务小技巧
原文:玩转Windows服务系列——Windows服务小技巧 伴随着研究Windows服务,逐渐掌握了一些小技巧,现在与大家分享一下. 将Windows服务转变为控制台程序 由于默认的Windows服 ...
- Windows服务(system权限)程序显示界面与用户交互,Session0通知Session1里弹出对话框(真的很牛) good
源码资源下载:http://download.csdn.net/detail/stony1980/4512984 1.VC2008中编写“Windows服务”(Windows Service)程序 ...
- 以Windows服务方式运行.NET Core程序
在之前一篇博客<以Windows服务方式运行ASP.NET Core程序>中我讲述了如何把ASP.NET Core程序作为Windows服务运行的方法,而今,我们又遇到了新的问题,那就是: ...
- 动手开发一个名为“微天气”的微信小程序(上)
引言:在智能手机软件的装机量中,天气预报类的APP排在比較靠前的位置.说明用户对天气的关注度非常高.由于人们不管是工作还是度假旅游等各种活动都须要依据自然天气来安排.跟着本文开发一个"微天气 ...
随机推荐
- iceoryx源码阅读(八)——IPC通信机制
目录 1 整体结构 2 序列化与反序列化 3 类Unix系统的实现 3.1 发送函数send 3.2 接收函数receive 4 Windows系统的实现 4.1 发送函数send 4.2 接收函数r ...
- 回顾复习x学习笔记
从头回顾(截至搜索) #define fo(x,y,z) for(int (x)=(y);(x)<=(z);(x)++) #define foo(x,y,z) for(int (x)=(y);( ...
- C 语言编程 — 逻辑控制语句
目录 文章目录 目录 前文列表 结构化程序设计 条件分支语句 if/else 语句 if 语句 if/else 语句 if/else-if/else 语句 嵌套 if 语句 switch 语句 swi ...
- [kernel] 带着问题看源码 —— 进程 ID 是如何分配的
前言 在<[apue] 进程控制那些事儿>一文中,曾提到进程 ID 并不是唯一的,在整个系统运行期间一个进程 ID 可能会出现好多次. > ./pid fork and exec c ...
- HTML——input之单选按钮
在 HTML 中,把 <input> 标签的 type 属性设置为 radio 可以表示单选按钮.具体语法格式如下: <input type="radio" /& ...
- C#自动安装字体
在Windows系统中,原有自带的字体样式有限,有时候我们的程序会使用到个别稀有或系统不自带的字体.因此我们需要将字体打包到程序中,当程序启动时,检测系统是否有该字体,如果没有则安装该字体,也可以动态 ...
- windows下载安装ipopt求解器 可用于pyomo调用
方案一:采用官方编译的应用程序 官方对windows下有已经编译好的应用程序,只需要下载下来,并将ipopt的应用程序所在文件夹路径添加到系统全局环境变量就可以了.这样在利用pyomo或者其他建模工具 ...
- itest(爱测试) 4.5.2 发布,开源BUG 跟踪管理 & 敏捷测试管理软件
itest 简介 itest 开源敏捷测试管理,testOps 践行者,极简的任务管理,测试管理,缺陷管理,测试环境管理4合1,又有丰富的统计分析.可按测试包分配测试用例执行,也可建测试迭代(含任务, ...
- k8s——kubctl命令基础
语法 kubevtl [command] [type] [name] [flags] command: 指定要对一个或多个资源执行的操作,例如,`create`,`get`,`describe`,`d ...
- 【Socket】解决UDP丢包问题
一.介绍 UDP是一种不可靠的.无连接的.基于数据报的传输层协议.相比于TCP就比较简单,像写信一样,直接打包丢过去,就不用管了,而不用TCP这样的反复确认.所以UDP的优势就是速度快,开销小.但是随 ...