今更ながら、Windows アプリをサービス化して実行する必要に迫られたので、手順を記録してみる。
サービスはメインスレッドとサービススレッドの最低2つが必要となるため、登録/状態監視用の関数と実際のサービスを提供する関数を作成する。前者を SvcMain, 後者を SvcMainLoop と書くことにするとと、起動処理は以下のようになる。
#include <Windows.h>
#include <cstdio>
SERVICE_STATUS gSvcStatus;
SERVICE_STATUS_HANDLE gSvcStatusHandle;
HANDLE ghSvcStopEvent = NULL;
DWORD gdwTimeOut = 5;
TCHAR gsDrive[MAX_PATH], gsDir[MAX_PATH], gsFilename[MAX_PATH], gsExt[MAX_PATH];
DWORD WINAPI SvcCtrlHandler(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext);
VOID SvcMain(DWORD, LPTSTR*);
VOID ReportSvcStatus(DWORD, DWORD, DWORD);
VOID SvcInit(DWORD, LPTSTR*);
VOID SvcMainLoop(DWORD, LPTSTR*);
VOID SvcEnd(DWORD, LPTSTR*);
#define SVCNAME (L"MyService")
int __cdecl _tmain(int argc, char **argv) {
// サービス登録モード
TCHAR buf[MAX_PATH];
if (GetModuleFileNameW(NULL, buf, _countof(buf)) != 0LL) {
_tsplitpath(buf, gsDrive, gsDir, gsFilename, gsExt);
_tcscat(gsDrive, gsDir);
}
SERVICE_TABLE_ENTRY DispatchTable[] = {
{ (LPTSTR)SVCNAME, (LPSERVICE_MAIN_FUNCTION)SvcMain},
{ NULL, NULL }
};
BOOL dwRet = StartServiceCtrlDispatcherW(DispatchTable);
printf("Registration:%d\n", dwRet);
return (dwRet == 0) ? 1 : 0;
}
// サービス状態変化時に呼び出される関数
DWORD WINAPI SvcCtrlHandler(DWORD dwControl, DWORD dwEventType, LPVOID lpEventData, LPVOID lpContext) {
switch (dwControl) {
case SERVICE_CONTROL_STOP:
ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);
// サービス終了を通知
SetEvent(ghSvcStopEvent);
ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0);
default:;
// Nothing to do
}
return NO_ERROR;
}
// サービス登録及びメインループ
VOID SvcMain(DWORD dwArgc, LPTSTR* lpszArgv) {
gSvcStatusHandle = RegisterServiceCtrlHandlerEx(SVCNAME, SvcCtrlHandler, NULL);
if (gSvcStatusHandle == NULL) {
return;
}
// 独自プロセス
gSvcStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
gSvcStatus.dwServiceSpecificExitCode = 0;
ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000);
SvcInit(dwArgc, lpszArgv);
SvcMainLoop(dwArgc, lpszArgv);
}
VOID SvcInit(DWORD dwArgc, LPTSTR* lpszArgv) {
ghSvcStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (ghSvcStopEvent == NULL) {
ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
return;
}
ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);
}
void myMain() {
// ...
}
// メインループ
VOID SvcMainLoop(DWORD dwArgc, LPTSTR* lpszArgv) {
while (1) {
// 周期的に実行する関数
myMain();
// 第2引数を INFINITE にすると終了イベント発生まで戻らない
DWORD r = WaitForSingleObject(ghSvcStopEvent, gdwTimeOut * 60 * 1000);
if (r != WAIT_TIMEOUT) {
// 終了イベント発生時はループを抜ける
break;
}
}
SvcEnd(dwArgc, lpszArgv);
}
// 終了処理: 終了状態を宣言する
VOID SvcEnd(DWORD dwArgc, LPTSTR* lpszArgv) {
ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);
}
// サービス状態を通知する関数
VOID ReportSvcStatus(DWORD dwCurrentState, DWORD dwExitStateCode, DWORD dwWaitHint) {
gSvcStatus.dwCurrentState = dwCurrentState;
gSvcStatus.dwWin32ExitCode = dwExitStateCode;
gSvcStatus.dwWaitHint = dwWaitHint;
gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP
| SERVICE_ACCEPT_PAUSE_CONTINUE;
if (dwCurrentState == SERVICE_RUNNING || dwCurrentState == SERVICE_STOPPED) {
gSvcStatus.dwCheckPoint = 0;
}
else {
gSvcStatus.dwCheckPoint++;
}
SetServiceStatus(gSvcStatusHandle, &gSvcStatus);
}
サービスの登録/登録解除処理も必要になるが、sc コマンドで実行できる。
# 登録
sc create "MyService" binPath="C:\MyService\myService.exe"
# 登録解除
sc delete "MyService"
GetModuleFileNameW() は実行ファイルの絶対パスを取得するのに使用する。(実行ファイルと同じフォルダに .ini ファイル等を置きたかったため)相対パスだとサービスを実行できないようだ。
あとは「サービス」ユーティリティを上げて「開始」ボタンを押すとサービスが開始される。「停止」ボタンを押すとサービスが終了する。
ProgramFiles 以下にないと登録できないかと思ったが、任意のフォルダ上の実行ファイルを登録できるようだ。
コメントを残す