1、服务介绍

操作系统在启动的时候,会启动一些不需要用户交互的进程。这些进程被称为服务。当操作系统启动后它就自动被运行。

2、组成

服务程序、服务控制程序(SCP,service control program)和服务控制管理器(SCM,service control manager)组成了Windows服务。我们可以通过SCP操纵SCM启动、暂停、停止服务程序。其中服务程序和SCP由我们自己编写。

SCM

servics.exe是操作系统内置的一个部件。建立数据库、启动服务(自启动),分配服务进程。

SCP

服务控制程序,例如windows自带的服务工具:

当然也可以自己写一个服务管理工具。

服务程序

我们需要执行的任务

3、执行原理

首先看看服务入口:

  1. static void Main()
  2. {
  3. ServiceBase[] ServicesToRun;
  4. ServicesToRun = new ServiceBase[]
  5. {
  6. new MainService()
  7. };
  8. ServiceBase.Run(ServicesToRun);
  9. }

从入口看,这和控制台程序一样,因为绝大部分的服务都不需要交互,所以没有用户界面。 那么ServiceBase.Run到底做了什么事情呢?平常情况下,我们并不关心,只是windows服务听起来有点神秘。于是就搜索关于windows service原理的文章,理解一下。如下图:

大致原理:服务主线程调用StartServiceCtrlDispatcher,最终执行了ServiceMain回调,调用了我们自己写的服务代码。SCP通过CtrlHandle回调了我们对服务的一些操作,比如暂停,启动等等。它们都通过SetServiceStatus方法与SCM通信,把服务的状态等信息及时地告诉SCM。我结合代码主要介绍下,我们的服务代码是如何被调用的。

Main方法中的ServiceBase是一个什么样的类呢?

从继承关系上看,它是可以跨应用程序域调用(MarshalByRefObject——Enables access to objects across application domain boundaries in applications that support remoting)以及需要释放资源(IDisposable)。这说明,可以远程调用服务以及服务占用了非托管资源。

我们看Run方法:

  1. public static void Run(ServiceBase[] services)
  2. {
  3. if ((services == null) || (services.Length == ))
  4. {
  5. throw new ArgumentException(Res.GetString("NoServices"));
  6. }
  7. if (Environment.OSVersion.Platform != PlatformID.Win32NT)
  8. {
  9. string message = Res.GetString("CantRunOnWin9x");
  10. string title = Res.GetString("CantRunOnWin9xTitle");
  11. LateBoundMessageBoxShow(message, title);
  12. }
  13. else
  14. {
  15. IntPtr entry = Marshal.AllocHGlobal((IntPtr) ((services.Length + ) * Marshal.SizeOf(typeof(System.ServiceProcess.NativeMethods.SERVICE_TABLE_ENTRY))));
  16. System.ServiceProcess.NativeMethods.SERVICE_TABLE_ENTRY[] service_table_entryArray = new System.ServiceProcess.NativeMethods.SERVICE_TABLE_ENTRY[services.Length];
  17. bool multipleServices = services.Length > ;
  18. IntPtr zero = IntPtr.Zero;
  19. for (int i = ; i < services.Length; i++)
  20. {
  21. services[i].Initialize(multipleServices);
  22. service_table_entryArray[i] = services[i].GetEntry();
  23. zero = (IntPtr) (((long) entry) + (Marshal.SizeOf(typeof(System.ServiceProcess.NativeMethods.SERVICE_TABLE_ENTRY)) * i));
  24. Marshal.StructureToPtr(service_table_entryArray[i], zero, true);
  25. }
  26. System.ServiceProcess.NativeMethods.SERVICE_TABLE_ENTRY structure = new System.ServiceProcess.NativeMethods.SERVICE_TABLE_ENTRY {
  27. callback = null,
  28. name = IntPtr.Zero
  29. };
  30. zero = (IntPtr) (((long) entry) + (Marshal.SizeOf(typeof(System.ServiceProcess.NativeMethods.SERVICE_TABLE_ENTRY)) * services.Length));
  31. Marshal.StructureToPtr(structure, zero, true);
  32. bool flag2 = System.ServiceProcess.NativeMethods.StartServiceCtrlDispatcher(entry);
  33. foreach (ServiceBase base2 in services)
  34. {
  35. if (base2.startFailedException != null)
  36. {
  37. base2.startFailedException.Throw();
  38. }
  39. }
  40. string str = "";
  41. if (!flag2)
  42. {
  43. str = new Win32Exception().Message;
  44. string str4 = Res.GetString("CantStartFromCommandLine");
  45. if (Environment.UserInteractive)
  46. {
  47. string str5 = Res.GetString("CantStartFromCommandLineTitle");
  48. LateBoundMessageBoxShow(str4, str5);
  49. }
  50. else
  51. {
  52. Console.WriteLine(str4);
  53. }
  54. }
  55. foreach (ServiceBase base3 in services)
  56. {
  57. base3.Dispose();
  58. if (!flag2 && (base3.EventLog.Source.Length != ))
  59. {
  60. object[] args = new object[] { str };
  61. base3.WriteEventLogEntry(Res.GetString("StartFailed", args), EventLogEntryType.Error);
  62. }
  63. }
  64. }
  65. }

第32行 System.ServiceProcess.NativeMethods.StartServiceCtrlDispatcher(entry)这是一个平台调用,它非常关键,看看原型:

它接收一个参数 entry,这是一个指针或者句柄类型( A platform-specific type that is used to represent a pointer or a handle)。那么它应该指向服务的入口地址。我们看看entry是什么结构?

第15行  IntPtr entry = Marshal.AllocHGlobal((IntPtr) ((services.Length + 1) * Marshal.SizeOf(typeof(System.ServiceProcess.NativeMethods.SERVICE_TABLE_ENTRY)))); 这句一看就是分配内存的。如果我们都学过c语言的话,也不会陌生。虽然c#自动分配内存,我们不用管,其实这件事情还是存在的。SERVICE_TABLE_ENTRY,这个结构如下

callback是委托类型。这个类什么时候实例化的?第22行 service_table_entryArray[i] = services[i].GetEntry(); GetEntry方法如下:

  1. private System.ServiceProcess.NativeMethods.SERVICE_TABLE_ENTRY GetEntry()
  2. {
  3. System.ServiceProcess.NativeMethods.SERVICE_TABLE_ENTRY service_table_entry = new System.ServiceProcess.NativeMethods.SERVICE_TABLE_ENTRY();
  4. this.nameFrozen = true;
  5. service_table_entry.callback = this.mainCallback;
  6. service_table_entry.name = this.handleName;
  7. return service_table_entry;
  8. }

第5行,callback的实例是this.mainCallback,定义 private System.ServiceProcess.NativeMethods.ServiceMainCallback mainCallback; 它的类型: public delegate void ServiceMainCallback(int argCount, IntPtr argPointer);在什么时候实例化呢?

  1. private void Initialize(bool multipleServices)
  2. {
  3. if (!this.initialized)
  4. {
  5. ...this.status.currentState = ;
  6. this.status.controlsAccepted = ;
  7. this.status.win32ExitCode = ;
  8. this.status.serviceSpecificExitCode = ;
  9. this.status.checkPoint = ;
  10. this.status.waitHint = ;
  11. this.mainCallback = new System.ServiceProcess.NativeMethods.ServiceMainCallback(this.ServiceMainCallback);
  12. ...
  13. }
  14. }

在服务初始化的方法中实例化的。在Run方法的第21行中调用了初始化方法 :services[i].Initialize(multipleServices); 所以现在重心转移到 this.ServiceMainCallback:

  1.     public unsafe void ServiceMainCallback(int argCount, IntPtr argPointer)
  2. {
  3. fixed (System.ServiceProcess.NativeMethods.SERVICE_STATUS* service_statusRef = &this.status)
  4. {
  5. string[] state = null;
  6. ...this.startCompletedSignal = new ManualResetEvent(false);
  7. this.startFailedException = null;
  8. ThreadPool.QueueUserWorkItem(new WaitCallback(this.ServiceQueuedMainCallback), state);
  9. this.startCompletedSignal.WaitOne();
  10. if ((this.startFailedException != null) && (this.status.win32ExitCode == ))
  11. {
  12. this.status.win32ExitCode = 0x428;
  13. }
  14. if (!System.ServiceProcess.NativeMethods.SetServiceStatus(this.statusHandle, service_statusRef))
  15. {
  16. object[] objArray2 = new object[] { new Win32Exception().Message };
  17. this.WriteEventLogEntry(Res.GetString("StartFailed", objArray2), EventLogEntryType.Error);
  18. this.status.currentState = ;
  19. System.ServiceProcess.NativeMethods.SetServiceStatus(this.statusHandle, service_statusRef);
  20. }
  21. }
  22. }

在这段代码中,它开启了一个新的线程 ThreadPool.QueueUserWorkItem,回调了ServiceQueuedMainCallback,除此之外,定义了ManualResetEvent,用于主线程和子线程之间的同步。this.startCompletedSignal.WaitOne()  开启子线程后,先阻塞主线程运行,等待子线程的结果。ServiceQueuedMainCallback干了些什么事情?

  1. private void ServiceQueuedMainCallback(object state)
  2. {
  3. string[] args = (string[]) state;
  4. try
  5. {
  6. this.OnStart(args);
  7. this.WriteEventLogEntry(Res.GetString("StartSuccessful"));
  8. this.status.checkPoint = ;
  9. this.status.waitHint = ;
  10. this.status.currentState = ;
  11. }
  12. catch (Exception exception)
  13. {
  14. object[] objArray1 = new object[] { exception.ToString() };
  15. this.WriteEventLogEntry(Res.GetString("StartFailed", objArray1), EventLogEntryType.Error);
  16. this.status.currentState = ;
  17. if (!System.LocalAppContextSwitches.DontThrowExceptionsOnStart)
  18. {
  19. this.startFailedException = ExceptionDispatchInfo.Capture(exception);
  20. }
  21. }
  22. this.startCompletedSignal.Set();
  23. }

第6行,this.OnStart,这是一个服务开始运行的地方。

  1. protected virtual void OnStart(string[] args)
  2. {
  3. }

相应还有OnStop方法,微软暴露出这些虚方法,我们在子类中,刚好重写,这样我们写的服务代码就被执行了。

感兴趣的同学,可以写个服务,安装个反编译工具,按F12,就可以跟进到代码里面去看了。

windows服务是如何被调用的?的更多相关文章

  1. ASP.NET MVC 中应用Windows服务以及Webservice服务开发分布式定时器

    ASP.NET MVC 中应用Windows服务以及Webservice服务开发分布式定时器一:闲谈一下:1.现在任务跟踪管理系统已经开发快要结束了,抽一点时间来写一下,想一想自己就有成就感啊!!  ...

  2. 使用 Topshelf 结合 Quartz.NET 创建 Windows 服务

    Ø  前言 之前一篇文章已经介绍了,如何使用 Topshelf 创建 Windows 服务.当时提到还缺少一个任务调度框架,就是 Quartz.NET.而本文就展开对 Quartz.NET 的研究,以 ...

  3. Windows服务调用Quartz.net 实现消息调度

    Quartz.NET是一个开源的作业调度框架,是OpenSymphony 的 Quartz API的.NET移植,它用C#写成,可用于winform和asp.net应用中.它提供了巨大的灵活性而不牺牲 ...

  4. 使用C#创建及调用WCF完整实例 (Windows服务宿主)

    关于WCF的概念.原理.优缺点等,在这里就不多说了,网上很多,可以自行搜索,比我解释的要专业的多. 这里直接说使用Windows 服务(Windows Service)作为宿主如何实现,其它方式不在此 ...

  5. webform调用windows服务

    准备工作: .电脑->管理->本地用户和组->组->Administrator双击->隶属->添加Network service->确定 .启动windows ...

  6. C#使用windows服务定时调用api接口

    使用VS创建windows服务项目: 创建好项目  会出现一个设计界面 右键弹出对话框 选择添加安装程序 名字什么的自己可以改: 项目目录: 打开项目中的ProjectInstaller.Design ...

  7. Windows服务的新建,安装,卸载,调试以及调用!

    一.前言: 写这篇博文之前,我正顶着压力在尝试着调试我一无所知的Windows自建服务.历经千辛万苦,找了无数零散文档拼凑关于VisualStudio2015中怎样创建和调试服务程序!最后终于调试成功 ...

  8. Python搭建调用本地dll的Windows服务(浏览器可以访问,附测试dll64位和32位文件)

    一.前言说明 博客声明:此文链接地址https://www.cnblogs.com/Vrapile/p/14113683.html,请尊重原创,未经允许禁止转载!!! 1. 功能简述 (1)本文提供生 ...

  9. 基于SignalR实现B/S系统对windows服务运行状态的监测

    通常来讲一个BS项目肯定不止单独的一个BS应用,可能涉及到很多后台服务来支持BS的运行,特别是针对耗时较长的某些任务来说,Windows服务肯定是必不可少的,我们还需要利用B/S与windows服务进 ...

随机推荐

  1. mybatis入门学习记录(一)

    过硬的技术本领,可以给我们保驾护航,飞得更高.今天开始呢.我们就一起来探讨使用mybatis的好处. 首先我们一起来先看看原生的JDBC对于数据库的操作,然后总结其中的利弊,为学习mybatis奠定基 ...

  2. wpf利用线程制作初始界面和关闭窗体特效

    1.首先定义初始窗体,和主窗体. 初始窗体(StartWindow) 主窗体(MainWindow): 2.在主窗体界面中,加载初始窗体.注意在线程中操作UI元素需要使用BeginInvoke或者In ...

  3. Book Review of “The practice of programming” (Ⅱ)

    The practice of programming Chapter 2 Algorithms and Data Structures Searching sequential search (li ...

  4. 做Webservice时报错java.util.List是接口, 而 JAXB 无法处理接口。

    Caused by: com.sun.xml.bind.v2.runtime.IllegalAnnotationsException: 1 counts of IllegalAnnotationExc ...

  5. windows技巧--一次关闭所有资源管理器目录,文件夹目录

    每天开机工作一段时间以后,你可能会和我一样,打开了很多的文件目录,于是一个一个的点窗口关闭.于是想有没有一次关闭所有目录的办法~~咚咚咚,经过一番寻觅,下面是我找到的办法 新建bat文件 close_ ...

  6. javascript日期格式处理

    一. 服务端返回的日期和时间之间有T Asp.net MVC中 action返回前台的日期类型数据 是带有 T的,如: 2015-07-07T10:15:01. 这样的数据在Chrome浏览器,会自动 ...

  7. Java Override和@Override

    Override : 重写. 当子类的某个方法的方法名.返回值.参数列表均与父类的方法保持一致,我们就可以说子类重写了父类的该方法. 其中需要注意: 父类中修饰符为private, static, f ...

  8. codeforces781A Andryusha and Colored Balloons

    本文版权归ljh2000和博客园共有,欢迎转载,但须保留此声明,并给出原文链接,谢谢合作. 本文作者:ljh2000 作者博客:http://www.cnblogs.com/ljh2000-jump/ ...

  9. java 类的加载、连接和初始化

    JVM和类 调用Java命令运行Java程序时,该命令将会启动一条Java虚拟机进程,不管该Java程序启动了多少条线程,创建了多少个变量,它们都处于该Java虚拟机进程里,共享该JVM进程的内存区. ...

  10. 解读Mirantis最新的Neutron性能测试

    最近,mirantis的工程师发布了最新的基于Mitaka版本的Neutron性能测试结果.得出的结论是:Neutron现在的性能已经可以用生产环境了. 报告的三位作者都是OpenStack社区的活跃 ...