◆VC++ 2005でのWindows Service制御 - 実装2

[ 上へ ]

・スタートアップの種類の取得/変更

まず、VBA - API編 の時と同様にエラー対応のために定数を定義します。ヘッダファイルCWinService.h に下記の列挙定数(SVC_STARTMODE)を定義してください。

//CWinService.h////////////////////////////////////////////
enum SVC_STATUS {
   //・
   //・
};

enum SVC_ACTION {
   //・
   //・
};

enum SVC_STARTMODE {
    SVC_BOOT_START   = 0x00000000,
    SVC_SYSTEM_START = 0x00000001,
    SVC_AUTO_START   = 0x00000002,
    SVC_DEMAND_START = 0x00000003,
    SVC_DISABLED     = 0x00000004,
    SVC_NON          = 0x00000010
};

class CWinService
{
   // ・
   // ・
};

実はスタートアップの種類の取得と変更のコードは共通する部分が多いので、 冗長になるのを防ぐためアクセス制御がprotectedな共通のコードを先に作成して、 取得と変更を行うpublicな関数からその関数を呼び出す形にします。それでは共通のメンバ関数を メンバ関数の追加ウィザードから以下のように 作成してください。

  
戻値の型        : DWORD
関数名          : ExecStartMode
パラメータの型  : bool            パラメータ名 : bChange  (追加ボタン)
パラメータの型  : SVC_STARTMODE   パラメータ名 : eMode    (追加ボタン)
アクセス制御    : protected

宣言ができたら以下のコードを入力してください。

//CWinService.cpp ///////////////////////////////////////////
//スタートモード取得/変更
//引数 bChange : 取得の場合 FALSE      変更の場合 TRUE
//     eMode   : bChange==TRUE  の時変更するスタートアップの種類
//               bChange==FALSE の時無視
//戻値 bChange==TRUE  スタートアップが変更できた場合0以外 失敗の場合0
//     bChange==FALSE 現在のスタートアップの種類
DWORD CWinService::ExecStartMode(bool bChange, SVC_STARTMODE eMode)
{
    SC_HANDLE              hSCManager = NULL;
    SC_HANDLE              hService   = NULL;
    DWORD                  dwSize     = 0;
    LPQUERY_SERVICE_CONFIG pQscBuf    = NULL;
    DWORD                  dwRet      = bChange ? 0 : (DWORD)SVC_NON;

    while (TRUE)
    {
        //サービス制御マネージャとの接続
        hSCManager = ::OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
        if (!hSCManager)
            break;

        //サービスOpen
        hService = ::OpenService(hSCManager, m_pSvcName, SERVICE_QUERY_CONFIG
                                                       | SERVICE_CHANGE_CONFIG);
        if (!hService)
            break;

        //QUERY_SERVICE_CONFIG構造体のサイズ取得
        if ((!::QueryServiceConfig(hService, NULL, 0, &dwSize)) &&      //(A)
                                   (::GetLastError() != ERROR_INSUFFICIENT_BUFFER))
            break;

        //QUERY_SERVICE_CONFIG構造体用メモリ確保
        pQscBuf = (LPQUERY_SERVICE_CONFIG)::HeapAlloc(::GetProcessHeap(),   //(B)
                                                        HEAP_ZERO_MEMORY, dwSize);
        if (!pQscBuf)
            break;

        while (TRUE)
        {
            //構造体に現在のQUERY_SERVICE_CONFIG結果受取
            if (!::QueryServiceConfig(hService, pQscBuf, dwSize, &dwSize))  //(C)
                break;

            if (!bChange)   //StartMode取得の場合
            {
                dwRet = pQscBuf->dwStartType;  //(D)

            } else          //StartMode変更の場合
            {
                if (pQscBuf->dwStartType == (DWORD)eMode)
                    break;

                if (!::ChangeServiceConfig(hService,                        //(E)
                                           SERVICE_NO_CHANGE, (DWORD)eMode,
                                           SERVICE_NO_CHANGE, NULL, NULL, NULL, NULL,
                                           NULL, NULL, pQscBuf->lpDisplayName))
                    break;

                dwRet = 1;
            }

            break;
        }
        //ヒープ解放
        ::HeapFree(::GetProcessHeap(), 0, pQscBuf);                         //(F)

        break;
    }

    if (hService) ::CloseServiceHandle(hService);
    if (hSCManager) ::CloseServiceHandle(hSCManager);

    return dwRet;
}

VBA - API編の時とロジックは同じですが、VBAの時よりも逆に簡単?になります。(A)で QUERY_SERVICE_CONFIG構造体のサイズを取得したら、VBAではバイト型の配列を作成しましたが、 VC++では、(B)のようにHeapAllocでヒープにメモリを確保して型をLPQUERY_SERVICE_CONFIG (QUERY_SERVICE_CONFIG構造体へのポインタ)にキャストして、(B)で作ったヒープへのポインタを(C)で 再度QueryServiceConfigに渡すだけです。VBAの時のように変にややこしいメモリ操作する必要もないです。

現在のスタートアップの種類の取得の場合は(D)でQUERY_SERVICE_CONFIG構造体の dwStartTypeメンバを戻値に返し、変更の場合は(E)でChangeServiceConfigで変更する種類の値を eModeから第三引数に渡します。
最後に(F)で確保したメモリ領域を解放して各ハンドルをCloseして終了です。

それでは、上記の関数を使って外部に公開する取得/変更の関数を作成します。取得用メンバ関数 2つをメンバ関数の追加ウィザードから以下のように作成してください。

戻値の型            : STARTMODE
関数名              : StartMode
アクセス制御        : public
// スタートモード取得
SVC_STARTMODE CWinService::StartMode()
{
    return (SVC_STARTMODE)ExecStartMode(FALSE, SVC_NON);
}
戻値の型        : BOOL
関数名          : ChangeStartMode
パラメータの型  :  SVC_STARTMODE
パラメータ名    : eMode
アクセス制御    : public
// スタートモード変更
BOOL CWinService::ChangeStartMode(SVC_STARTMODE eMode)
{
    return ExecStartMode(TRUE, eMode);
}

以上でCWinServiceクラスの実装は終わりましたので、プロジェクトの作成時に できているmain関数でテストしてみます。

・テスト

main関数があるTestWinService.cpp 実装ファイルを開いてください。CWinServiceクラスを使うため、 ここ で作成したStdWinService.hヘッダファイルを インクルードしてください。

#include "stdafx.h"        //定義済み
#include "StdWinService.h" //WinService用インクルードファイル

メイン関数に以下の実装をしてください。

int _tmain(int argc, _TCHAR* argv[])
{
    CWinService svc(argv[1]);   //CWinServiceインスタンス

    SVC_ACTION    status = (SVC_ACTION)_tstol(argv[2]);
    SVC_STARTMODE mode = (SVC_STARTMODE)_tstol(argv[3]);

    _tprintf(_T("Status : %d\n"), svc.Status());
    _tprintf(_T("ChangeStatus : %d\n"), svc.ChangeServiceStatus(status));
    _tprintf(_T("Status : %d\n\n"), svc.Status());
    _tprintf(_T("StartMode : %d\n"), svc.StartMode());
    _tprintf(_T("ChangeStartMode : %d\n"), svc.ChangeStartMode(mode));
    _tprintf(_T("StartMode : %d\n"), svc.StartMode());
    return 0;
}

ビルドを実行して実行ファイルができたら、Visual Studioから離れて スタートメニューからファイル名を指定して実行(R)cmdと 入力してコンソールを起動してください。実行ファイル(TestWinService.exe) があるフォルダに移動して

>TestWinService.exe "MSSQL$SQLEXPRESS" 4 2   [リターン]

とコマンドを入力してください。上記の場合はSQL Server 2005 Expressの サービスでサービスの状態を「再開」に変更、スタートアップの種類を「自動」に 変更しようとしています。実行結果が以下のように出力されると思います。
(停止させちゃまずいサービスもあるので気をつけてやってください!!)

以上でVC++ 2005での実装を終わりますが、あとVC++ではATL + WMIでの方法も ありますがVC++をつかってわざわざ重い処理をすることに疑問を感じますので また機会があれば書いていきたいと思います。