Win32API Windowsサービスアプリケーションの開発

Windowsサービスアプリケーションの開発を行う。

まず、アプリケーションをサービス制御マネージャのデータベースに登録するところから行う。

使用する主なAPIは、OpenSCManager関数とCreateService関数である。


アプリケーションをサービス制御マネージャのデータベースに登録を行うソースコードは、以下のようになる。ここでは、Win32アプリケーション(コンソールアプリケーションではない)で作成した。
WinMain関数で、InstallService関数を呼び出している。InstallService関数の中で、Windowsサービスを作成し、サービス制御マネージャのデータベースに追加している。

#include "stdafx.h"
#include <Windows.h>
#include <strsafe.h>

#define SERVICE_NAME TEXT("SampleServiceApp")
#define DISPLAY_NAME TEXT("SampleServiceApp")
#define DESCRIPTION TEXT("Sample Service Application")

BOOL InstallService()
{
    SC_HANDLE hSCM = NULL;
    SC_HANDLE hService = NULL;
    TCHAR szBinaryPathName[MAX_PATH];
    SERVICE_DESCRIPTION sd;
    BOOL bRet = FALSE;

    //サービス制御マネージャのデータベースのハンドルを取得する
    //第3引数に 「SC_MANAGER_CREATE_SERVICE」 を指定することで、
    //サービスを作成しサービス制御マネージャのデータベースに追加できるようにしている
    //つまり、CreateService関数の呼び出しをできるようにしているということになる。
    hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
    if (hSCM == NULL) {
        goto EXIT;
    }

    //自分自身の実行ファイルのフルパスを取得する
    GetModuleFileName(NULL, 
        szBinaryPathName, 
        sizeof(szBinaryPathName)/sizeof(szBinaryPathName[0]));


    //サービスを作成し、サービス制御マネージャのデータベースに追加する。
    hService = CreateService(hSCM, //サービス制御マネージャのハンドル
        SERVICE_NAME, //サービス名
        DISPLAY_NAME, //表示名
        SERVICE_CHANGE_CONFIG,//サービスの構成を変更できるようにする
        SERVICE_WIN32_OWN_PROCESS, //サービスアプリケーションが専用のプロセス内で動作
        SERVICE_DEMAND_START,//StartService関数が呼び出されたときに、サービスが開始される
        SERVICE_ERROR_IGNORE,//エラー発生時に、サービス開始操作を続行する
        szBinaryPathName, //サービスアプリケーションの実行ファイルパス
        NULL,
        NULL,
        NULL,
        NULL,
        NULL);

    if (hService == NULL) {
        goto EXIT;
    }

    sd.lpDescription = DESCRIPTION;
    
    ChangeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, &sd);

    bRet = TRUE;


EXIT:
    //クリーンアップ
    CloseServiceHandle(hService);
    CloseServiceHandle(hSCM);

    return bRet;
}


int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    if (InstallService()) {
        MessageBox(NULL, TEXT("成功"), TEXT("成功"), MB_OK);
    } else {
        TCHAR szMsg[64];
        StringCchPrintf(szMsg, 
            sizeof(szMsg)/sizeof(szMsg[0]), 
            TEXT("失敗 - ErrorCode=%d"), GetLastError());
        MessageBox(NULL, szMsg, TEXT("失敗"), MB_OK);
    }

	return 0;
}


このコードをビルドし、生成された実行ファイルを管理者権限で実行すると、サービスがサービス制御マネージャのデータベースに登録される。登録されているかどうかは、


[スタート]->[コントロールパネル]->[システムとセキュリティ]->[管理ツール]->[サービス]


から確認できる。


以下の画像が、登録されたサービスである。









次に、実際のサービスアプリケーションを実装する。

先程、サービス制御マネージャのデータベースに登録した実行ファイルをサービスアプリケーションとして利用するため、先程のソースコードにサービスアプリケーションのコードを追加および修正していく。



ここでやるべきことは、2つのグローバル変数と3つの関数を追加し、WinMain関数の修正を行う。

追加する2つのグローバル変数とは、以下のSERVICE_STATUS構造体オブジェクトとSERVICE_STATUS_HANDLE型オブジェクトである。

SERVICE_STATUS g_ServiceStatus;
SERVICE_STATUS_HANDLE g_ServiceStatusHandle;




追加する3つの関数は、「ServiceEntry関数」、「ServiceThread関数」,「HandlerEx関数」である。ServiceEntry関数は、サービスアプリケーションのエントリポイントのような位置づけの関数である。ここでサービス制御ハンドラーであるHandlerExを登録し、ServiceThread関数をスレッドとして起動する。このServiceThread関数で、サービスアプリケーションで実現すべき処理を実装する。サービス制御ハンドラーHandlerEx関数は、実行中のサービスの状態が変更されたときに呼び出されるコールバック関数である。例えば、サービスの状態が「開始」から「停止」や「一時停止」などの状態に変更されたときに呼び出される。


WinMain関数では、StartServiceCtrlDispatcher関数(Win32API)をServiceEntry関数を指定して呼び出すように修正する。これにより、WinMain → ServiceEntry → ServiceThread という順番に呼び出される。



以下に、実装例を示す。

#include "stdafx.h"
#include <Windows.h>
#include <strsafe.h>
#include <process.h>
#include <stdio.h>

#include <shlwapi.h>
#pragma comment(lib, "shlwapi.lib")

#define SERVICE_NAME TEXT("SampleServiceApp")
#define DISPLAY_NAME TEXT("SampleServiceApp")
#define DESCRIPTION TEXT("Sample Service Application")

SERVICE_STATUS g_ServiceStatus;
SERVICE_STATUS_HANDLE g_ServiceStatusHandle;

//サービス制御ハンドラー
DWORD WINAPI HandlerEx(DWORD dwControl, DWORD dwEventType, 
                       PVOID pvEventData, PVOID pvContext)
{
    switch (dwControl) {
    
        case SERVICE_CONTROL_STOP:
            g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
            break;

        case SERVICE_CONTROL_SHUTDOWN:
            g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
            break;

        case SERVICE_CONTROL_PAUSE:
            g_ServiceStatus.dwCurrentState = SERVICE_PAUSED;
            break;

        case SERVICE_CONTROL_CONTINUE:
            g_ServiceStatus.dwCurrentState = SERVICE_RUNNING;
            break;

        default:
            break;
    }

    SetServiceStatus(g_ServiceStatusHandle, &g_ServiceStatus);

    return ERROR_CALL_NOT_IMPLEMENTED;
}


//サービスアプリケーション自身の実行ファイルパスと同じ場所にあるファイルに
//現在の時刻を書き続けるだけの処理を実装する
unsigned int WINAPI ServiceThread(LPVOID arg)
{
    char szFileName[MAX_PATH] = {'\0'};
    SYSTEMTIME localTime;

    GetModuleFileNameA(NULL, szFileName, sizeof(szFileName)/sizeof(szFileName[0]));
    PathRenameExtensionA(szFileName, ".txt");

    for (;;) {
        FILE *fp = fopen(szFileName, "a");
        if (fp != NULL) {
            GetLocalTime(&localTime);
            fprintf(fp, "%02d:%02d:%02d\n", 
                localTime.wHour, localTime.wMinute, localTime.wSecond);
            fclose(fp);
        }
        Sleep(1000);
    }

    return 0;
}

void WINAPI ServiceEntry(DWORD dwArgs, LPTSTR *pszArgv)
{
    unsigned int uiThreadID;
    HANDLE hThread;

    //サービススレッドを起動する
    hThread = (HANDLE)_beginthreadex(NULL,
        0,
        ServiceThread,
        NULL,
        0,
        &uiThreadID);

    
    //サービス制御ハンドラーを登録する
    g_ServiceStatusHandle = RegisterServiceCtrlHandlerEx(SERVICE_NAME, HandlerEx, NULL);
    
    

    //サービスの状態を設定する
    memset(&g_ServiceStatus, 0, sizeof(g_ServiceStatus));
    g_ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    g_ServiceStatus.dwCurrentState = SERVICE_RUNNING;//サービス実行状態
    g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;//サービスの停止と、一時停止+続行を受け入れる
    g_ServiceStatus.dwWin32ExitCode = NO_ERROR;
    g_ServiceStatus.dwServiceSpecificExitCode = 0;
    g_ServiceStatus.dwCheckPoint = 0;
    g_ServiceStatus.dwWaitHint = 2000;
    
    SetServiceStatus(g_ServiceStatusHandle, &g_ServiceStatus);
}



BOOL InstallService()
{
    SC_HANDLE hSCM = NULL;
    SC_HANDLE hService = NULL;
    TCHAR szBinaryPathName[MAX_PATH];
    SERVICE_DESCRIPTION sd;
    BOOL bRet = FALSE;

    //サービス制御マネージャのデータベースのハンドルを取得する
    //第3引数に 「SC_MANAGER_CREATE_SERVICE」 を指定することで、
    //サービスを作成しサービス制御マネージャのデータベースに追加できるようにしている
    //つまり、CreateService関数の呼び出しをできるようにしているということになる。
    hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
    if (hSCM == NULL) {
        goto EXIT;
    }

    //自分自身の実行ファイルのフルパスを取得する
    GetModuleFileName(NULL, 
        szBinaryPathName, 
        sizeof(szBinaryPathName)/sizeof(szBinaryPathName[0]));


    //サービスを作成し、サービス制御マネージャのデータベースに追加する。
    hService = CreateService(hSCM, //サービス制御マネージャのハンドル
        SERVICE_NAME, //サービス名
        DISPLAY_NAME, //表示名
        SERVICE_CHANGE_CONFIG,//サービスの構成を変更できるようにする
        SERVICE_WIN32_OWN_PROCESS, //サービスアプリケーションが専用のプロセス内で動作
        SERVICE_DEMAND_START,//StartService関数が呼び出されたときに、サービスが開始される
        SERVICE_ERROR_IGNORE,//エラー発生時に、サービス開始操作を続行する
        szBinaryPathName, //サービスアプリケーションの実行ファイルパス
        NULL,
        NULL,
        NULL,
        NULL,
        NULL);

    if (hService == NULL) {
        goto EXIT;
    }

    sd.lpDescription = DESCRIPTION;
    
    ChangeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, &sd);

    bRet = TRUE;


EXIT:
    //クリーンアップ
    CloseServiceHandle(hService);
    CloseServiceHandle(hSCM);

    return bRet;
}


int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);


    SERVICE_TABLE_ENTRY ServiceTableEntry[] = {
        {SERVICE_NAME, ServiceEntry},
        {NULL, NULL}
    };

    StartServiceCtrlDispatcher(ServiceTableEntry);

    return 0;
}




このソースコードをビルドして、サービスアプリケーションの実行ファイルを生成する。
そして、管理ツールの「サービス」を管理者権限で起動し、[開始]ボタンを押し、サービスを開始する。



サービスアプリケーションが開始すると、サービスアプリケーションの実行ファイルと同じ場所にテキストファイルが作成され、1秒毎に現在の時刻が追記されていくことを確認することができる。








最後に、サービスのアンインストールを行う。

このサービスのアンインストールの実装も上記のファイルに追加および修正していく。


サービスのアンインストールをするには、サービス制御マネージャのデータベースに登録されたサービスを削除することになる。そのために使用する主なWin32APIは、DeleteService関数である。サービスをアンインストールする手順は以下のようになる。


1.OpenSCManager関数で、サービス制御マネージャに接続する

2.OpenService関数で、サービスのハンドルを取得する

3.DeleteService関数で、サービス制御マネージャのデータベースに登録されたサービスを削除する




注意点は、OpenService関数の第3引数のアクセス権に「DELETE」を指定して、DeleteServiceの呼び出しを有効にする必要があるということ。


実装例は、以下のようになる。UninstallService関数の中でサービスをアンインストールしている。そして、WinMain関数からUninstallService関数を呼び出すようにする。

#include "stdafx.h"
#include <Windows.h>
#include <strsafe.h>
#include <process.h>
#include <stdio.h>

#include <shlwapi.h>
#pragma comment(lib, "shlwapi.lib")

#define SERVICE_NAME TEXT("SampleServiceApp")
#define DISPLAY_NAME TEXT("SampleServiceApp")
#define DESCRIPTION TEXT("Sample Service Application")

SERVICE_STATUS g_ServiceStatus;
SERVICE_STATUS_HANDLE g_ServiceStatusHandle;

//サービス制御ハンドラー
DWORD WINAPI HandlerEx(DWORD dwControl, DWORD dwEventType, 
                       PVOID pvEventData, PVOID pvContext)
{
    switch (dwControl) {
    
        case SERVICE_CONTROL_STOP:
            g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
            break;

        case SERVICE_CONTROL_SHUTDOWN:
            g_ServiceStatus.dwCurrentState = SERVICE_STOPPED;
            break;

        case SERVICE_CONTROL_PAUSE:
            g_ServiceStatus.dwCurrentState = SERVICE_PAUSED;
            break;

        case SERVICE_CONTROL_CONTINUE:
            g_ServiceStatus.dwCurrentState = SERVICE_RUNNING;
            break;

        default:
            break;
    }

    SetServiceStatus(g_ServiceStatusHandle, &g_ServiceStatus);

    return ERROR_CALL_NOT_IMPLEMENTED;
}


//サービスアプリケーション自身の実行ファイルパスと同じ場所にあるファイルに
//現在の時刻を書き続けるだけの処理を実装する
unsigned int WINAPI ServiceThread(LPVOID arg)
{
    char szFileName[MAX_PATH] = {'\0'};
    SYSTEMTIME localTime;

    GetModuleFileNameA(NULL, szFileName, sizeof(szFileName)/sizeof(szFileName[0]));
    PathRenameExtensionA(szFileName, ".txt");

    for (;;) {
        FILE *fp = fopen(szFileName, "a");
        if (fp != NULL) {
            GetLocalTime(&localTime);
            fprintf(fp, "%02d:%02d:%02d\n", 
                localTime.wHour, localTime.wMinute, localTime.wSecond);
            fclose(fp);
        }
        Sleep(1000);
    }

    return 0;
}

void WINAPI ServiceEntry(DWORD dwArgs, LPTSTR *pszArgv)
{
    unsigned int uiThreadID;
    HANDLE hThread;

    //サービススレッドを起動する
    hThread = (HANDLE)_beginthreadex(NULL,
        0,
        ServiceThread,
        NULL,
        0,
        &uiThreadID);

    
    //サービス制御ハンドラーを登録する
    g_ServiceStatusHandle = RegisterServiceCtrlHandlerEx(SERVICE_NAME, HandlerEx, NULL);
    
    

    //サービスの状態を設定する
    memset(&g_ServiceStatus, 0, sizeof(g_ServiceStatus));
    g_ServiceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
    g_ServiceStatus.dwCurrentState = SERVICE_RUNNING;//サービス実行状態
    g_ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;//サービスの停止と、一時停止+続行を受け入れる
    g_ServiceStatus.dwWin32ExitCode = NO_ERROR;
    g_ServiceStatus.dwServiceSpecificExitCode = 0;
    g_ServiceStatus.dwCheckPoint = 0;
    g_ServiceStatus.dwWaitHint = 2000;
    
    SetServiceStatus(g_ServiceStatusHandle, &g_ServiceStatus);
}



BOOL InstallService()
{
    SC_HANDLE hSCM = NULL;
    SC_HANDLE hService = NULL;
    TCHAR szBinaryPathName[MAX_PATH];
    SERVICE_DESCRIPTION sd;
    BOOL bRet = FALSE;

    //サービス制御マネージャのデータベースのハンドルを取得する
    //第3引数に 「SC_MANAGER_CREATE_SERVICE」 を指定することで、
    //サービスを作成しサービス制御マネージャのデータベースに追加できるようにしている
    //つまり、CreateService関数の呼び出しをできるようにしているということになる。
    hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CREATE_SERVICE);
    if (hSCM == NULL) {
        goto EXIT;
    }

    //自分自身の実行ファイルのフルパスを取得する
    GetModuleFileName(NULL, 
        szBinaryPathName, 
        sizeof(szBinaryPathName)/sizeof(szBinaryPathName[0]));


    //サービスを作成し、サービス制御マネージャのデータベースに追加する。
    hService = CreateService(hSCM, //サービス制御マネージャのハンドル
        SERVICE_NAME, //サービス名
        DISPLAY_NAME, //表示名
        SERVICE_CHANGE_CONFIG,//サービスの構成を変更できるようにする
        SERVICE_WIN32_OWN_PROCESS, //サービスアプリケーションが専用のプロセス内で動作
        SERVICE_DEMAND_START,//StartService関数が呼び出されたときに、サービスが開始される
        SERVICE_ERROR_IGNORE,//エラー発生時に、サービス開始操作を続行する
        szBinaryPathName, //サービスアプリケーションの実行ファイルパス
        NULL,
        NULL,
        NULL,
        NULL,
        NULL);

    if (hService == NULL) {
        goto EXIT;
    }

    sd.lpDescription = DESCRIPTION;
    
    ChangeServiceConfig2(hService, SERVICE_CONFIG_DESCRIPTION, &sd);

    bRet = TRUE;


EXIT:
    //クリーンアップ
    CloseServiceHandle(hService);
    CloseServiceHandle(hSCM);

    return bRet;
}

BOOL UninstallService()
{
    SC_HANDLE hSCM = NULL;
    SC_HANDLE hService = NULL;
    BOOL bRet = FALSE;

    //サービス制御マネージャに接続する
    hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
    if (hSCM == NULL) {
        goto EXIT;
    }

    //サービスのハンドルを取得する
    //第3引数のアクセス権に DELETE を指定して、DeleteService呼び出しを有効にする
    hService = OpenService(hSCM, SERVICE_NAME, DELETE);
    if (hService == NULL) {
        goto EXIT;
    }

    //サービス制御マネージャのデータベースに登録されたサービスを削除する
    if (DeleteService(hService) == FALSE) {
        goto EXIT;
    }

    bRet = TRUE;


EXIT:
    //クリーンアップ
    CloseServiceHandle(hSCM);
    CloseServiceHandle(hService);
    
    return bRet;
}


int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    UninstallService();

    return 0;
}


このソースコードをビルドし実行ファイルを生成して、これを管理者権限で実行するとサービス制御マネージャのデータベースからサービスが削除される。管理ツールの「サービス」に表示されるサービスの一覧から、登録してあったサービス名が削除されていれば、サービスのアンインストールは成功したということになる。


あとは、WinMain関数でInstallService関数呼び出し、StartServiceCtrlDispatcher関数の呼び出し、Uninstall関数の呼び出しをそれぞれ実装し、コマンドライン引数でそれぞれの動作を切り替えるようにすれば良い。


以上。