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

[ 上へ ]

・メンバ変数/コンストラクタ/デストラクタ

まず、サービス名を保持するメンバ変数を追加します。ツールバーの表示(V)→クラス ビュー(A)でクラスビューを表示してください。そこでCWinServiceクラスを選択して 右クリックすると追加メニューがあらわれるのでクリックし、変数の追加(B) を選択するとダイアログが現れます。

アクセスはprotected、変数の種類はLPTSTR、変数名は m_pSvcNameとして完了ボタンを押してください。

アクセス制御は万が一?継承することを考慮して protectedにしました。派生クラスにサービス名がないと困りますから。

ところでLPTSTRとはどんな型?と疑問をもたれる方もいるでしょう。
サービス名を保持するのですから文字列です。今回は文字へのポインタとして処理しますが、 そうすると char *と思われるはずです。ですが、ANSIとUNICODEの違いがあるのでそれを 吸収するためにTCHARという型が定義されています。

#ifdef UNICODE
typedef WCHAR TCHAR;
#else
typedef char TCHAR;

とすると、TCHAR *とすればいいと思いますが、これもwinnt.hに
UNICODEの場合は

typedef WCHAR *LPWSTR;
typedef LPWSTR LPTSTR;

ANSIの場合は

typedef CHAR *LPSTR;
typedef LPSTR LPTSTR;

とあるので、TCHAR *LPTSTRとなります。

class CWinService
{
public:
    CWinService(void);
    ~CWinService(void);

protected:
    LPTSTR m_pSvcName; //サービス名
};

次にコンストラクタのプロトタイプを記述します。クラスを作成した時に 出来てたのでは? もちろん出来てました。でももう一つ大事なコンストラクタがあります。 コピーコンストラクタです。

メンバ変数m_pSvcNameの型がポインタなので、クラスインスタンスのコピーが 行われた時、何も明示してないとC++は既定のコピーコンストラクタを呼び出して、 コピー元のポインタの値をコピー先のメンバ変数にそのままコピーします。しかしその後、 例えばコピー元のインスタンスが破壊されたとすると、そのコピー元のメンバのポインタが 指すメモリも解放されるわけですから、残ったコピー先のメンバのポインタが指すメモリ領域は デタラメになってしまいます。そこでちゃんとコピーコンストラクタを定義してそこで 新しいメモリ領域にコピー元の文字列(サービス名)を入れる処理をします。

先ほどのメンバ変数の時と同様にクラスビュー→(右クリック)→ 追加から、関数の追加(U)を選択してください。

上記ダイアログに

戻り値の型(Y)   :  (空欄)
関数名          :   CWinService         
パラメータの型  :   const CWinService &
パラメータ名    :   inst (入力したら追加ボタンクリック)
アクセス        :   public

として完了ボタンを押してください。

//CWinService.h//////////////////////////////////
CWinService(const CWinService & inst);

//----------------------------------------------------------
//CWinService.cpp//////////////////////////////
CWinService::CWinService(const CWinService & inst)
{
}

あと、サービス名を引数にしたコンストラクタも作成しておきます。

戻り値の型(Y)   :   (空欄)
関数名          :   CWinService
パラメータの型  :   LPCTSTR
パラメータ名    :   pName
アクセス        :   public
//CWinService.h//////////////////////////////////
CWinService(LPCTSTR pName);

//----------------------------------------------------------
//CWinService.cpp//////////////////////////////
CWinService::CWinService(LPCTSTR pName)
{
}

設定するサービス名を受け取るLPCTSTR型は、LPTSTRと似てますが、LPTSTRと違う点は constなところです。値を変えようとした実装はコンパイラにしかられます。

次に、このままではメンバ変数m_pSvcNameのポイントしているアドレスが不明で 気持ち悪いし、バグのもとなのでイニシャライザでNULLを入れておきます。

// CWinService.cpp//////////////////////////////////
//デフォルトコンストラクタ
CWinService::CWinService() : m_pSvcName(NULL)
{
}
//サービス名を引数にしたコンストラクタ
CWinService::CWinService(LPCTSTR pName) : m_pSvcName(NULL)
{
}
//コピーコンストラクタ
CWinService::CWinService(const CWinService & inst) : m_pSvcName(NULL)
{
}

イニシャライザではなく、コンストラクタ内で下記のように

CWinService::CWinService(LPCTSTR pName)
{
    m_pSvcName = NULL;
}

NULLをいれてもいいかもしれませんが、コンストラクタが実行されるときはメンバは すでに構築されているので、メンバが構築される時に初期化するイニシャライザで 対応するのがいいかと思います。

ではコンストラクタの実装ですが、引数付きコンストラクタもコピーコンストラクタも サービス名を受け取るメモリ領域を用意してそこに値(文字列)をいれる同じ処理なので冗長をなくすため、 サービス名を設定するメンバ関数をコピーコンストラクタの時のように関数の追加ウィザード から作ってください。

戻り値の型      : BOOL
関数名          :  ServiceName
パラメータの型  :  LPCTSTR
パラメータ名    :  pSvcName
アクセス制御    :  public

引数はサービス名へのポインタ、戻り値はサービス名が設定できたときはTRUE、 失敗した場合はFALSEです。メンバ関数の宣言ができたら以下のように実装してください。 アクセス制御は引数なしでCWinServiceのインスタンスを作成したときに後でサービス名を 設定できるようにpublicにします。

//CWinService.cpp//////////////////////////////////// 
// サービス名設定
BOOL CWinService::ServiceName(LPCTSTR pSvcName)
{
    if (m_pSvcName)                             //(A)
        delete [] m_pSvcName;
        
    if (!pSvcName)                              //(B)
    {
        m_pSvcName = NULL;          return TRUE;
    }

    size_t len = lstrlen(pSvcName);             //(C)
    m_pSvcName = new TCHAR[len + 1];            //(D)

    if (!lstrcpy(m_pSvcName, pSvcName))         //(E)
    {
        delete [] m_pSvcName;
        m_pSvcName = NULL;          return FALSE;
    }
    return TRUE;
}

(A)でm_pSvcNameポインタがNULLでないなら(何かサービス名が保持されている)、 その文字列を削除します。(何もサービス名がないときはNULLポインタにする仕様です)
(B)で設定するサービス名(pSvcName)がNULLならm_pSvcNameにもNULLを代入して終了。
(C)で何かしらサービス名を設定しようとする場合は、lstrlenで引数pSvcNameで受け取った サービス名の文字列の長さをTCHAR単位で取得します。
(D)で、(C)で受け取ったサイズ + 1(lstrlenはNULL文字の分を計算してくれないので+1)の メモリ領域を確保し先頭アドレスをm_pSvcNameに代入します。
(E)のlstrcpyで(D)で作成した領域に設定しようとするサービス名の文字列をコピーします。 失敗した場合はm_pSvcNameにNULLを代入して終了。

以上でサービス名を設定するメンバ関数ができたので、 デフォルトコンストラクタ以外でこれを呼び出すようにします。

//サービス名を引数にしたコンストラクタ
CWinService::CWinService(LPCTSTR pName) : m_pSvcName(NULL)
{
    ServiceName(pName);
}
//コピーコンストラクタ
CWinService::CWinService(const CWinService & inst) : m_pSvcName(NULL)
{
    ServiceName(inst.m_pSvcName);
}

コピーコンストラクタの引数について書いてなかったのでちょっと書いておきます。 引数instはコピー元のCWinServiceインスタンスへの参照でその値が変えられないconstということです。 関数の引数としてこのクラスのインスタンスを渡したり、戻値として返したり、

CWinService svc1(_T("MSSQL$SQLEXPRESS"));
CWinService svc2 = svc1;

と したときにsvc2はコピーコンストラクタが呼ばれます。 instは引数の場合は呼び出し元の、戻値の場合は呼び出し先のインスタンスへの参照であり、 上記の場合はsvc1への参照です。

最後にデストラクタで保持しているサービス名のポインタ(m_pSvcName)を開放します。

//CWinService.cpp////////////////////////////
CWinService::~CWinService()
{
    if (m_pSvcName)
        delete [] m_pSvcName;
}

・サービス状態の取得

用意ができたところでそれでは本題のサービス状態の取得の実装をします。サービスの状態は VBA-API使用編の事例 でも書きましたがSERVICE_STAUS構造体のdwCurrentStateメンバの 値ですが、この型はDWORDで分かりにくいしエラー時の戻値の問題もあるので別に定義します。 CWinServiceクラス内に定義したほうがいいかもしれませんが、今回はグローバルで列挙定数として SVC_STATUSという名前でCWinService.hに定義します。

//CWinService.h////////////////////////////////////////////
enum SVC_STATUS {
    SVC_STOPPED          = 0x00000001,
    SVC_START_PENDING    = 0x00000002,
    SVC_STOP_PENDING     = 0x00000003,
    SVC_RUNNING          = 0x00000004,
    SVC_CONTINUE_PENDING = 0x00000005,
    SVC_PAUSE_PENDING    = 0x00000006,
    SVC_PAUSED           = 0x00000007,
    SVC_NONE             = 0x00000000
};

class CWinService
{
// ・
// ・
};

次に同様の手順でクラスビューからメンバ関数の追加ウィザード でサービス状態の取得用メンバ関数を以下のように定義してください。

戻値の型            : SVC_STAUS
関数名              : Status
パラメータの型      : (空欄)
アクセス制御        : public

できたメンバ関数に以下の実装をしてください。

// サービス状態取得
SVC_STATUS CWinService::Status()
{
    SVC_STATUS      ret         = SVC_NONE;
    SC_HANDLE       hSCManager  = NULL;
    SC_HANDLE       hService    = NULL;
    SERVICE_STATUS  stStatus;

    while (TRUE)
    {
        hSCManager = ::OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
        if (!hSCManager) break;

        hService = ::OpenService(hSCManager, m_pSvcName, SERVICE_QUERY_STATUS);
        if (!hService) break;

        ::ZeroMemory(&stStatus, sizeof stStatus);
        if (!QueryServiceStatus(hService, &stStatus))
            break;

        ret = (SVC_STATUS)stStatus.dwCurrentState;

        break;
    }

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

    return ret;
}

ロジックは VBA - API編の時 と同じなので説明は割愛します。もし、APIで分からない点があればMSDN の各関数のドキュメントを参照してください。

・サービス状態の変更

次はサービス状態の変更のですが、 VBA - API編 で変更動作用の列挙定数を定義した時と同様にSVC_ACTION列挙定数として CWinService.hヘッダファイルで以下のように記述してください。

//CWinService.h////////////////////////////////////////////
enum SVC_STATUS {
    SVC_STOPPED          = 0x00000001,
    SVC_START_PENDING    = 0x00000002,
    SVC_STOP_PENDING     = 0x00000003,
    SVC_RUNNING          = 0x00000004,
    SVC_CONTINUE_PENDING = 0x00000005,
    SVC_PAUSE_PENDING    = 0x00000006,
    SVC_PAUSED           = 0x00000007,
    SVC_NONE             = 0x00000000
};

enum SVC_ACTION {
    SVC_START    = 1,
    SVC_STOP     = 2,
    SVC_PAUSE    = 3,
    SVC_CONTINUE = 4
};

class CWinService
{
// ・
// ・
};

メンバ関数をメンバ関数の追加ウィザードから以下のように作成してください。

戻値の型            : BOOL
関数名              : ChangeServiceStatus
パラメータの型      : SVC_ACTION
パラメータ名        : eAction
アクセス制御        : public

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

//CWinService.cpp/////////////////////////////////////////////
// サービス状態の変更
//引数 : eAction 変更するサービスの状態
//戻値 : 状態変更成功 - TRUE 失敗or現在の状態がeActionと同じ - False
BOOL CWinService::ChangeServiceStatus(SVC_ACTION eAction)
{
    BOOL            ret        = FALSE;     //戻値
    SC_HANDLE       hSCManager = NULL;      //SCManager制御ハンドル
    SC_HANDLE       hService   = NULL;      //サービスハンドル
    SERVICE_STATUS  stStatus;               //SERVICE_STATUS構造体
    BOOL            flg        = FALSE;     //ループ制御フラグ

    while (TRUE)
    {
        hSCManager = ::OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
        if (!hSCManager)
            break;

        hService = ::OpenService(hSCManager, m_pSvcName, SERVICE_QUERY_STATUS
                                                       | SERVICE_PAUSE_CONTINUE
                                                       | SERVICE_START
                                                       | SERVICE_STOP);

        if (!hService)
            break;

        ::ZeroMemory(&stStatus, sizeof stStatus);
        if (!::QueryServiceStatus(hService, &stStatus))
            break;

        do
        {
            switch (stStatus.dwCurrentState)
            {
            //現在停止中
            case SERVICE_STOPPED:
                flg = TRUE;
                if (eAction == SVC_START)   //開始要求
                {
                    if (!::StartService(hService, 0, NULL))
                        break;

                    while (::QueryServiceStatus(hService, &stStatus))
                    {
                        if (stStatus.dwCurrentState == SERVICE_RUNNING)
                            break;

                        ::Sleep(stStatus.dwWaitHint > 500 ?
                                                         500 : stStatus.dwWaitHint);
                    }
                    ret = TRUE;
                }
                break;
            //現在実行中
            case SERVICE_RUNNING:
                flg = TRUE;
                if (eAction == SVC_STOP)    //停止要求
                {
                    if (!::ControlService(hService, SERVICE_CONTROL_STOP,
                                                                    &stStatus))
                        break;

                    while (::QueryServiceStatus(hService, &stStatus))
                    {
                        if (stStatus.dwCurrentState == SERVICE_STOPPED)
                            break;

                        ::Sleep(stStatus.dwWaitHint > 500 ?
                                                         500 : stStatus.dwWaitHint);
                    }
                    ret = TRUE;

                } else if (eAction == SVC_PAUSE)    //一時停止要求
                {
                    if (!::ControlService(hService, SERVICE_CONTROL_PAUSE, 
                                                                    &stStatus))
                        break;

                    while (::QueryServiceStatus(hService, &stStatus))
                    {
                        if (stStatus.dwCurrentState == SERVICE_PAUSED)
                            break;

                        ::Sleep(stStatus.dwWaitHint > 500 ?
                                                         500 : stStatus.dwWaitHint);
                    }
                    ret = TRUE;
                }
                break;
            //現在一時停止
            case SERVICE_PAUSED:
                flg = TRUE;
                if (eAction == SVC_STOP)    //停止要求
                {
                    if (!::ControlService(hService, SERVICE_CONTROL_STOP,
                                                                    &stStatus))
                        break;

                    while (::QueryServiceStatus(hService, &stStatus))
                    {
                        if (stStatus.dwCurrentState == SERVICE_STOPPED)
                            break;

                        ::Sleep(stStatus.dwWaitHint > 500 ?
                                                         500 : stStatus.dwWaitHint);
                    }
                    ret = TRUE;

                } else if (eAction == SVC_CONTINUE) //再開要求
                {
                    if (!::ControlService(hService, SERVICE_CONTROL_CONTINUE,
                                                                    &stStatus))
                        break;

                    while (::QueryServiceStatus(hService, &stStatus))
                    {
                        if (stStatus.dwCurrentState == SERVICE_RUNNING)
                            break;

                        ::Sleep(stStatus.dwWaitHint > 500 ?
                                                         500 : stStatus.dwWaitHint);
                    }
                    ret = TRUE;
                }
                break;
            //現在Pending状態
            default:
                ::Sleep(stStatus.dwWaitHint > 500 ? 500 : stStatus.dwWaitHint);
                ::QueryServiceStatus(hService, &stStatus);
            }

        } while (!flg);

        break;
    }

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

    return ret;
}

引数には変更したいサービスの状態をSVC_ACTION型で与えて、 変更が成功したら、TRUE、失敗したり変更前の状態と変更しようとする状態が同じ場合は FALSEを返します。コードは長いですが、ロジックは VBA - API編 で書いた内容と同様なのでそちらをご覧ください。

次はスタートアップの種類の変更について書いていきます。