Windows API 之 Windows Service

      其实本应该把 AutoExcuteJob 系列写完,再来谈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.gif Windows 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.gif Struct代码
    [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.gif CreateService代码
  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 ControlService function 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.gif DeleteService 代码
   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

转载于:https://www.cnblogs.com/bmwchampion/archive/2010/08/22/WindowServiceAPI.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值