65.9K
CodeProject 正在变化。 阅读更多。
Home

关于 Windows 服务

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.72/5 (19投票s)

2001年11月16日

CPOL

4分钟阅读

viewsIcon

140789

downloadIcon

2118

关于 Windows 服务的讨论及示例

引言

编写服务器端程序与普通程序略有不同。因为您需要处理很多问题,例如安全性。微软建议服务器端应用程序应以服务的形式编写。

服务也是 Windows 程序。区别在于系统而不是用户来运行服务。另一个主要区别是服务没有用户界面。因此,服务程序可以由 main 或 WinMain 启动。Windows 自带的服务控制程序负责运行、注册和执行所有 Windows 服务的操作。所有服务的信息都存储在 Windows 注册表中,键为

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services

不建议直接访问此注册表项。要访问有关使用 Windows API 的信息,这些 API 会操作有关所有服务的信息。首先,您必须使用 OpenSCManager 打开 SCM(服务控制管理器),它有三个参数:第一个是您要在其上打开 SCM 的计算机名称。对于本地计算机,只需传递 NULL。第二个参数是数据库名称,要获取活动的数据库,只需传递 NULL。第三个参数是所需的访问权限。

这是一个显示此内容的简短控制台程序

程序 1

#include &ltwindows.h>
#include &ltiostream.h>

void ErrorDescription(DWORD p_dwError);

int main()
{
    SC_HANDLE hHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

    if (NULL == hHandle) {
        ErrorDescription(GetLastError());
        return -1;
    }
    else {
        cout << "Open SCM sucessfully" << endl;
    }

    if (!CloseServiceHandle(hHandle)) {
        ErrorDescription(GetLastError());
    }
    else {
        cout << "Close SCM sucessfully" << endl;
    }

    return 0;
}

// get the description of error
void ErrorDescription(DWORD p_dwError) {
    
    HLOCAL hLocal = NULL;
    
    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL, p_dwError, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),(LPTSTR)&hLocal, 
        0, NULL);
    
    MessageBox(NULL, (LPCTSTR)LocalLock(hLocal), TEXT("Error"), MB_OK | MB_ICONERROR);
    LocalFree(hLocal);
}

要获取有关所有服务的信息,使用 EnumServicesStatus。此函数需要 8 个参数。第一个是 SCM 的句柄。第二个是服务类型,您是想查看 Win32 服务、设备驱动程序还是两者兼有。第三个是服务状态,其值可以是 SERVICE_ACTIVESERVICE_INACTIVESERVICE_STATE_ALL,分别用于获取有关活动、非活动或所有服务的信息。第四个是 ENUM_SERVICE_STATUS 结构。

ENUM_SERVICE_STATUS 定义在 WinSvc.h 文件中,如下所示:

typedef struct _ENUM_SERVICE_STATUSW {
    LPWSTR         lpServiceName;
    LPWSTR         lpDisplayName;
    SERVICE_STATUS ServiceStatus;
} ENUM_SERVICE_STATUSW, *LPENUM_SERVICE_STATUSW;

此结构体的第一个成员是服务名称,第二个是其显示名称。第三个参数也是定义在同一文件 WinSvc.h 中的结构体,如下所示:

//
// Service Status Structure
//

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

第五个参数是用于存储服务信息的缓冲区大小。这里的大小是 sizeof(ENUM_SERVICE_STATUS)。最后三个参数是输出变量,返回所需的字节数、服务条目数和下一个服务条目数。

我在最后一个程序中添加了一些代码来获取服务信息

程序 2

#include &ltwindows.h>
#include &ltiostream.h>

void ErrorDescription(DWORD p_dwError);

int main()
{
    SC_HANDLE hHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

    if (NULL == hHandle) {
        ErrorDescription(GetLastError());
        return -1;
    }
    else {
        cout << "Open SCM sucessfully" << endl;
    }

    ENUM_SERVICE_STATUS service;
    
    DWORD dwBytesNeeded = 0;
    DWORD dwServicesReturned = 0;
    DWORD dwResumedHandle = 0;
    DWORD dwServiceType = SERVICE_WIN32 | SERVICE_DRIVER;

    // Query services
    BOOL retVal = EnumServicesStatus(hHandle, dwServiceType, SERVICE_STATE_ALL, 
        &service, sizeof(ENUM_SERVICE_STATUS), &dwBytesNeeded, &dwServicesReturned,
        &dwResumedHandle);

    if (!retVal) {
        ErrorDescription(GetLastError());
    }

    if (!CloseServiceHandle(hHandle)) {
        ErrorDescription(GetLastError());
    }
    else {
        cout << "Close SCM sucessfully" << endl;
    }

    return 0;
}

// get the description of error
void ErrorDescription(DWORD p_dwError) {
    
    HLOCAL hLocal = NULL;
    
    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL, p_dwError, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),(LPTSTR)&hLocal, 
        0, NULL);
    
    MessageBox(NULL, (LPCTSTR)LocalLock(hLocal), TEXT("Error"), MB_OK | MB_ICONERROR);
    LocalFree(hLocal);
}

此程序的输出是一个消息框,显示消息“提供了更多数据”。要获取原因,只需在调用 EnumServicesStatus 后在程序中添加两行。

cout << "Bytes needed for buffer" << dwBytesNeeded << endl;
cout << "Bytes given for buffer" << sizeof(ENUM_SERVICE_STATUS) << endl;

这些语句的输出应该不同,以说明为什么我们收到此错误消息。现在分配由 dwBytesNeeded 返回所需大小的缓冲区,并再次调用此函数以获取正确的结果。

添加此代码以显示服务名称及其显示名称。

    ENUM_SERVICE_STATUS service;
    
    DWORD dwBytesNeeded = 0;
    DWORD dwServicesReturned = 0;
    DWORD dwResumedHandle = 0;
    DWORD dwServiceType = SERVICE_WIN32 | SERVICE_DRIVER;

    // Query services
    BOOL retVal = EnumServicesStatus(hHandle, dwServiceType, SERVICE_STATE_ALL, 
        &service, sizeof(ENUM_SERVICE_STATUS), &dwBytesNeeded, &dwServicesReturned,
        &dwResumedHandle);

    if (!retVal) {
        // Need big buffer
        if (ERROR_MORE_DATA == GetLastError()) {
            // Set the buffer
            DWORD dwBytes = sizeof(ENUM_SERVICE_STATUS) + dwBytesNeeded;
            ENUM_SERVICE_STATUS* pServices = NULL;
            pServices = new ENUM_SERVICE_STATUS [dwBytes];

            // Now query again for services
            EnumServicesStatus(hHandle, SERVICE_WIN32 | SERVICE_DRIVER, 
                SERVICE_STATE_ALL, pServices, dwBytes, &dwBytesNeeded, 
                &dwServicesReturned, &dwResumedHandle);

            // now traverse each service to get information
            for (unsigned iIndex = 0; iIndex < dwServicesReturned; iIndex++) {
                
                cout << TEXT("Display Name") << (pServices + iIndex)->lpDisplayName << TEXT('\t');
                cout << TEXT("Service Name") << (pServices + iIndex)->lpServiceName << endl;                                    
            }

            delete [] pServices;
            pServices = NULL;

        }
        // there is any other reason
        else {
            ErrorDescription(GetLastError());
        }
    }

这是一个完整的程序,显示服务名称及其显示名称。

程序 3

#include &ltwindows.h>
#include &ltiostream.h>

void ErrorDescription(DWORD p_dwError);

int main()
{
    SC_HANDLE hHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);

    if (NULL == hHandle) {
        ErrorDescription(GetLastError());
        return -1;
    }
    else {
        cout << "Open SCM sucessfully" << endl;
    }

    ENUM_SERVICE_STATUS service;
    
    DWORD dwBytesNeeded = 0;
    DWORD dwServicesReturned = 0;
    DWORD dwResumedHandle = 0;
    DWORD dwServiceType = SERVICE_WIN32 | SERVICE_DRIVER;

    // Query services
    BOOL retVal = EnumServicesStatus(hHandle, dwServiceType, SERVICE_STATE_ALL, 
        &service, sizeof(ENUM_SERVICE_STATUS), &dwBytesNeeded, &dwServicesReturned,
        &dwResumedHandle);

    if (!retVal) {
        // Need big buffer
        if (ERROR_MORE_DATA == GetLastError()) {
            // Set the buffer
            DWORD dwBytes = sizeof(ENUM_SERVICE_STATUS) + dwBytesNeeded;
            ENUM_SERVICE_STATUS* pServices = NULL;
            pServices = new ENUM_SERVICE_STATUS [dwBytes];

            // Now query again for services
            EnumServicesStatus(hHandle, SERVICE_WIN32 | SERVICE_DRIVER, SERVICE_STATE_ALL, 
                pServices, dwBytes, &dwBytesNeeded, &dwServicesReturned, &dwResumedHandle);

            // now traverse each service to get information
            for (unsigned iIndex = 0; iIndex < dwServicesReturned; iIndex++) {
                
                cout << TEXT("Display Name") << (pServices + iIndex)->lpDisplayName << TEXT('\t');
                cout << TEXT("Service Name") << (pServices + iIndex)->lpServiceName << endl;                                    
            }

            delete [] pServices;
            pServices = NULL;

        }
        // there is any other reason
        else {
            ErrorDescription(GetLastError());
        }
    }

    if (!CloseServiceHandle(hHandle)) {
        ErrorDescription(GetLastError());
    }
    else {
        cout << "Close SCM sucessfully" << endl;
    }

    return 0;
}

// get the description of error
void ErrorDescription(DWORD p_dwError) {
    
    HLOCAL hLocal = NULL;
    
    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL, p_dwError, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),(LPTSTR)&hLocal, 
        0, NULL);
    
    MessageBox(NULL, (LPCTSTR)LocalLock(hLocal), TEXT("Error"), MB_OK | MB_ICONERROR);
    LocalFree(hLocal);
}

我们可以通过 QueryServiceConfig 获取有关服务的更多信息,例如其路径、启动类型等。要使用此函数,首先通过 OpenService 获取服务句柄。OpenService 接受三个参数:SCM 的句柄、服务名称和所需的访问权限。以获取服务句柄。

SC_HANDLE hService = OpenService(hHandle, (pServices + iIndex)->lpServiceName, 
    SERVICE_ALL_ACCESS);

// if can not get handle of service
if (!hService) {
    ErrorDescription(GetLastError());
}

获取服务句柄后,使用 QueryServiceConfig 获取有关服务的更多信息。QueryServiceConfig 接受四个参数。第一个是服务的句柄,第二个是 QUERY_SERVICE_CONFIG 的地址,它在 WinSvc.h 中定义如下:

typedef struct _QUERY_SERVICE_CONFIGA {
    DWORD   dwServiceType;
    DWORD   dwStartType;
    DWORD   dwErrorControl;
    LPSTR   lpBinaryPathName;
    LPSTR   lpLoadOrderGroup;
    DWORD   dwTagId;
    LPSTR   lpDependencies;
    LPSTR   lpServiceStartName;
    LPSTR   lpDisplayName;
} QUERY_SERVICE_CONFIGA, *LPQUERY_SERVICE_CONFIGA;
typedef struct _QUERY_SERVICE_CONFIGW {
    DWORD   dwServiceType;
    DWORD   dwStartType;
    DWORD   dwErrorControl;
    LPWSTR  lpBinaryPathName;
    LPWSTR  lpLoadOrderGroup;
    DWORD   dwTagId;
    LPWSTR  lpDependencies;
    LPWSTR  lpServiceStartName;
    LPWSTR  lpDisplayName;
} QUERY_SERVICE_CONFIGW, *LPQUERY_SERVICE_CONFIGW;
#ifdef UNICODE
typedef QUERY_SERVICE_CONFIGW QUERY_SERVICE_CONFIG;
typedef LPQUERY_SERVICE_CONFIGW LPQUERY_SERVICE_CONFIG;
#else
typedef QUERY_SERVICE_CONFIGA QUERY_SERVICE_CONFIG;
typedef LPQUERY_SERVICE_CONFIGA LPQUERY_SERVICE_CONFIG;
#endif // UNICODE

第三个参数是缓冲区大小,最后一个是输出参数,返回所需的字节数。添加此代码以显示服务的路径。

// now traverse each service to get information
for (unsigned iIndex = 0; iIndex < dwServicesReturned; iIndex++) {
    
    SC_HANDLE hService = OpenService(hHandle, (pServices + iIndex)->lpServiceName, 
        SERVICE_ALL_ACCESS);

    // if can not get handle of service
    if (!hService) {
        ErrorDescription(GetLastError());
        continue;
    }

    QUERY_SERVICE_CONFIG sc;
    DWORD dwBytesNeeded = 0;

    // Try to get information about the query
    BOOL bRetVal = QueryServiceConfig(hService, &sc, sizeof(QUERY_SERVICE_CONFIG),
        &dwBytesNeeded);

    if (!bRetVal) {
        ErrorDescription(GetLastError());
    }

    cout << TEXT("Display Name") << (pServices + iIndex)->lpDisplayName << TEXT('\t');
    cout << TEXT("Service Name") << (pServices + iIndex)->lpServiceName << TEXT('\t');
    cout << TEXT("Path Name") << sc.lpBinaryPathName << endl;
}

此程序的输出是一个错误。该错误的描述是“传递给系统调用的数据区域太小”。这里也是同样的问题。解决方案是相同的,分配根据所需字节数的缓冲区,并再次调用此函数。

这是一个完整的基于控制台的程序,显示服务路径及其名称和显示名称。

程序 4

#include &ltwindows.h>
#include &ltiostream.h>

void ErrorDescription(DWORD p_dwError);

QUERY_SERVICE_CONFIG* g_psc = NULL;

int main()
{
    SC_HANDLE hHandle = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);
    
    if (NULL == hHandle) {
        ErrorDescription(GetLastError());
        return -1;
    }
    else {
        cout << "Open SCM sucessfully" << endl;
    }
    
    ENUM_SERVICE_STATUS service;
    
    DWORD dwBytesNeeded = 0;
    DWORD dwServicesReturned = 0;
    DWORD dwResumedHandle = 0;
    DWORD dwServiceType = SERVICE_WIN32 | SERVICE_DRIVER;
    
    // Query services
    BOOL retVal = EnumServicesStatus(hHandle, dwServiceType, SERVICE_STATE_ALL, 
        &service, sizeof(ENUM_SERVICE_STATUS), &dwBytesNeeded, &dwServicesReturned,
        &dwResumedHandle);
    
    if (!retVal) {
        // Need big buffer
        if (ERROR_MORE_DATA == GetLastError()) {
            // Set the buffer
            DWORD dwBytes = sizeof(ENUM_SERVICE_STATUS) + dwBytesNeeded;
            ENUM_SERVICE_STATUS* pServices = NULL;
            pServices = new ENUM_SERVICE_STATUS [dwBytes];
            
            // Now query again for services
            EnumServicesStatus(hHandle, SERVICE_WIN32 | SERVICE_DRIVER, SERVICE_STATE_ALL, 
                pServices, dwBytes, &dwBytesNeeded, &dwServicesReturned, &dwResumedHandle);
            
            // now traverse each service to get information
            for (unsigned iIndex = 0; iIndex < dwServicesReturned; iIndex++) {
                
                SC_HANDLE hService = OpenService(hHandle, (pServices + iIndex)->lpServiceName, 
                    SERVICE_ALL_ACCESS);
                
                // if can not get handle of service
                if (!hService) {
                    ErrorDescription(GetLastError());
                    continue;
                }
                
                QUERY_SERVICE_CONFIG sc;
                DWORD dwBytesNeeded = 0;
                
                // Try to get information about the query
                BOOL bRetVal = QueryServiceConfig(hService, &sc, 
                    sizeof(QUERY_SERVICE_CONFIG), &dwBytesNeeded);
                
                if (!bRetVal) {

                    DWORD retVal = GetLastError();
                    // buffer size is small. 
                    // Required size is in dwBytesNeeded
                    if (ERROR_INSUFFICIENT_BUFFER == retVal) {
                        
                        DWORD dwBytes = sizeof(QUERY_SERVICE_CONFIG) + dwBytesNeeded;
                        g_psc = new QUERY_SERVICE_CONFIG[dwBytesNeeded];
                        
                        bRetVal = QueryServiceConfig(hService, g_psc, dwBytes, 
                                              &dwBytesNeeded);
                        
                        if (!bRetVal) {
                            
                            ErrorDescription(GetLastError());
                            
                            delete [] g_psc;
                            g_psc = NULL;
                            break;
                        }
                        
                    }
                    
                    cout << TEXT("Display Name:") << (pServices + iIndex)->lpDisplayName << TEXT('\t');
                    cout << TEXT("Service Name:") << (pServices + iIndex)->lpServiceName << TEXT('\t');
                    cout << TEXT("Path Name:") << g_psc->lpBinaryPathName << endl;
                }
            }

            delete [] pServices;
            pServices = NULL;
                
        }
    }
    
    if (!CloseServiceHandle(hHandle)) {
        ErrorDescription(GetLastError());
    }
    else {
        cout << "Close SCM sucessfully" << endl;
    }
    
    return 0;
}

// get the description of error
void ErrorDescription(DWORD p_dwError) {
    
    HLOCAL hLocal = NULL;
    
    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER,
        NULL, p_dwError, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US),(LPTSTR)&hLocal, 
        0, NULL);
    
    MessageBox(NULL, (LPCTSTR)LocalLock(hLocal), TEXT("Error"), MB_OK | MB_ICONERROR);
    LocalFree(hLocal);
}

OpenSCManager 不仅可以显示本地计算机的服务,还可以显示远程计算机上的信息。OpenSCManager 的第一个参数是计算机名称。要获取网络上所有计算机的名称,使用 NetServerEnum。此函数仅在 Win NT/2000 上可用。因此,使用此 API 在 Win 9x 上运行的程序会给出此错误消息。

此消息没有向用户提供任何有用的信息,说明为什么此程序无法运行。最好先检查操作系统版本并给出相应的消息。

// Get the type of operating system
OSVERSIONINFO osInfo;
osInfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);

if (!GetVersionEx(&osInfo)) {
    ErrorDescription(GetLastError());
    return -1;
}

if (osInfo.dwPlatformId != VER_PLATFORM_WIN32_NT) {
    MessageBox(NULL, TEXT("This program runs on Win NT, 2000 or XP"),
        TEXT("Information"), MB_OK | MB_ICONINFORMATION);
}

此程序再次显示相同的消息框,因为 NetApi32.dll 在程序执行时被隐式加载。有两种解决方案。首先,使用 LoadLibrary 动态加载 DLL,并通过调用 GetProcAddress 获取函数地址。但是当 DLL 在内存中和不在内存中时,您必须小心。另一方面,VC++ 6 的链接器引入了一个新的开关 /delayload 来实现这一点。借助 delayload,我们可以告诉编译器,在调用 DLL 的函数时加载此 DLL。使用 delayload 并不难。

// for delay load
#include <delayimp.h>
#pragma comment(lib, "Delayimp.lib")

// This file will be delay load
#pragma comment(linker, "/DelayLoad:NetAPI32.dll")
#pragma comment(linker, "/Delay:unload")

现在,在 Win 9x 上运行此程序时,它将显示此消息框。

我编写了一个程序,不仅可以显示本地计算机上的服务信息,还可以显示远程计算机上的服务信息。该程序完全用 C 语言编写。

主屏幕

服务的详细信息

网络计算机信息
© . All rights reserved.