关于 Windows 服务






4.72/5 (19投票s)
关于 Windows 服务的讨论及示例
引言
编写服务器端程序与普通程序略有不同。因为您需要处理很多问题,例如安全性。微软建议服务器端应用程序应以服务的形式编写。
服务也是 Windows 程序。区别在于系统而不是用户来运行服务。另一个主要区别是服务没有用户界面。因此,服务程序可以由 main 或 WinMain 启动。Windows 自带的服务控制程序负责运行、注册和执行所有 Windows 服务的操作。所有服务的信息都存储在 Windows 注册表中,键为
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services
不建议直接访问此注册表项。要访问有关使用 Windows API 的信息,这些 API 会操作有关所有服务的信息。首先,您必须使用 OpenSCManager 打开 SCM(服务控制管理器),它有三个参数:第一个是您要在其上打开 SCM 的计算机名称。对于本地计算机,只需传递 NULL。第二个参数是数据库名称,要获取活动的数据库,只需传递 NULL。第三个参数是所需的访问权限。
这是一个显示此内容的简短控制台程序
程序 1
#include <windows.h> #include <iostream.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_ACTIVE
、SERVICE_INACTIVE
或 SERVICE_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 <windows.h> #include <iostream.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 <windows.h> #include <iostream.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 <windows.h> #include <iostream.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 语言编写。


