Windows API 之 Windows Service

weixin_34162629發表於2011-07-18

再來談Windows Service, 但是通過昨天的兩篇文章,發現Windows Service 還存有一些疑問,所以就順勢將Windows Service 徹底弄清楚。

      前兩篇:

AutoExcuteJob Framework(一)如何構建,部署 Windows Service

AutoExcuteJob Framework(二)再談Windows Service:SC 和 InstallUtil 區別

     已經對如何建立Windows Service,以及Windows Service 的安裝和部署有了一個大概的介紹,這一篇主要是通過Windows API 來操作Windows Service(因為目前.NET還未提供安裝和解除安裝Windows Service的類,ServiceInstaller除外,ServiceInstaller不方便我們隨意呼叫),並且羅列了一些常用的操作Windows Service 的API,製作了一個ServiceControllerExtension的類,通過ServiceControllerExtension和ServiceController,我們可以比較方便的操作Windows Service,ServiceControllerExtension主要是提供了Windows Service 的安裝和部署的介面,而.NET自帶的ServiceController則已經有對Service進行Pause(),Stop(),等等操作。ServiceControllerExtension 和ServiceController的主要作用還在於我們可以在開發的時候,用程式碼控制安裝和解除安裝Service,方便對Service進行除錯。

首先,我先把與Windows Service相關的API羅列出來,其實 AutoExcuteJob Framework(二)再談Windows Service:SC 和 InstallUtil 區別 中已經羅列了一些,只是不太全,這次我把查詢和控制Service 的API也羅列出來:

ExpandedBlockStart.gifWindows API程式碼

[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr CreateService(IntPtr databaseHandle, string serviceName, string displayName, int access, int serviceType, int startType, int errorControl, string binaryPath, string loadOrderGroup, IntPtr pTagId, string dependencies, string servicesStartName, string password);
        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr OpenSCManager(string machineName, string databaseName, int access);
        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern IntPtr OpenService(IntPtr databaseHandle, string serviceName, int access);
        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool DeleteService(IntPtr serviceHandle);
        [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success), DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CloseServiceHandle(IntPtr handle);
        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool ChangeServiceConfig2(IntPtr serviceHandle, uint infoLevel, ref SERVICE_DESCRIPTION serviceDesc);
        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool EnumDependentServices(IntPtr serviceHandle, int serviceState, IntPtr bufferOfENUM_SERVICE_STATUS, int bufSize, ref int bytesNeeded, ref int numEnumerated);
        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool EnumServicesStatus(IntPtr databaseHandle, int serviceType, int serviceState, IntPtr status, int size, out int bytesNeeded, out int servicesReturned, ref int resumeHandle);
        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool EnumServicesStatusEx(IntPtr databaseHandle, int infolevel, int serviceType, int serviceState, IntPtr status, int size, out int bytesNeeded, out int servicesReturned, ref int resumeHandle, string group);
        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool QueryServiceConfig(IntPtr serviceHandle, IntPtr query_service_config_ptr, int bufferSize, out int bytesNeeded);
        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool StartService(IntPtr serviceHandle, int argNum, IntPtr argPtrs);
[DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern unsafe bool ControlService(IntPtr serviceHandle, int control, SERVICE_STATUS* pStatus);
        [DllImport("advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern unsafe bool QueryServiceStatus(IntPtr serviceHandle, SERVICE_STATUS* pStatus);

後面兩個方法 ControlService和QueryServiceStatus中用到指標,是unsafe的,所以需要在專案的屬性中:Build->Allow unsafe code 選中。

這裡用到兩個Struct:

ExpandedBlockStart.gifStruct程式碼

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct SERVICE_DESCRIPTION
    {
public IntPtr description;
    }
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct SERVICE_STATUS
    {
public int serviceType;
public int currentState;
public int controlsAccepted;
public int win32ExitCode;
public int serviceSpecificExitCode;
public int checkPoint;
public int waitHint;
    }

     在上一篇文章  AutoExcuteJob Framework(二)中我提及到,如果希望使用Windows API來操作Windows Service,需要遵循幾個步驟:

  1. 獲取SCManager的控制程式碼
  2. 根據SCManager控制程式碼和Windows Service的ServiceName開啟Service的控制程式碼 (或者是建立新的Service)
  3. 利用該Service控制程式碼進行Pause,Stop,Start等操作 
  4. 關閉Service控制程式碼
  5. 關閉SCManager控制程式碼

    這裡,不管是註冊服務,還是登出服務,不管是停止服務,還是啟動服務,等等,只要是對服務進行操作,都是遵循這麼一個規則。

    由於對服務的Pause,Stop,Start,等操作,.NET中的ServiceController已經提供了,所以,我們就沒有必要再利用API來構建一個新的類或者方法,除非希望在這些操作中增加一些自定義的操作進去,那另當別論; 所以,我就主要針對CreateService和DeleteService進行一個封裝,封裝在ServiceControllerExtension中間。

    我們先來看看CreateService   

ExpandedBlockStart.gifCreateService程式碼

public static ServiceController CreateService(string serviceName,string displayName,string binPath ,string description,ServiceStartType serviceStartType ,
             ServiceAccount serviceAccount,string dependencies,bool startAfterRun)
        {
if (CheckServiceExist(serviceName))
            {
throw new InvalidOperationException("Windows Service:" + serviceName + " has existed!");
            }
            IntPtr databaseHandle = SafeNativeMethods.OpenSCManager(null, null, (int)SCManagerAccess.All );
            IntPtr zero = IntPtr.Zero;
if (databaseHandle == zero)
            {
throw new Win32Exception ();
            }
string servicesStartName=null  ;
string password = null  ;
switch (serviceAccount)
            {
case ServiceAccount.LocalService:
                    {
                        servicesStartName = @"NT AUTHORITY\LocalService";
break;
                    }
case ServiceAccount.LocalSystem:
                    {
                        servicesStartName =null ;
                        password = null;
break;
                    }
case ServiceAccount.NetworkService:
                    {
                        servicesStartName = @"NT AUTHORITY\NetworkService";
break;
                    }
case ServiceAccount.User:
                    {
                        AccountInfo accountInfo = GetLoginInfo();
                        serviceAccount = accountInfo.Account;
                        password = accountInfo.Password;
                        servicesStartName = accountInfo.UserName;
break;
                    }
            }
try
            {
                zero = SafeNativeMethods.CreateService(databaseHandle, serviceName, displayName, (int)ServiceAccess.All, (int)ServiceType.Win32OwnProcess,
                    (int)serviceStartType, (int)ServiceErrorControlType.Ignore, binPath, null, IntPtr.Zero, dependencies, servicesStartName, password);
if (zero == IntPtr.Zero)
                {
throw new Win32Exception();
                }
if (description != null && description.Length > 0)
                {
                    SERVICE_DESCRIPTION serviceDesc = new SERVICE_DESCRIPTION();
                    serviceDesc.description = Marshal.StringToHGlobalUni(description);
bool flag = SafeNativeMethods.ChangeServiceConfig2(zero, (int)ServiceErrorControlType.Normal, ref serviceDesc);
                    Marshal.FreeHGlobal(serviceDesc.description);
if (!flag)
                    {
throw new Win32Exception();
                    }
                }
            }
finally
            {
if (zero != IntPtr.Zero)
                {
                    SafeNativeMethods.CloseServiceHandle(zero);
                }
                SafeNativeMethods.CloseServiceHandle(databaseHandle );
            }
if (zero != IntPtr.Zero)
            {
                ServiceController sc = new ServiceController(serviceName);
if (startAfterRun)
                    sc.Start();
return sc;
            }
else
            {
return null;
            }
        }

    CreateService:

  1. CheckServiceExist():自定義的方法,用ServiceController來判斷是否存在名為serviceName 的Service ,如果存在的話,就丟擲異常;
  2. 用OpenSCManager() API 來開啟SCManager控制程式碼,其中第一個引數是機器名稱,null 指的是本機;第二個引數是Service Control Manager的Database, 一般用 SERVICES_ACTIVE_DATABASE,如果用null,指用預設的;第三個引數是用來指明訪問許可權,這裡選擇所有許可權。
  3. 準備CreateService() API 的所有引數,上面那個Switch語句主要是來判斷該服務用什麼賬戶作為啟動賬戶,如果傳入的是ServiceAccount.User時,會呼叫GetLoginInfo()方法,開啟一個對話方塊,讓使用者輸入啟動賬戶的使用者名稱和密碼。
  4. 呼叫Windows API CreateService() 來建立一個新的服務,如果建立成功,則返回該服務的控制程式碼;否則返回IntPtr.Zero
  5. 關閉新建立服務的控制程式碼
  6. 關閉Service Control Manager的控制程式碼 (至此,呼叫API建立服務部分已經結束)
  7. 如果需要建立服務後,並啟動服務,那麼就根據serviceName建立一個新的ServiceController物件,呼叫該物件的Start()方法啟動服務;並且返回該ServiceController物件;建立失敗,則返回null;

       至此,CreateService()方法封裝完畢。

       接著,我們看一下DeleteService(),整個操作的流程必須符合上面紅色字型的流程,所以大體上和CreateService()差不多,但是由於DeleteService() API本身存在一定的特殊性,所以需要一些額外的操作和得引起注意;

       MSDN上對DeleteService API的解釋是:

The DeleteService function marks a service for deletion from the service control manager database. The database entry is not removed until all open handles to the service have been closed by calls to the CloseServiceHandle function, and the service is not running. A running service is stopped by a call to the ControlServicefunction with the SERVICE_CONTROL_STOP control code. If the service cannot be stopped, the database entry is removed when the system is restarted.

The service control manager deletes the service by deleting the service key and its subkeys from the registry.

     第一個,我們需要弄明白的是DeleteService 分為兩個步驟來刪除服務:

  1. 標記該服務為可刪除的服務
  2. 檢查該服務是否已經停止,並且該牽涉到該服務的所有控制程式碼都已經被關閉的時候,再來刪除該服務;如果該服務一直都在執行狀態,那麼就等到下次機器重啟的時候,來刪除該服務。

      而刪除服務的本質是在登錄檔裡面刪除該服務的登錄檔鍵以及該鍵的子鍵。

      看到這裡,有個問題就迎刃而解,當我們呼叫SC delete 或者 InstallUtil /u 刪除服務的時候,cmd視窗提示刪除成功,但是為什麼在Service Control Manager裡面,我們還能看到該服務還在執行,原因就在DeleteService是分這兩個步驟進行的。    

      弄清楚這一點,下面DeleteService()的程式碼就不難明白,上篇文章中,我自己的疑問也解決了:(在UnInstall的操作過程的最後,還要呼叫ServiceController去停止該服務)。

ExpandedBlockStart.gifDeleteService 程式碼

public static bool DeleteService(string serviceName)
        {
if (!CheckServiceExist(serviceName))
            {
throw new InvalidOperationException("Windows Service:"+serviceName +" doesn't exist!");
            }
bool result = false;
            IntPtr databaseHandle = IntPtr.Zero;
            IntPtr zero = IntPtr.Zero;
            databaseHandle = SafeNativeMethods.OpenSCManager(null, null, (int)SCManagerAccess.All);
if (databaseHandle == zero)
            {
throw new Win32Exception();
            }
try
            {
                zero = SafeNativeMethods.OpenService(databaseHandle, serviceName, (int)ServiceAccess.All);
if (zero == IntPtr.Zero)
                {
throw new Win32Exception();
                }
                result = SafeNativeMethods.DeleteService(zero);
            }
finally
            {
if (zero != IntPtr.Zero)
                {
                    SafeNativeMethods.CloseServiceHandle(zero);
                }
                SafeNativeMethods.CloseServiceHandle(databaseHandle);
            }
try
            {
using (ServiceController sc = new ServiceController(serviceName))
                {
if (sc.Status != ServiceControllerStatus.Stopped)
                    {
                        sc.Stop();
                        sc.Refresh();
int num = 10;
while (sc.Status != ServiceControllerStatus.Stopped && num > 0)
                        {
                            Thread.Sleep(0x3e8);
                            sc.Refresh();
                            num--;
                        }
                    }
                }
            }
catch { }
return result;
        }

   從程式碼可以明確的看出來,DeleteService()包括以下幾個步驟: 

  1. CheckServiceExist():自定義的方法,用ServiceController來判斷是否存在名為serviceName 的Service ,不存在的話,就丟擲異常或者直接返回;
  2. 用OpenSCManager() API 來開啟SCManager控制程式碼,其中第一個引數是機器名稱,null 指的是本機;第二個引數是Service Control Manager的Database, 一般用 SERVICES_ACTIVE_DATABASE,如果用null,指用預設的;第三個引數是用來指明訪問許可權,這裡選擇所有許可權。
  3. 呼叫Windows API OpenService() 來開啟將要刪除的服務,如果開啟成功,則返回該服務的控制程式碼;否則返回IntPtr.Zero
  4. 呼叫Windows API DeleteService()刪除該服務
  5. 關閉新建立服務的控制程式碼
  6. 關閉Service Control Manager 控制程式碼
  7. 再次呼叫ServiceController去確認下該服務是否存在,如果還未被刪除並且處於執行狀態,那麼就先停止該服務,以便於Service Control Manager 來刪除該服務。 最後返回刪除的結果,是否正確刪除服務。

      當然,在DeleteService()中,我們可以不判斷該服務是否存在,因為如果該服務不存在,那麼呼叫OpenService API就會返回IntPtr.Zero,這樣的話,我們就不需要呼叫DeleteService API來刪除服務了,具體怎麼處理,那就看如何的需求了!

       到此,CreateService()和DeleteService()兩個重量級的方法封裝完成,至於其他的比如操作已經存在的服務,Pause(),Stop(),Start()等等,以及修改Description,查詢狀態,等等,所牽涉到的API都在上面,並且這些功能,ServiceController都已經提供了,所以我就不再封裝了,可以直接用ServiceController的方法。

      既然我們可以通過程式碼來註冊和登出服務,那麼我們在做與Windows Service相關的程式的時候,直接掉用程式碼來註冊和登出,相比用命令列來的更快更省事,而且便於除錯。

    本文所用到的原始碼(沒有注意重構,但是程式碼通過測試了,只需要把這幾個cs檔案新增到自己的專案裡就可以直接使用,或者自己封裝成dll,都行,):

ServiceControllerExtension.rar

相關文章