EternalWindows
サービス / サービスの状態

これまで、サービスの開発及びサービスのインストールについて説明しましたが、 今回からはSCP(Service Control Program)の開発について説明します。 SCPは既に何回か述べてきた通りサービスを制御するプログラムであり、 再開や一時停止といった制御コードをサービスに送信します。 実際にはこの送信はまずSCMに渡り、その後SCMがサービスに制御コードを送信するため、 SCPとサービスが直接通信し合うことは決してありません。 しかし、SCPが呼び出す関数によってサービスの状態が変化するのも事実ですから、 SCPがサービスを制御していると考えて問題ないでしょう。

SCPの目的はサービスを制御することですから、 呼び出すべき関数もそういった系統の関数になります。 今回は、サービスのステータスを取得するQueryServiceStatusExについて説明します。

BOOL QueryServiceStatusEx(
  SC_HANDLE hService,
  SC_STATUS_TYPE InfoLevel,
  LPBYTE lpBuffer,
  DWORD cbBufSize,
  LPDWORD pcbBytesNeeded
);

hServiceは、SERIVCE_QUERY_STATUSアクセス権を割り当てたサービスのハンドルです。 InfoLevelは、 SC_STATUS_PROCESS_INFO定数を指定します。 lpBufferは、SERVICE_STATUS_PROCESS構造体のアドレスを指定します。 この構造体にサービスのステータスが格納されます。 cbBufSizeは、lpBufferのサイズです。 pcbBytesNeededには、バッファのサイズが足りないために関数が失敗したとき、 必要なサイズが返ります。 この変数にNULLを指定することはできません。

SERVICE_STATUS_PROCESS構造体は、以下のように定義されています。

typedef struct _SERVICE_STATUS_PROCESS {
  DWORD dwServiceType;
  DWORD dwCurrentState;
  DWORD dwControlsAccepted;
  DWORD dwWin32ExitCode;
  DWORD dwServiceSpecificExitCode;
  DWORD dwCheckPoint;
  DWORD dwWaitHint;
  DWORD dwProcessId;
  DWORD dwServiceFlags;
} SERVICE_STATUS_PROCESS, *LPSERVICE_STATUS_PROCESS;

この構造体は、SERVICE_STATUS構造体にプロセスIDとフラグが追加されたものです。 QueryServiceStatusは、SERVICE_STATUS構造体を初期化しますが、 より多くの情報を取得するならQueryServiceStatusExを呼び出すべきです。

QueryServiceStatusExの第1引数はサービスのハンドルを要求しますが、 これはどのように取得すればよいでしょうか。 この問いに対して何を今更と思うかもしれませんが、 自分が制御したいサービスを表示名で識別していたような場合、 問題に直面することになります。

hService = OpenService(hSCManager, TEXT("Task Scheduler"), SERVICE_QUERY_STATUS);

システムにはTask Schedulerという表示名を持つサービスがデフォルトで存在しており、 上記コードはそのハンドルを取得しようとしています。 しかし、OpenServiceの第2引数に指定するのはサービス名であるため、 上記コードではハンドルを取得することはできません。 一般にはサービスは表示名で識別していますから、 前もってサービス名を把握しておくのは困難な場合があると思われます。 このようなとき、GetServiceKeyNameを呼び出すことによって、 表示名に関連付けられているサービス名を取得する手立てがあります。

BOOL GetServiceKeyName(
  SC_HANDLE hSCManager,
  LPCTSTR lpDisplayName,
  LPTSTR lpServiceName,
  LPDWORD lpcchBuffer
);

hSCManagerは、OpenSCManagerが返したハンドルを指定します。 lpDisplayNameは、サービスの表示名を指定します。 lpServiceNameは、サービス名を受け取るバッファを指定します。 lpcchBufferは、バッファのサイズを格納した変数のアドレスを指定します。 この変数にNULLを指定するとエラーのダイアログが表示されるので注意してください。

今回のプログラムはリストボックスを作成し、 SERVICE_STATUS_PROCESS構造体の各メンバを列挙します。

#include <windows.h>
#include <tlhelp32.h>

void AddValue(HWND hwndListBox, DWORD dwValue);
void AddServiceType(HWND hwndListBox, DWORD dwServiceType);
void AddCurrentState(HWND hwndListBox, DWORD dwCurrentState);
void AddControlsAccepted(HWND hwndListBox, DWORD dwControlsAccepted);
void AddProcessId(HWND hwndListBox, DWORD dwProcessId);
void AddServiceFlags(HWND hwndListBox, DWORD dwServiceFlags);

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR      szAppName[] = TEXT("sample");
	HWND       hwnd;
	MSG        msg;
	WNDCLASSEX wc;

	wc.cbSize        = sizeof(WNDCLASSEX);
	wc.style         = 0;
	wc.lpfnWndProc   = WindowProc;
	wc.cbClsExtra    = 0;
	wc.cbWndExtra    = 0;
	wc.hInstance     = hinst;
	wc.hIcon         = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	wc.hCursor       = (HCURSOR)LoadImage(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
	wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
	wc.lpszMenuName  = NULL;
	wc.lpszClassName = szAppName;
	wc.hIconSm       = (HICON)LoadImage(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_SHARED);
	
	if (RegisterClassEx(&wc) == 0)
		return 0;

	hwnd = CreateWindowEx(0, szAppName, szAppName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hinst, NULL);
	if (hwnd == NULL)
		return 0;

	ShowWindow(hwnd, nCmdShow);
	UpdateWindow(hwnd);
	
	while (GetMessage(&msg, NULL, 0, 0) > 0) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return (int)msg.wParam;
}

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
	static HWND hwndListBox = NULL;

	switch (uMsg) {

	case WM_CREATE: {
		SC_HANDLE              hSCManager;
		SC_HANDLE              hService;
		DWORD                  dwLength;
		TCHAR                  szServiceName[256];
		SERVICE_STATUS_PROCESS ss;
		
		hwndListBox = CreateWindowEx(0, TEXT("LISTBOX"), NULL, WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, hwnd, (HMENU)1, ((LPCREATESTRUCT)lParam)->hInstance, NULL);

		hSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
		if (hSCManager == NULL) {
			MessageBox(NULL, TEXT("SCMデータベースのオープンに失敗しました。"), NULL, MB_ICONWARNING);
			return -1;
		}
		
		dwLength = sizeof(szServiceName);
		GetServiceKeyName(hSCManager, TEXT("Task Scheduler"), szServiceName, &dwLength);
		
		hService = OpenService(hSCManager, szServiceName, SERVICE_QUERY_STATUS);
		if (hService == NULL) {
			MessageBox(NULL, TEXT("サービスのオープンに失敗しました。"), NULL, MB_ICONWARNING);
			CloseServiceHandle(hSCManager);
			return -1;
		}

		QueryServiceStatusEx(hService, SC_STATUS_PROCESS_INFO, (LPBYTE)&ss, sizeof(SERVICE_STATUS_PROCESS), &dwLength);

		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("dwServiceType:"));
		AddServiceType(hwndListBox, ss.dwServiceType);

		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT(""));
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("dwCurrentState:"));
		AddCurrentState(hwndListBox, ss.dwCurrentState);

		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT(""));
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("dwControlsAccepted:"));
		AddControlsAccepted(hwndListBox, ss.dwControlsAccepted);

		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT(""));
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("dwCheckPoint:"));
		AddValue(hwndListBox, ss.dwCheckPoint);
		
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT(""));
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("dwWaitHint:"));
		AddValue(hwndListBox, ss.dwWaitHint);

		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT(""));
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("dwProcessId:"));
		AddProcessId(hwndListBox, ss.dwProcessId);
		
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT(""));
		SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)TEXT("dwServiceFlags:"));
		AddServiceFlags(hwndListBox, ss.dwServiceFlags);

		CloseServiceHandle(hService);
		CloseServiceHandle(hSCManager);
	
		return 0;
	}

	case WM_SIZE:
		MoveWindow(hwndListBox, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
		return 0;

	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;

	default:
		break;

	}

	return DefWindowProc(hwnd, uMsg, wParam, lParam);
}

void AddValue(HWND hwndListBox, DWORD dwValue)
{
	TCHAR szBuf[256];
	
	wsprintf(szBuf, TEXT("%d"), dwValue);
	SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
}

void AddServiceType(HWND hwndListBox, DWORD dwServiceType)
{
	struct TYPE {
		DWORD  dwServiceType;
		LPTSTR lpszServiceType;
	} type[] = {
		{SERVICE_WIN32_OWN_PROCESS, TEXT("SERVICE_WIN32_OWN_PROCESS")},
		{SERVICE_WIN32_SHARE_PROCESS, TEXT("SERVICE_WIN32_SHARE_PROCESS")}
	};
	int   i;
	int   n = sizeof(type) / sizeof(type[0]);
	TCHAR szBuf[256];

	for (i = 0; i < n; i++) {
		if (dwServiceType & type[i].dwServiceType) {
			lstrcpy(szBuf, type[i].lpszServiceType);
			if (dwServiceType & SERVICE_INTERACTIVE_PROCESS)
				lstrcat(szBuf, TEXT(" (SERVICE_INTERACTIVE_PROCESS)"));
			SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
			break;
		}
	}
}

void AddCurrentState(HWND hwndListBox, DWORD dwCurrentState)
{
	struct STATE {
		DWORD  dwCurrentState;
		LPTSTR lpszCurrentState;
	} state[] = {
		{SERVICE_STOPPED, TEXT("SERVICE_STOPPED")},
		{SERVICE_START_PENDING, TEXT("SERVICE_START_PENDING")},
		{SERVICE_STOP_PENDING, TEXT("SERVICE_STOP_PENDING")},
		{SERVICE_RUNNING, TEXT("SERVICE_RUNNING")},
		{SERVICE_CONTINUE_PENDING, TEXT("SERVICE_CONTINUE_PENDING")},
		{SERVICE_PAUSE_PENDING, TEXT("SERVICE_PAUSE_PENDING")},
		{SERVICE_PAUSED, TEXT("SERVICE_PAUSED")}
	};
	
	int i;
	int n = sizeof(state) / sizeof(state[0]);
	
	for (i = 0; i < n; i++) {
		if (dwCurrentState == state[i].dwCurrentState) {
			SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)state[i].lpszCurrentState);
			break;
		}
	}
}

void AddControlsAccepted(HWND hwndListBox, DWORD dwControlsAccepted)
{
	struct ACCEPT {
		DWORD  dwControlsAccepted;
		LPTSTR lpszControlsAccepted;
	} accept[] = {
		{SERVICE_ACCEPT_NETBINDCHANGE, TEXT("SERVICE_ACCEPT_NETBINDCHANGE")},
		{SERVICE_ACCEPT_PARAMCHANGE, TEXT("SERVICE_ACCEPT_PARAMCHANGE")},
		{SERVICE_ACCEPT_PAUSE_CONTINUE, TEXT("SERVICE_ACCEPT_PAUSE_CONTINUE")},
		{SERVICE_ACCEPT_SHUTDOWN, TEXT("SERVICE_ACCEPT_SHUTDOWN")},
		{SERVICE_ACCEPT_STOP, TEXT("SERVICE_ACCEPT_STOP")},
		{SERVICE_ACCEPT_HARDWAREPROFILECHANGE, TEXT("SERVICE_ACCEPT_HARDWAREPROFILECHANGE")},
		{SERVICE_ACCEPT_POWEREVENT, TEXT("SERVICE_ACCEPT_POWEREVENT")},
		{SERVICE_ACCEPT_SESSIONCHANGE, TEXT("SERVICE_ACCEPT_SESSIONCHANGE")}
	};
	
	int i;
	int n = sizeof(accept) / sizeof(accept[0]);
	
	for (i = 0; i < n; i++) {
		if (dwControlsAccepted & accept[i].dwControlsAccepted)
			SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)accept[i].lpszControlsAccepted);
	}

}

void AddProcessId(HWND hwndListBox, DWORD dwProcessId)
{
	TCHAR          szBuf[256];
	HANDLE         hSnapshot;
	PROCESSENTRY32 pe;
	
	hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

	pe.dwSize = sizeof(PROCESSENTRY32);
	Process32First(hSnapshot, &pe);

	do {
		if (dwProcessId == pe.th32ProcessID) {
			wsprintf(szBuf, TEXT("%d (%s) "), dwProcessId, pe.szExeFile);
			SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
			break;
		}
	} while (Process32Next(hSnapshot, &pe));
	
	CloseHandle(hSnapshot);
}

void AddServiceFlags(HWND hwndListBox, DWORD dwServiceFlags)
{
	TCHAR szBuf[256];

	if (dwServiceFlags == 0)
		wsprintf(szBuf, TEXT("%d"), 0);
	else
		wsprintf(szBuf, TEXT("%s"), TEXT("SERVICE_RUNS_IN_SYSTEM_PROCESS"));

	SendMessage(hwndListBox, LB_ADDSTRING, 0, (LPARAM)szBuf);
}

SCPはサービスではないため、ServiceMainのコードや SetServiceStatusの呼び出しなどは必要ありません。 SCPは、サービスを制御する関数を主として呼び出すだけで、 それ以外は普通のプログラムと全く同じです。

dwLength = sizeof(szServiceName);
GetServiceKeyName(hSCManager, TEXT("Task Scheduler"), szServiceName, &dwLength);
		
hService = OpenService(hSCManager, szServiceName, SERVICE_QUERY_STATUS);

表示名をサービス名に変換し、それをOpenServiceの第2引数に指定します。 QueryServiceStatusExを呼ぶことになるため、 第3引数にSERVICE_QUERY_STATUSを指定します。

QueryServiceStatusEx(hService, SC_STATUS_PROCESS_INFO, (LPBYTE)&ss, sizeof(SERVICE_STATUS_PROCESS), &dwLength);

SERVICE_STATUS_PROCESS構造体を初期化します。 これ以降のコードでは単純にメンバを表示するだけなので特に述べることもありませんが、 プロセスIDを表示するAddProcessIdでは、プロセス名も併せて表示しています。 ここでは、Process32First等のTool Help Libraryという関数群を使っているため、 冒頭ではtlhelp32.hをインクルードしています。 ちなみに、dwCurrentStateがSERVICE_STOPPEDときは、 dwControlsAcceptedやdwProcessIdは0になるので注意してください。

今回のプログラムは、OpenServiceの第2引数を調整することによって、 任意のサービスのステータスを取得することができますが、 ステータスを取得する必要性とはどのようなときに発生するのでしょうか。 たとえば、サービスを開始させるStartServiceという関数がありますが、 この関数は実際にサービスが開始するのを待たずに制御を返します。 これでは、サービスがSERVICE_START_RUNNINGを報告したのかどうかを 確かめることはできませんから、QueryServiceStatus(Ex)を呼び出すことによって、 サービスのステータスを確認することになるでしょう。 このようなとき、QueryServiceStatus(Ex)を呼び出す間隔はdwWaitHintとなり、 dwCheckPointが増えているかどうかも確認しなくてはなりません。

サービスのステータスはいつどのように変化するか分かりませんから、 どちらにせよステータスは周期的に取得するべきなのかもしれません。 今回のプログラムは、サービスのステータスを1回取得するだけですから、 他のSCPによってサービスに変更が加えられたとしても、 リストボックスを新しいステータスに反映させることはできません。 Windows付属のSCPのように、最新の情報に更新という選択を設ける方法もありますが、 やはり、変更が生じた瞬間に自動的に情報を更新したいものです。 幸いにも、次世代OSのWindowsVistaでは、NotifyServiceStatusChangeという ステータスの変更を通知する関数が追加されるようです。


戻る