何となく Blog by Jitta
Microsoft .NET 考

目次

Blog 利用状況
  • 投稿数 - 761
  • 記事 - 18
  • コメント - 37042
  • トラックバック - 222
ニュース
  • IE7以前では、表示がおかしい。div の解釈に問題があるようだ。
    IE8の場合は、「互換」表示を OFF にしてください。
  • 検索エンジンで来られた方へ:
    お望みの情報は見つかりましたか? よろしければ、コメント欄にどのような情報を探していたのか、ご記入ください。
It's ME!
  • はなおか じった
  • 世界遺産の近くに住んでます。
  • Microsoft MVP for Visual Developer ASP/ASP.NET 10, 2004 - 9, 2011
広告

記事カテゴリ

書庫

日記カテゴリ

ギャラリ

その他

わんくま同盟

同郷

 

こんな感じでやってる、ってことで。


あるプログラムが異常を検出し、停止するときに、そいつに情報を提供するサービスと監視タスクも止めなければならなくなりました。そこの部分です。


BOOL CSample::terminate(void)
{
    // TODO: サービスを止める
    // TODO: 監視タスクを止める
    return FALSE;    // TODO: 実装が終わったら TRUE にする
}

まず、これだけ。ここで、「サービスを止める」のと、「監視タスクを止める」のは、「終了する」ために必要なことだけど、別々の処理なので、それぞれ関数に切り出す。


BOOL CSample::terminate(void)
{
    // サービスを止める
    if (terminateService() == FALSE) {
        return FALSE;
    }
    // 監視タスクを止める
    if (terminateWatcher() == FALSE) {
        return FALSE;
    }
    return TRUE;
}

「サービスを止める」方から。これも、しなければならないことと、その順番を考える。こんな感じ?


BOOL terminateService(void)
{
    // TODO: サービスにアクセスする
    // TODO: サービスに停止信号を送る
    return FALSE;   // TODO: 実装が終わったら TRUE にする
}

まだヘッダー ファイルに書き込んでいないので、クラス指定をしていません。

ここから MSDN ライブラリにアクセス。それぞれの方法を調べます。ちなみに、これは VisualStudio.NET 2003 C++/MFC。

私の VS.NET 2003 連結コレクションは、PSDK 用のフィルタが作ってあります。11月24日の話でもふれますが、VS 2005 用からはこれがなくなったのが不満。

それはともかく、とりあえず検索。検索キーワードは「サービス NEAR 停止」。これで、「サービス」という語の8語以内に「停止」という語があるトピックがピックアップされます。「サービスを停止させる」で検索しないのは、これだと「サービス」と「停止させる」に分解されるためです。このため、「サービス」と「停止させる」の両方を含んだページもヒットしてしまいます。停止させられるのがサービスでなくても、同じページに「サービス」と書かれていればヒットしてしまいます。

フィルタの作りによりますが、私の場合は検索結果の5位に、「ControlService 関数」が、「プラットフォーム SDK: DLL プロセス スレッド」から見つかりました。これを見ます。

制御コード「SERVICE_CONTROL_STOP」を送れば、停止させられます。これで下の方、「サービスに停止信号を送る」はできました。

この関数への引数を見ると、第一引数が「SC_HANDLE hService, // サービスのハンドル」となっています。これが上の方、「サービスにアクセスする」です。これは、OpenService 関数か、CreateService 関数から返されるとあります。今行いたいことは、すでに動いているサービスにアクセスすることなので、OpenService 関数で良さそうです。で、ポチッとリンクをクリックします。

一番最初に目に飛び込んできたのが「SC_HANDLE hSCManager, // SCM データベースのハンドル」・・・って、まだ呼び出しがいるのかっ!リンクをポチッとな。

OpenSCManager が一番上のようだ。返されたハンドルは CloseServiceHandle 関数で閉じる必要がある。といったことがわかるので、その辺をコードに追加します。

おっと忘れてました。下の方、「対応情報」から、必要なヘッダーファイルと、インポート ライブラリも調べて、それぞれ適切に追加しておきます。


BOOL terminateService(void)
{
    BOOL   retValue = FALSE;
    // サービスにアクセスする
    SC_HANDLE    scm;
    scm = OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, GENERIC_READ);
    if (scm != NULL) {
        SC_HANDLE   svc;
        svc = OpenService(scm, _T("SomeService"), SERVICE_STOP);
        if (svc != NULL) {
            // TODO: サービスに停止信号を送る
            // サービスから離脱
            (void) CloseServiceHandle(svc);
        }
        // サービスマネージャから離脱
        (void) CloseServiceHandle(scm);
    }
    return retValue;
}

さて、ControlService です。ここの説明を見ると、停止しているサービスに対して停止信号を送ると、関数は失敗を返すことがわかります。これでは単純に関数の成否を、サービスを停止させることの成否とはできません。よって、前もってサービスの状態を調べる必要もありそうです。よって、TODO とアクセス権を追加します。

サービスの状態を問い合わせるのは QueryServiceStatus 関数です。これを呼び出し、SERVICE_STATUS.dwCurrentStatus が SERVICE_STOPPED, SERVICE_STOP_PENDING でないとき、ControlService 関数を呼び出せば良さそうです。


BOOL terminateService(void)
{
    BOOL    retValue = FALSE;
    // サービスにアクセスする
    SC_HANDLE   scm;
    scm = OpenSCManager(NULL, SERVICES_ACTIVE_DATABASE, GENERIC_READ);
    if (scm != NULL) {
        SC_HANDLE   svc;
        svc = OpenService(scm, _T("SampleService")
            , SERVICE_STOP | SERVICE_QUERY_STATUS);
        if (svc != NULL) {
            SERVICE_STATUS  sts;
            // サービスの状態を問い合わせる
            if (QueryServiceStatus(svc, &sts) == TRUE) {
                if (sts.dwCurrentState != SERVICE_STOPPED
                    && sts.dwCurrentState != SERVICE_STOP_PENDING) {
                    // サービスに停止信号を送る
                    retValue = ControlService(svc
                        , SERVICE_CONTROL_STOP, &sts);
                } else {
                    // 停止中、または停止が保留されているので、成功とする
                    retValue = TRUE;
                }
            }
            // サービスから離脱
            (void) CloseServiceHandle(svc);
        }
        // サービスマネージャから離脱
        (void) CloseServiceHandle(scm);
    }
    return retValue;
}

ControlService 関数がエラーを返した後、GetLastError 関数でその原因を調べる、という方法もあります。しかし、.NET Framework などで例外を使うようになったとき、例外は発生するときに大きなコストを必要とします。そのため、事前に調べられるなら事前に調べる癖を付けるために、先に調べるようにしました。



今回は異常処理用のルーチンで、「異常があるのは当たり前」のため、エラー処理はまったく行っていません。よって、このコードを「サービスを停止させるためのサンプル コード」として使用してはいけません。

このエントリでは、「いきなりコードを書くのではなく、最終目的を達成するために必要な手順を考える」ことを主眼としています。

投稿日時 : 2007年11月10日 21:10
コメント
  • # re: プログラミングの前に設計
    シャノン
    Posted @ 2007/11/11 14:26
    QueryServiceStatus でサービス状態を確認した後、ControlService を呼び出す前に、他人が MMC からサービス止めちゃったら大丈夫か? とか、自分で作る時は気にもしないことを他人にケチつける時には思いつく。
    そういえば LockServiceDatabase なんていう関数があったけど、日本語版 MSDN には「サービスの開始をロックする」としか書いてなくて、終了はどうなるのかよくわからない。
    英語版を見てみたら、しれっと「Vista と 2008 じゃ使えません」とか書いてあるの。なにそれー!
  • # re: プログラミングの前に設計
    Jitta
    Posted @ 2007/11/12 7:24
    止めることが目的、止まった状態になっていればいいので、いいの!
    つか、ヤバいかも?
    「自爆再生ルーチン」なんか仕込んであるんだよ
    そっちに入ってたら、再起動しちゃうなぁ...
タイトル
名前
Url
コメント