windows服務是如何被調用的?


1、服務介紹

操作系統在啟動的時候,會啟動一些不需要用戶交互的進程。這些進程被稱為服務。當操作系統啟動后它就自動被運行。

2、組成

服務程序、服務控制程序(SCP,service control program)和服務控制管理器(SCM,service control manager)組成了Windows服務。我們可以通過SCP操縱SCM啟動、暫停、停止服務程序。其中服務程序和SCP由我們自己編寫。

SCM

servics.exe是操作系統內置的一個部件。建立數據庫、啟動服務(自啟動),分配服務進程。

SCP

服務控制程序,例如windows自帶的服務工具:

 

 

當然也可以自己寫一個服務管理工具。

服務程序

我們需要執行的任務

3、執行原理

首先看看服務入口:

  static void Main()
  {
      ServiceBase[] ServicesToRun;
      ServicesToRun = new ServiceBase[] 
      { 
         new MainService() 
      };
      ServiceBase.Run(ServicesToRun);
 }

從入口看,這和控制台程序一樣,因為絕大部分的服務都不需要交互,所以沒有用戶界面。 那么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 == 0))
 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 + 1) * 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 > 1;
18                 IntPtr zero = IntPtr.Zero;
19                 for (int i = 0; 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 != 0))
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);在什么時候實例化呢?

        private void Initialize(bool multipleServices)
        {
            if (!this.initialized)
            {
                ...this.status.currentState = 2;
                this.status.controlsAccepted = 0;
                this.status.win32ExitCode = 0;
                this.status.serviceSpecificExitCode = 0;
                this.status.checkPoint = 0;
                this.status.waitHint = 0;
                this.mainCallback = new System.ServiceProcess.NativeMethods.ServiceMainCallback(this.ServiceMainCallback);
                ...
            }
        }

在服務初始化的方法中實例化的。在Run方法的第21行中調用了初始化方法 :services[i].Initialize(multipleServices); 所以現在重心轉移到 this.ServiceMainCallback:

     public unsafe void ServiceMainCallback(int argCount, IntPtr argPointer)
        {
            fixed (System.ServiceProcess.NativeMethods.SERVICE_STATUS* service_statusRef = &this.status)
            {
                string[] state = null;
                ...this.startCompletedSignal = new ManualResetEvent(false);
                this.startFailedException = null;
                ThreadPool.QueueUserWorkItem(new WaitCallback(this.ServiceQueuedMainCallback), state);
                this.startCompletedSignal.WaitOne();
                if ((this.startFailedException != null) && (this.status.win32ExitCode == 0))
                {
                    this.status.win32ExitCode = 0x428;
                }
                if (!System.ServiceProcess.NativeMethods.SetServiceStatus(this.statusHandle, service_statusRef))
                {
                    object[] objArray2 = new object[] { new Win32Exception().Message };
                    this.WriteEventLogEntry(Res.GetString("StartFailed", objArray2), EventLogEntryType.Error);
                    this.status.currentState = 1;
                    System.ServiceProcess.NativeMethods.SetServiceStatus(this.statusHandle, service_statusRef);
                }
            }
        }

在這段代碼中,它開啟了一個新的線程 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 = 0;
 9                 this.status.waitHint = 0;
10                 this.status.currentState = 4;
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 = 1;
17                 if (!System.LocalAppContextSwitches.DontThrowExceptionsOnStart)
18                 {
19                     this.startFailedException = ExceptionDispatchInfo.Capture(exception);
20                 }
21             }
22             this.startCompletedSignal.Set();
23         }

第6行,this.OnStart,這是一個服務開始運行的地方。

   protected virtual void OnStart(string[] args)
   {
   }

相應還有OnStop方法,微軟暴露出這些虛方法,我們在子類中,剛好重寫,這樣我們寫的服務代碼就被執行了。

感興趣的同學,可以寫個服務,安裝個反編譯工具,按F12,就可以跟進到代碼里面去看了。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM