其实本应该把 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也罗列出来:

 

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:

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   

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(nullnull, (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去停止该服务)。

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(nullnull, (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

posted on 2010-08-22 18:50  博弈无涯  阅读(4861)  评论(5编辑  收藏  举报