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

  1. VS2013创建Windows服务 || VS2015+Windows服务简易教程

    转自:https://www.cnblogs.com/no27/p/4849123.htmlhttps://blog.csdn.net/ly416/article/details/78860522 V ...

  2. [转帖]以Windows服务方式运行.NET Core程序

    以Windows服务方式运行.NET Core程序 原作者blog:https://www.cnblogs.com/guogangj/p/10093102.html 里面使用了NSSM 工具 但是自己 ...

  3. 连表查询都用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%我不知道在 ...

  4. 玩转Windows服务系列——Windows服务小技巧

    伴随着研究Windows服务,逐渐掌握了一些小技巧,现在与大家分享一下. 将Windows服务转变为控制台程序 由于默认的Windows服务程序,编译后为Win32的窗口程序.我们在程序启动或运行过程 ...

  5. 玩转Windows服务系列——Windows服务启动超时时间

    最近有客户反映,机房出现断电情况,服务器的系统重新启动后,数据库服务自启动失败.第一次遇到这种情况,为了查看是不是断电情况导致数据库文件损坏,从客户的服务器拿到数据库的日志,进行分析. 数据库工作机制 ...

  6. Java魔法堂:以Windows服务的形式运行Java程序

    一.前言 由于防止维护人员误操作关闭Java控制台程序,因此决定将其改造为以Windows服务的形式运行.弄了一个上午总算搞定了,下面记录下来,以供日后查阅. 二.Java Service Wrapp ...

  7. 玩转Windows服务系列——Windows服务小技巧

    原文:玩转Windows服务系列——Windows服务小技巧 伴随着研究Windows服务,逐渐掌握了一些小技巧,现在与大家分享一下. 将Windows服务转变为控制台程序 由于默认的Windows服 ...

  8. Windows服务(system权限)程序显示界面与用户交互,Session0通知Session1里弹出对话框(真的很牛) good

    源码资源下载:http://download.csdn.net/detail/stony1980/4512984   1.VC2008中编写“Windows服务”(Windows Service)程序 ...

  9. 以Windows服务方式运行.NET Core程序

    在之前一篇博客<以Windows服务方式运行ASP.NET Core程序>中我讲述了如何把ASP.NET Core程序作为Windows服务运行的方法,而今,我们又遇到了新的问题,那就是: ...

  10. 动手开发一个名为“微天气”的微信小程序(上)

    引言:在智能手机软件的装机量中,天气预报类的APP排在比較靠前的位置.说明用户对天气的关注度非常高.由于人们不管是工作还是度假旅游等各种活动都须要依据自然天气来安排.跟着本文开发一个"微天气 ...

随机推荐

  1. Golang重复Rails Devise gem密码加密

    https://github.com/haimait/go-devise-encryptor package main import ( "fmt" //devisecrypto ...

  2. grpc使用nginx代理配置

    参考:https://www.nginx.com/blog/nginx-1-13-10-grpc/ 重点是标记红色的部分 http { log_format main '$remote_addr - ...

  3. 10-flask博客项目

    centos7 编译安装python3.7.1 安装步骤 centos7自带python2,由于执行yum需要python2,所以即使安装了python3也不能删除python21.安装依赖包yum ...

  4. 排查Python卡慢神器

    如果遇到Python正在运行中的进程卡住,找不到原因.可以试试以下工具方法, 对于python就像jstack对于java一样. 法一 使用pystack-debugger 安装方式如下: yum i ...

  5. Android 13 - Media框架(26)- OMXNodeInstance(三)

    关注公众号免费阅读全文,进入音视频开发技术分享群! 上一节我们了解了OMXNodeInstance中的端口定义,这一节我们一起来学习ACodec.OMXNode.OMX 组件使用的 buffer 到底 ...

  6. 使用 CompeletedFuture 实现异步调用

    在我们平时写的项目中,异步调用是一个比较重要的优化手段,在 Java 中,提供了 CompletedFuture 供我们使用,具体实现如下: 例子 假如现在有一个需求,我需要去淘宝.天猫和京东去搜索某 ...

  7. 腾讯消息队列CMQ一键化部署脚本

    CMQ-1.0.2-软件包.tar安装包放在家目录,脚本也放在家目录,然后执行:sh -x cmq_install.sh [ip1] [ip2] [ip3] 即可 下列脚本代码保存为:cmq_inst ...

  8. Flutter TextField开始输入中文的时候,被打断导致错误输入字母问题

    一.Bug样例 建立一个web demo flutter run -d chrome --web-renderer html 出现问题: 输入中文的时候,比如打字 hao, 第一个字母h会先输入,变成 ...

  9. ko编译常见问题

    记录ko编译过程中遇到的常见问题: (1)找不到标准库头文件 解决方式:将lib库中的头文件链接到编译内核中.

  10. Kafka--Rebalance重平衡

    Rebalance总览 Rebalance触发条件 (1)消费组成员发生变更,有新消费者加入或者离开,或者有消费者崩溃 (2)消费者组订阅的主题数量发生变更 (3)消费组订阅主题的分区数发生变更 避免 ...