こんな感じでやってる、ってことで。
あるプログラムが異常を検出し、停止するときに、そいつに情報を提供するサービスと監視タスクも止めなければならなくなりました。そこの部分です。
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