CSharp Tips:調用API註冊和註銷Windows Service

0、寫在前面
    DotNET平臺下的類庫封裝的相當完善,普通的應用完全可以利用類庫完成所有的工作。對於Windows Service的支持也是一樣,只需要繼承DotNET下提供的ServiceBase就可以創建Windows的Service,調用ServiceControl類的方法就可以控制Service的啓動和關閉,非常容易。
    然而生成了一個Service類型的應用程序之後,必須在SCM(Service Control Manager)中註冊,才能夠被當作一個Service被系統調用。但是對於Service的註冊和註銷卻比較麻煩,DotNET的確也提供了對應的類ServiceInstaller和ServiceProcessInstaller,但是隻能夠和Installer的安裝過程集成使用,不能夠單獨在普通應用程序中調用,也就是說爲了安裝一個Service用作測試,我不得不創建一個安裝包。
    怎樣利用API向SCM註冊以及註銷一個Service呢,下面就簡單的介紹一下。
 
1、實現概述
    打開Control Panel中Service的管理工具,可以看到本機所有已註冊的Service,選擇一個Service的屬性會發現一個Service有很多設置項:名稱、描述、類型、運行帳號等。所有這些都可以通過調用API函數來設定。
    在Service的API中,每一個服務的實例通過句柄來表示,而需要服務的句柄之前必須要先要的到SCM的句柄(Handle),所以所有的調用都是通過OpenSCManager開始,成功的調用OpenSCManager後將獲得一個SCM的句柄。如果是註冊Service,那麼利用SCM句柄調用CreateService來創建一個新的服務;如果是註銷Service,則調用OpenService函數,獲得一個已經存在的Service的句柄,然後利用這個服務句柄調用DeleteService註銷服務。最後通過CloseServiceHandle關閉Service的句柄和SCM的句柄。
    過程很簡單,在CSharp下比較麻煩的地方就是API的聲明,下面會一一提到。
 
2、註冊Service
    前面已經提到註冊Service一共用到了三個API,OpenSCManager、CreateService和CloseServiceHandle。使用之前先介紹一下這些API的聲明。
  [DllImport("advapi32.dll")]
  public static extern System.IntPtr OpenSCManager(
    System.String lpMachineName,
    System.String lpDatabaseName,
    System.UInt32 dwDesiredAccess
   );
  [DllImport("advapi32.dll",EntryPoint = "CreateServiceA")]
  public static extern System.IntPtr CreateService(
    System.IntPtr hSCManager,
    System.String lpServiceName,
    System.String lpDisplayName,
    System.UInt32 dwDesiredAccess,
    System.UInt32 dwServiceType,
    System.UInt32 dwStartType,
    System.UInt32 dwErrorControl,
    System.String lpBinaryPathName,
    System.String lpLoadOrderGroup,
    System.IntPtr lpdwTagId,
    System.String lpDependencies,
    System.String lpServiceStartName,
    System.String lpPassword
   );
  [DllImport("advapi32.dll")]
  public static extern System.Boolean CloseServiceHandle(
    System.IntPtr hSCObject
   );
    這個了關於Service的API都是在Advapi32.dll中實現的,函數的原型可以自行查找頭文件,在Winsvc和Winbase中。
    熟悉Windows編程的話一定會了解,句柄類型就是一個32位的整型,在DotNET下用IntPtr來聲明,DWORD對應UINT32,LPCSTR對應String類型,唯一需要強調的是CreateService這個函數的lpdwTagId,是一個DWORD*,這裏聲明也IntPtr,因爲在調用中絕大多數情況下傳遞NULL值,如果用out UINT32,無法傳遞NULL值。
    仔細看CreateService中聲明可以發現,這個函數真的可以做很多事情,其中包括Service的名稱(lpServiceName,服務的標識,調用OpenSerive等函數時用到)、顯示名(lpDisplayName,就是我們在Service的管理工具中看到的名稱)、服務類型(dwServiceType,指定服務的運行方式:獨立進程、共享進程、驅動程序還是交互式登錄模式等等)、啓動類型(dwStartType,自動、手動還是禁止等等)、服務失敗的嚴重性(dwErrorControl)、實現服務代碼的二進制文件的路徑(lpBinaryPathName)、加載順序組的名稱(lpLoadOrderGroup)、接受Tag標誌碼(lpdwTagId)、依賴服務的名稱組(lpDependencies)、啓動服務的帳號(lpServiceStartName,如果爲NULL,表示使用LocalSystem)、啓動服務帳號的口令(lpPassword)。如果調用成功,那麼將返回一個非0的句柄,表示服務註冊成功。
    看了上面的一系列屬性說明,大家也許發現還少了一樣,就是在Service的管理工具中最長最醒目的一欄Description,Description的設置需要調用另一個API,如下:
  public static extern System.Boolean ChangeServiceConfig2(
    System.IntPtr hService,
    System.UInt32 dwInfoLevel,
    ref System.String lpInfo
   );
    其中lpInfo的聲明原型是一個LPVOID,如果設置Description屬性的話指向的是一個結構:
 typedef struct _SERVICE_DESCRIPTION {
   LPTSTR
lpDescription;
 } SERVICE_DESCRIPTION, *LPSERVICE_DESCRIPTION;
    這個結構裏面包含了一個字符指針,也就是說需要在函數調用時傳遞一個指向字符指針的指針,偷懶起見,ref System.String就足夠了,無需再定義結構了,呵呵!
    完整的例子如下,常量定義見本文最後:
  private static System.Boolean RegistService()
  {
   System.Boolean  fRet = false;
   System.IntPtr  hServiceManager = IntPtr.Zero,hService = IntPtr.Zero;
   System.String  sServicePath = null,sDesc = null;
 
   sServicePath = Application.StartupPath + @"/sampleservice.exe";
   hServiceManager = OpenSCManager(Environment.MachineName,null,SC_MANAGER_ALL_ACCESS);
   if (hServiceManager != IntPtr.Zero)
   {
    hService = CreateService(hServiceManager,"sampleservice","Sample Service",SERVICE_ALL_ACCESS,
     SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS,SERVICE_AUTO_START,SERVICE_ERROR_NORMAL,
     sServicePath,null,IntPtr.Zero,null,null,null);
    if (hService != IntPtr.Zero)
    {
     sDesc = "This is a sample service.";
     fRet = ChangeServiceConfig2(hService,SERVICE_CONFIG_DESCRIPTION,ref sDesc);
     CloseServiceHandle(hService);
     hService = IntPtr.Zero;
    }
    CloseServiceHandle(hServiceManager);
    hServiceManager = IntPtr.Zero;
   }
   return fRet;
  }
3、註銷Service
    相對於註冊,註銷就簡單很多了,用到了OpenSCManager、OpenService、DeleteService和CloseServiceHandle四個API,另兩個API的聲明如下:
  [DllImport("advapi32.dll")]
  public static extern System.IntPtr OpenService(
    System.IntPtr hSCManager,
    System.String lpServiceName,
    System.UInt32 dwDesiredAccess
   );
  [DllImport("advapi32.dll")]
  public static extern System.Boolean DeleteService(
    System.IntPtr hService
   );
    比較簡單沒什麼可多說的看一個完整的例子:
  private static System.Boolean UnRegistService()
  {
   System.Boolean  fRet = false;
   System.IntPtr  hServiceManager = IntPtr.Zero,hService = IntPtr.Zero;
 
   hServiceManager = OpenSCManager(Environment.MachineName,null,SC_MANAGER_ALL_ACCESS);
   if (hServiceManager != IntPtr.Zero)
   {
    hService = OpenService(hServiceManager,"sampleservice",SERVICE_ALL_ACCESS);
    if (hService != IntPtr.Zero)
    {
     fRet = DeleteService(hService);
     CloseServiceHandle(hService);
     hService = IntPtr.Zero;
    }
    CloseServiceHandle(hServiceManager);
    hServiceManager = IntPtr.Zero;
   }
   return fRet;
  }
4、最後
    可以在實現Service的工程中增加一下額外的處理,Main函數中判斷一下調用參數,如果是“-I”,則表示註冊Service,如果是“-U”表示註銷Service,反之則作爲Service正常運行。
 
5、API和常量的聲明
    本文所用到的所有API和常量如下:
  // declare APIs for service
  [DllImport("advapi32.dll")]
  public static extern System.IntPtr OpenSCManager(
    System.String lpMachineName,
    System.String lpDatabaseName,
    System.UInt32 dwDesiredAccess
   );
  [DllImport("advapi32.dll",EntryPoint = "CreateServiceA")]
  public static extern System.IntPtr CreateService(
    System.IntPtr hSCManager,
    System.String lpServiceName,
    System.String lpDisplayName,
    System.UInt32 dwDesiredAccess,
    System.UInt32 dwServiceType,
    System.UInt32 dwStartType,
    System.UInt32 dwErrorControl,
    System.String lpBinaryPathName,
    System.String lpLoadOrderGroup,
    System.IntPtr lpdwTagId,
    System.String lpDependencies,
    System.String lpServiceStartName,
    System.String lpPassword
   );
  [DllImport("advapi32.dll")]
  public static extern System.IntPtr OpenService(
    System.IntPtr hSCManager,
    System.String lpServiceName,
    System.UInt32 dwDesiredAccess
   );
  [DllImport("advapi32.dll")]
  public static extern System.Boolean DeleteService(
    System.IntPtr hService
   );
  [DllImport("advapi32.dll")]
  public static extern System.Boolean CloseServiceHandle(
    System.IntPtr hSCObject
   );
  [DllImport("advapi32.dll")]
  public static extern System.Boolean ChangeServiceConfig2(
    System.IntPtr hService,
    System.UInt32 dwInfoLevel,
    ref System.String lpInfo
   );
 
  public const System.UInt32 STANDARD_RIGHTS_REQUIRED = 0xF0000;
  // Service Control Manager object specific access types
  public const System.UInt32 SC_MANAGER_CONNECT = 0x0001;
  public const System.UInt32 SC_MANAGER_CREATE_SERVICE = 0x0002;
  public const System.UInt32 SC_MANAGER_ENUMERATE_SERVICE = 0x0004;
  public const System.UInt32 SC_MANAGER_LOCK = 0x0008;
  public const System.UInt32 SC_MANAGER_QUERY_LOCK_STATUS = 0x0010;
  public const System.UInt32 SC_MANAGER_MODIFY_BOOT_CONFIG = 0x0020;
  public const System.UInt32 SC_MANAGER_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED |
    SC_MANAGER_CONNECT            |
    SC_MANAGER_CREATE_SERVICE     |
    SC_MANAGER_ENUMERATE_SERVICE  |
    SC_MANAGER_LOCK               |
    SC_MANAGER_QUERY_LOCK_STATUS  |
    SC_MANAGER_MODIFY_BOOT_CONFIG;
  // Service object specific access type
  public const System.UInt32 SERVICE_QUERY_CONFIG = 0x0001;
  public const System.UInt32 SERVICE_CHANGE_CONFIG = 0x0002;
  public const System.UInt32 SERVICE_QUERY_STATUS = 0x0004;
  public const System.UInt32 SERVICE_ENUMERATE_DEPENDENTS = 0x0008;
  public const System.UInt32 SERVICE_START = 0x0010;
  public const System.UInt32 SERVICE_STOP = 0x0020;
  public const System.UInt32 SERVICE_PAUSE_CONTINUE = 0x0040;
  public const System.UInt32 SERVICE_INTERROGATE = 0x0080;
  public const System.UInt32 SERVICE_USER_DEFINED_CONTROL = 0x0100;
  public const System.UInt32 SERVICE_ALL_ACCESS = STANDARD_RIGHTS_REQUIRED     |
    SERVICE_QUERY_CONFIG         |
    SERVICE_CHANGE_CONFIG        |
    SERVICE_QUERY_STATUS         |
    SERVICE_ENUMERATE_DEPENDENTS |
    SERVICE_START                |
    SERVICE_STOP                 |
    SERVICE_PAUSE_CONTINUE       |
    SERVICE_INTERROGATE          |
    SERVICE_USER_DEFINED_CONTROL;
  // service type
  public const System.UInt32 SERVICE_KERNEL_DRIVER = 0x00000001;
  public const System.UInt32 SERVICE_FILE_SYSTEM_DRIVER = 0x00000002;
  public const System.UInt32 SERVICE_ADAPTER = 0x00000004;
  public const System.UInt32 SERVICE_RECOGNIZER_DRIVER = 0x00000008;
  public const System.UInt32 SERVICE_DRIVER = SERVICE_KERNEL_DRIVER |
    SERVICE_FILE_SYSTEM_DRIVER |
    SERVICE_RECOGNIZER_DRIVER;
  public const System.UInt32 SERVICE_WIN32_OWN_PROCESS = 0x00000010;
  public const System.UInt32 SERVICE_WIN32_SHARE_PROCESS = 0x00000020;
  public const System.UInt32 SERVICE_WIN32 = SERVICE_WIN32_OWN_PROCESS |
    SERVICE_WIN32_SHARE_PROCESS;
  public const System.UInt32 SERVICE_INTERACTIVE_PROCESS = 0x00000100;
  public const System.UInt32 SERVICE_TYPE_ALL = SERVICE_WIN32  |
    SERVICE_ADAPTER |
    SERVICE_DRIVER  |
    SERVICE_INTERACTIVE_PROCESS;
  // Start Type
  public const System.UInt32 SERVICE_BOOT_START = 0x00000000;
  public const System.UInt32 SERVICE_SYSTEM_START = 0x00000001;
  public const System.UInt32 SERVICE_AUTO_START = 0x00000002;
  public const System.UInt32 SERVICE_DEMAND_START = 0x00000003;
  public const System.UInt32 SERVICE_DISABLED = 0x00000004;
  // Error control type
  public const System.UInt32 SERVICE_ERROR_IGNORE = 0x00000000;
  public const System.UInt32 SERVICE_ERROR_NORMAL = 0x00000001;
  public const System.UInt32 SERVICE_ERROR_SEVERE = 0x00000002;
  public const System.UInt32 SERVICE_ERROR_CRITICAL = 0x00000003;
  // Info levels for ChangeServiceConfig2 and QueryServiceConfig2
  public const System.UInt32 SERVICE_CONFIG_DESCRIPTION = 1;
  public const System.UInt32 SERVICE_CONFIG_FAILURE_ACTIONS = 2;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章