3.3.2 服务控制管理器的实现

服务控制管理器的开发过程与注册表启动管理器的开发过程比较类似,主要也是枚举服务并显示到列表控件中。至于对服务状态的控制,是通过服务相关的API函数来完成的。这次首先来编写代码,希望读者能够掌握服务相关的API函数。在代码的后面,会对开发服务管理器涉及的API进行相应的解释。

:学习API函数的使用,MSDN是最好的老师,详细、透彻、权威。在编程的道路上,要不断通过阅读别人的代码来提高自己的编程能力,就需要自己来掌握陌生的API函数,那时一定要想起查阅MSDN。

1.服务的类型

服务控制管理器的界面都已经熟悉了,界面的布局可以按照自己的方式进行调整。在枚举服务的时候,将“Win32应用程序服务”和“驱动程序服务”分开枚举,这样有助于对各种服务的了解。

枚举这两类服务的主要差别在于调用EnumServicesStatus()函数时为其传递的第二个参数。如果枚举“Win32应用程序服务”,那么传递的参数为SERVICE_WIN32;如果枚举“驱动程序服务”,那么传递的参数为SERVICE_DRIVER。这两个参数其实是系统定义的宏,该宏定义在WinNt.h头文件中,具体定义如下:

        #define SERVICE_DRIVER                    (SERVICE_KERNEL_DRIVER | \
                                                SERVICE_FILE_SYSTEM_DRIVER | \
                                                SERVICE_RECOGNIZER_DRIVER)
        #define SERVICE_WIN32                      (SERVICE_WIN32_OWN_PROCESS | \
                                                SERVICE_WIN32_SHARE_PROCESS)

SERVICE_DRIVER和SERVICE_WIN32是其他宏的组合,而那些宏又有具体的值。下面解释一下其他宏的含义。

SERVICE_DRIVER宏由3个宏组成,具体如下:

        #define SERVICE_KERNEL_DRIVER       0x00000001     // 设备驱动程序
        #define SERVICE_FILE_SYSTEM_DRIVER  0x00000002    // 内核模式文件系统驱动程序
        #define SERVICE_RECOGNIZER_DRIVER  0x00000008     // 文件系统识别器驱动程序

SERVICE_WIN32宏由两个宏组成,具体如下:

        #define SERVICE_WIN32_OWN_PROCESS    0x00000010    // 独占一个进程的服务
        #define SERVICE_WIN32_SHARE_PROCESS  0x00000020  // 与其他服务共享一个进程的服务

除了以上两个宏以外,还有其他的服务可以进行枚举,这里就不介绍了(其实有些类型的服务,笔者也不知道)。如果想要枚举全部类型的服务,那么使用SERVICE_TYPE_ALL宏即可,该宏的定义如下:

        #define SERVICE_TYPE_ALL                  (SERVICE_WIN32  | \
                                                SERVICE_ADAPTER | \
                                                SERVICE_DRIVER  | \
                                                SERVICE_INTERACTIVE_PROCESS)

2.服务的枚举函数

服务的枚举所使用的API函数是EnumServicesStatus(),该函数中需要指定枚举的类型分别是SERVICE_DRIVER和SERVICE_WIN32。

具体来看服务枚举的函数,代码如下:

        VOID CManageServicesDlg::ShowServiceList(DWORD dwServiceType)
        {
            m_ServiceList.DeleteAllItems();
            // 打开服务管理器
            SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
            if ( NULL == hSCM )
            {
                AfxMessageBox("OpenSCManger Error!");
                return ;
            }

            DWORD ServiceCount = 0;
            DWORD dwSize = 0;
            LPENUM_SERVICE_STATUS lpInfo;
            // 第一次调用
            BOOL bRet = EnumServicesStatus(hSCM,
                          dwServiceType, SERVICE_STATE_ALL,
                          NULL, 0, &dwSize,
                          &ServiceCount, NULL);

            // 由于没有给定接收服务列表的缓冲区,这里必定会调用失败
            // 失败的原因是ERROR_MORE_DATA
            // 说明需要更大的缓冲区来保存数据
            if ( !bRet && GetLastError() == ERROR_MORE_DATA )
            {
                // 分配缓冲区,保存服务列表
                lpInfo = (LPENUM_SERVICE_STATUS)(new BYTE[dwSize]);
                bRet = EnumServicesStatus(hSCM,
                          dwServiceType, SERVICE_STATE_ALL,
                          (LPENUM_SERVICE_STATUS)lpInfo,
                          dwSize, &dwSize,
                          &ServiceCount, NULL);
                if ( !bRet )
                {
                    CloseServiceHandle(hSCM);
                    return ;
                }
                //逐个获取数据,添加至列表控件
                for ( DWORD i = 0; i < ServiceCount; i ++)
                {
                    CString str;
                    m_ServiceList.InsertItem(i, lpInfo[i].lpServiceName);
                    m_ServiceList.SetItemText(i, 1, lpInfo[i].lpDisplayName);
                    switch ( lpInfo[i].ServiceStatus.dwCurrentState )
                    {
                    case SERVICE_PAUSED:
                        {
                            m_ServiceList.SetItemText(i, 2, "暂停");
                            break;
                        }
                    case SERVICE_STOPPED:
                        {
                            m_ServiceList.SetItemText(i, 2, "停止");
                            break;
                        }
                    case SERVICE_RUNNING:
                        {
                                m_ServiceList.SetItemText(i, 2, "运行");
                                break;
                            }
                        default:
                            {
                                m_ServiceList.SetItemText(i, 2, "其他");
                            }
                        }
                    }
                    // 释放申请的空间
                    delete lpInfo;
                }

                // 关闭服务管理器句柄
                CloseServiceHandle(hSCM);
            }

该函数有一个参数,用来指明枚举类型是“Win32应用程序服务”,还是“驱动程序服务”。该函数的默认参数为“Win32应用程序服务”,该函数的定义如下:

        VOID ShowServiceList(DWORD dwServiceType = SERVICE_WIN32);

3.枚举服务相关API函数解释

(1)打开和关闭服务管理器。

打开服务管理器的函数定义如下:

        SC_HANDLE OpenSCManager(
          LPCTSTR lpMachineName,    // computer name
          LPCTSTR lpDatabaseName,  // SCM database name
          DWORD dwDesiredAccess     // access type
        );

参数说明如下。

lpMachineName:指向欲打开服务控制管理器数据库的目标主机名,本机则设置为NULL。

lpDatabaseName:指向目标主机SCM数据库名字的字符串。

dwDesiredAccess:指定对SCM数据库的访问权限。

该函数调用成功,返回一个SCM句柄,否则返回NULL。

:SCM是服务控制管理器的意思,它是系统服务的一个组成部分,跟开发的软件不是一个概念。由于这里不是编写一个具体的服务,而只是对系统现有的服务进行枚举,因此,一些概念性的知识希望读者可以自行查阅相关的资料。

关闭服务句柄的函数定义如下:

        BOOL CloseServiceHandle(
          SC_HANDLE hSCObject    // handle to service or SCM object
        );

该函数用来关闭由OpenSCManager()和OpenService()打开的句柄。

(2)服务的枚举函数。

枚举服务的函数定义如下:

        BOOL EnumServicesStatus(
          SC_HANDLE hSCManager,                // handle to SCM database
          DWORD dwServiceType,                 // service type
          DWORD dwServiceState,                // service state
          LPENUM_SERVICE_STATUS lpServices, // status buffer
          DWORD cbBufSize,                      // size of status buffer
          LPDWORD pcbBytesNeeded,             // buffer size needed
          LPDWORD lpServicesReturned,         // number of entries returned
          LPDWORD lpResumeHandle               // next entry
        );

参数说明如下。

hSCManager:OpenSCManager()函数返回的句柄。

dwServiceType:指定枚举的服务类型,也就是自定义函数的参数。

dwServiceState:枚举指定状态的服务。

lpServices:指向ENUM_SERVICE_STATUS类型的指针。

cbBufSize:指定缓冲区的大小。

pcbBytesNeeded:返回实际使用的内存空间大小。

lpServicesReturned:返回枚举服务的个数。

lpResumeHandle:返回枚举是否成功。

ENUM_SERVICE_STATUS结构体的定义如下:

        typedef struct _ENUM_SERVICE_STATUS {
          LPTSTR lpServiceName;
          LPTSTR lpDisplayName;
          SERVICE_STATUS ServiceStatus;
        } ENUM_SERVICE_STATUS, *LPENUM_SERVICE_STATUS;

SERVICE_STATUS结构体的定义如下:

        typedef struct _SERVICE_STATUS {
            DWORD    dwServiceType;
            DWORD    dwCurrentState;
            DWORD    dwControlsAccepted;
            DWORD    dwWin32ExitCode;
            DWORD    dwServiceSpecificExitCode;
            DWORD    dwCheckPoint;
            DWORD    dwWaitHint;
        } SERVICE_STATUS, *LPSERVICE_STATUS;

代码中两次调用EnumServicesStatus()函数。第1次没有传递第4个和第5个参数,使得函数返回FALSE,用GetLastError()得到错误的原因为ERROR_MORE_DATA。这时,第6个参数pcbBytesNeeded返回实际需要使用的内存大小,这样可以通过new动态申请所需的堆空间。以这种方式来获取实际所需缓冲区大小的情况是比较多的,请读者一定要理解。

4.服务的启动与停止

对服务的操作只介绍两种,一种是启动服务,另一种是停止服务,也就是改变服务的状态。对于笔者来说,经常会用到停止服务的操作,因为系统中有很多笔者用不到的服务,但是它仍然会随着系统的启动而启动,这样既会影响到系统的启动速度,也会占用宝贵的系统资源。因此,没有用的系统服务最好将其停止(其实真正停止服务是改变它的启动状态,而不是这里介绍的运行状态)。

启动服务的代码如下:

        void CManageServicesDlg::OnBtnStart()
        {
              // TODO: Add your control notification handler code here
            // 选中服务的索引
            POSITION Pos = m_ServiceList.GetFirstSelectedItemPosition();
            int nSelect = -1;

            while ( Pos )
            {
                nSelect = m_ServiceList.GetNextSelectedItem(Pos);
            }

            if ( -1 == nSelect )
            {
            AfxMessageBox("请选择要启动的服务");
            return ;
        }

        // 获取选中服务的服务名
        char szServiceName[MAXBYTE] = { 0 };
        m_ServiceList.GetItemText(nSelect, 0, szServiceName, MAXBYTE);

        SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
        if ( NULL == hSCM )
        {
            AfxMessageBox("OpenSCManager Error");
            return ;
        }

        // 打开指定的服务
        SC_HANDLE hSCService = OpenService(hSCM, szServiceName, SERVICE_ALL_ACCESS);

        // 启动服务
        BOOL bRet = StartService(hSCService, 0, NULL);
        if ( bRet == TRUE )
        {
            m_ServiceList.SetItemText(nSelect, 2, "运行");
        }
        else
        {
            AfxMessageBox("启动失败!");
        }

        CloseServiceHandle(hSCService);
        CloseServiceHandle(hSCM);
    }

停止服务的代码如下:

    void CManageServicesDlg::OnBtnStop()
    {
          // TODO: Add your control notification handler code here
        // 选中服务的索引
        // 此部分操作与启动服务相同,为节省篇幅,此处省略
        // ……

        SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
        if ( NULL == hSCM )
        {
            AfxMessageBox("OpenSCManager Error");
            return ;
        }

        // 打开指定的服务
        SC_HANDLE hSCService = OpenService(hSCM, szServiceName, SERVICE_ALL_ACCESS);
        SERVICE_STATUS ServiceStatus;
        // 停止服务
        BOOL bRet = ControlService(hSCService, SERVICE_CONTROL_STOP, &ServiceStatus);
        if ( bRet == TRUE )
        {
            m_ServiceList.SetItemText(nSelect, 2, "停止");
        }
        else
        {
            AfxMessageBox("停止失败!");
        }

        CloseServiceHandle(hSCService);
        CloseServiceHandle(hSCM);
    }

5.启动与停止服务相关API函数解释

打开指定服务的函数定义如下:

          SC_HANDLE OpenService(
            SC_HANDLE hSCManager,  // handle to SCM database
            LPCTSTR lpServiceName, // service name
            DWORD dwDesiredAccess  // access
          );

参数说明如下。

hSCManager:指定由OpenSCManager()函数打开的服务句柄。

lpServiceName:指定要打开的服务的名称。

dwDesiredAccess:对要打开服务的访问权限,这里为了方便,指定为SC_MANAGER_ALL_ACCESS。

启动服务的函数定义如下:

          BOOL StartService(
            SC_HANDLE hService,              // handle to service
            DWORD dwNumServiceArgs,          // number of arguments
            LPCTSTR *lpServiceArgVectors    // array of arguments
          );

参数说明如下。

hService:指定要启动服务的句柄,该句柄由OpenService()返回。

dwNumServiceArgs:指向启动服务所需的参数个数。

lpServiceArgVectors:指向启动服务的参数。

停止服务的函数定义如下:

          BOOL ControlService(
            SC_HANDLE hService,                  // handle to service
            DWORD dwControl,                      // control code
            LPSERVICE_STATUS lpServiceStatus  // status information
          );

参数说明如下。

hService:指定一个由OpenService()打开的服务句柄。

dwControl:指定要发送的控制码。

lpServiceStatus:返回服务的状态。

ControlService()可以对服务进行多种控制操作,每种控制操作对应一种控制码。当要停止服务时,使用的控制码为SERVICE_CONTROL_STOP。在这里读者一定要注意,停止服务不要想当然的使用StopService()这样的函数,因为没有这个API函数。

:更多与服务相关的操作,可参考金山卫士开源代码,具体目录为oss/ksm/src/src_ bksafe/beikeutils下的winservice.cpp和winservice.h两个文件。