PCをログオフ時に強制的にアプリケーションが終了される時に何らかの処理を行うには?

解決


どら  2008-02-07 21:11:56  No: 67452

こんにちは。
今回はちょっと変わった質問かもしれません。

実は、現在ローカルシステムサービスで起動するExeとクライアントが実行す
るExeの2つをセットで作成しています。

サービス側では、名前付きパイプを作成し、クライアントからのイベントが上
がるのを、WaitForSingleObject関数で待機し、イベントが上がるとパイプの
読込を行うように作っています。

クライアント側のExeでは、SetEvent関数でイベントをサービスへ通知後、パ
イプにデータを送り込む用に作り込んでいます。

ここで質問なのですが、パイプで渡すデータに、クライアントExeを終了する
と言うフラグを設けており、クライアントexeの終了時にこのフラグをたてて
パイプを送るようにしているのですが・・・
(これを行わないと、クライアントのみをいったん終了→起動と行った時点で
パイプに接続できなくなってしまいます[サービスを再起動すると復活します])

このクライアント用のexeはタスクトレイに常駐し、左クリックでメニューを
表示、選択した処理の命令をパイプでサービスに送る、という動きになってい
るため、ユーザがクライアントexeを意図的に終了すると言うことはあまりあ
りません。

そこで問題になっているのが、コンピュータをログオフした時の動作です。
ログオフすると、そのユーザで起動していたアプリケーションは強制的に終了
されますよね?
この時に、クライアント側から終了パイプを送れないため、サービスを再起動
しない限り、クライアント側からパイプを送り込めなくなってしまいます。

このような場合、クライアント/サーバそれぞれの作りを変更して改善するこ
とが可能なのでしょうか?

長々とすみませんが、よろしくおねがいいたします。
念の為、サーバ側と、クライアント側のパイプのやり取りにかかわる部分のみ
を抜粋してのせておきます。

[サーバ側]
HANDLE hSettingThread = NULL;
HANDLE hEventSvToCl = NULL;  
HANDLE hEventClToSv = NULL;          

//Serviceのメインスレッド
DWORD ServiceThread(LPDWORD param)
{
   DWORD l_dwThreadID;
   static HANDLE l_hPipe;
   DWORD dwPipeDataSize;
   SECURITY_ATTRIBUTES l_sa;
   SECURITY_DESCRIPTOR l_sd;

   if(!InitializeSecurityDescriptor(&l_sd, SECURITY_DESCRIPTOR_REVISION))
   {
      return -10;
   }
   if(!SetSecurityDescriptorDacl(&l_sd, TRUE, NULL, FALSE))
   {
      return -20;
   }
   l_sa.nLength = sizeof(SECURITY_ATTRIBUTES);
   l_sa.bInheritHandle = FALSE;
   l_sa.lpSecurityDescriptor = &l_sd;

   hEventClToSv = CreateEvent(&l_sa, FALSE, FALSE, "DoraClToSv");
   hEventSvToCl = CreateEvent(&l_sa, FALSE, FALSE, "DoraSvToCl");
   if(hEventSvToCl == NULL || hEventClToSv == NULL)
   {
      return -30;
   }

   while(1)
   {
      if(l_hPipe)
      {
         FlushFileBuffers(l_hPipe);
         DisconnectNamedPipe(l_hPipe);
         CloseHandle(l_hPipe);
      }
      l_hPipe = CreateNamedPipe(PIPE_NAME, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_WAIT, 1, 0, 0, 150, &l_sa);
      if(l_hPipe == INVALID_HANDLE_VALUE)
      {
         return -40;
      }

      if(!ConnectNamedPipe(l_hPipe, (LPOVERLAPPED)NULL))
      {
         CloseHandle(l_hPipe);
         return -50;
      }

      hSettingThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)SettingThreadFunc, (LPVOID)l_hPipe, 0, &l_dwThreadID);
      if(hSettingThread == NULL)
      {
         CloseHandle(l_hPipe);
         return -60;
      }

      WaitForSingleObject(hSettingThread, INFINITE);
   }

   if(hEventClToSv)
   {
      CloseHandle(hEventClToSv);
   }
   if(hEventSvToCl)
   {
      CloseHandle(hEventSvToCl);
   }
   if(hSettingThread)
   {
      CloseHandle(hSettingThread);
   }
   return 0;
}


どら  2008-02-07 21:54:36  No: 67453

すみません、ソース書きかけで書込みしちゃいました。
ソースの部分のみ書き直します。

[サーバ側]
HANDLE hSettingThread = NULL;
HANDLE hEventSvToCl = NULL; 
HANDLE hEventClToSv = NULL; 

//Serviceのメインスレッド
DWORD ServiceThread(LPDWORD param)
{
   DWORD l_dwThreadID;
   static HANDLE l_hPipe;
   DWORD dwPipeDataSize;
   SECURITY_ATTRIBUTES l_sa;
   SECURITY_DESCRIPTOR l_sd;

   if(!InitializeSecurityDescriptor(&l_sd, SECURITY_DESCRIPTOR_REVISION))
   {
      return -10;
   }
   if(!SetSecurityDescriptorDacl(&l_sd, TRUE, NULL, FALSE))
   {
      return -20;
   }
   l_sa.nLength = sizeof(SECURITY_ATTRIBUTES);
   l_sa.bInheritHandle = FALSE;
   l_sa.lpSecurityDescriptor = &l_sd;

   hEventClToSv = CreateEvent(&l_sa, FALSE, FALSE, "DoraClToSv");
   hEventSvToCl = CreateEvent(&l_sa, FALSE, FALSE, "DoraSvToCl");
   if(hEventSvToCl == NULL || hEventClToSv == NULL)
   {
      return -30;
   }

   while(1)
   {
      if(l_hPipe)
      {
         FlushFileBuffers(l_hPipe);
         DisconnectNamedPipe(l_hPipe);
         CloseHandle(l_hPipe);
      }
      l_hPipe = CreateNamedPipe(PIPE_NAME, PIPE_ACCESS_DUPLEX, PIPE_TYPE_BYTE | PIPE_WAIT, 1, 0, 0, 150, &l_sa);
      if(l_hPipe == INVALID_HANDLE_VALUE)
      {
         return -40;
      }

      if(!ConnectNamedPipe(l_hPipe, (LPOVERLAPPED)NULL))
      {
         CloseHandle(l_hPipe);
         return -50;
      }

      hSettingThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)SettingThreadFunc, (LPVOID)l_hPipe, 0, &l_dwThreadID);
      if(hSettingThread == NULL)
      {
         CloseHandle(l_hPipe);
         return -60;
      }

      WaitForSingleObject(hSettingThread, INFINITE);
   }

   if(hEventClToSv)
   {
      CloseHandle(hEventClToSv);
   }
   if(hEventSvToCl)
   {
      CloseHandle(hEventSvToCl);
   }
   if(hSettingThread)
   {
      CloseHandle(hSettingThread);
   }
   return 0;
}

//パイプでやり取りをして必要な処理をする関数
DWORD SettingThreadFunc(LPVOID lpParam)
{
   PIPEDATA l_PipeData;        //Named Pipeでやり取りするデータを格納するオリジナルの構造体
   DWORD dwByteRead;
   DWORD dwByteWrite;
   HANDLE l_hPipe;
   char Temp[2048];

   l_hPipe = (HANDLE)lpParam;

   while(1)
   {
      //スレッド終了フラグを下ろしておく
      l_PipeData.fStop = FALSE;

      WaitForSingleObject(hEventClToSv, INFINITE);
      if(!ReadFile(l_hPipe, &l_PipeData, sizeof(PIPEDATA), &dwByteRead, (LPOVERLAPPED)NULL))
      {
         break;
      }

      //パイプから終了フラグが上がっているかを確認
      if(l_PipeData.fStop)
      {
         ResetEvent(hEventClToSv);
         ResetEvent(hEventSvToCl);
         break;
      }

      //様々な処理を行いクライアント側に返送するデータを作成
      if(!SetEvent(hEventSvToCl))
      {
         break;
      }

      if(!WriteFile(l_hPipe, &l_PipeData, sizeof(PIPEDATA), &dwByteWrite, (LPOVERLAPPED)NULL))
      {
         break;
      }

      ResetEvent(hEventClToSv);
      ResetEvent(hEventSvToCl);
   }

   return 0;

}

[クライアント側]
HANDLE hFile;
HANDLE hEventSvToCl;
HANDLE hEventClToSv;

LRESULT CALLBACK WinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
   static PIPEDATA l_PD;
   static DWORD l_dwReturn;
   DWORD l_dwByteRead;
   DWORD l_dwByteWrite;

   switch (msg) 
   {
      case WM_CREATE:
         hEventClToSv = OpenEvent(EVENT_ALL_ACCESS, FALSE, "DoraClToSv");
         if(hEventClToSv == NULL)
         {
            MessageBox(hWnd, "起動エラー\n\nサーバへ送るイベントを取得できませんでした。", ClassName, MB_OK);
            DestroyWindow(hWnd);
            PostQuitMessage(0);
            break;
         }

         hEventSvToCl = OpenEvent(EVENT_ALL_ACCESS, FALSE, "DoraSvToCl");
         if(hEventSvToCl == NULL)
         {
            MessageBox(hWnd, "起動エラー\n\nサーバから受け取るイベントを取得できませんでした。", ClassName, MB_OK);
            DestroyWindow(hWnd);
            PostQuitMessage(0);
            break;
         }

         hFile = CreateFile(PIPE_NAME, GENERIC_READ | GENERIC_WRITE, 0, (LPSECURITY_ATTRIBUTES)NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, (HANDLE)NULL);
         if(hFile == INVALID_HANDLE_VALUE)
         {
            MessageBox(hWnd, "起動エラー\n\nパイプをオープンできませんでした。", ClassName, MB_OK);
            DestroyWindow(hWnd);
            PostQuitMessage(0);
            break;
         }

         ・・・中略

         case WM_COMMAND:
            switch(LOWORD(wParam)
            {
               case IDC_XXXX:
                  if(SetEvent(hEventClToSv) != TRUE)
                  {
                     MessageBox(hWnd, "イベントを通知できません", "エラー", MB_OK);
                     break;
                  }
                  if(!WriteFile(hFile, &l_PD, sizeof(PIPEDATA), &l_dwByteWrite, (LPOVERLAPPED)NULL))
                  {
                     MessageBox(hWnd, "名前付きパイプに書き込めません", "エラー", MB_OK);
                     break;
                  }

                  WaitForSingleObject(hEventSvToCl, INFINITE);

                  if(!ReadFile(hFile, &l_PD, sizeof(PIPEDATA), &l_dwByteRead, (LPOVERLAPPED)NULL))
                  {
                     MessageBox(hWnd, "名前付きパイプを読み込めません", "エラー", MB_OK);
                     ResetEvent(hEventSvToCl);
                     break;
                  }
                  //戻ってきたパイプデータで様々な処理

                  break;
            }

      case WM_DESTROY:
         l_PD.fStop = TRUE;
         if(SetEvent(hEventClToSv) != TRUE)
         {
            MessageBox(hWnd, "終了エラー\n\nイベントを通知できません。", ClassName, MB_OK);
            break;
         }
         if(!WriteFile(hFile, &l_PD, sizeof(PIPEDATA), &l_dwByteWrite, (LPOVERLAPPED)NULL))
         {
            MessageBox(hWnd, "終了エラー\n\n名前付きパイプに書き込めません。", ClassName, MB_OK);
            break;
         }

         PostQuitMessage(0);
         break;
   }
   return (0L);

}

一部変数の宣言漏れなどがあったらごめんなさい。
長々と申し訳ありませんが、よろしくおねがいいたします。

開発環境は Windows XP Pro with SP2 + VS.NET 2003 Enterprise です。


RAPT  URL  2008-02-07 21:58:40  No: 67454

メッセージループを回して、WM_QUERYENDSESSION、WM_ENDSESSION を処理しては?


仲澤@失業者  2008-02-07 21:59:37  No: 67455

WM_QUERYENDSESSION等のタイミングで接続解除処理を
行えば良いだけの気がしますが。どうなんでしょう。


どら  2008-02-08 01:46:19  No: 67456

RAPTさん、中澤@失業者さん

ありがとうございます。
こんな単純だったんですね。

      case WM_DESTROY:
         l_PD.fStop = TRUE;
         if(SetEvent(hEventClToSv) != TRUE)
         {
            MessageBox(hWnd, "終了エラー\n\nイベントを通知できません。", ClassName, MB_OK);
            break;
         }
         if(!WriteFile(hFile, &l_PD, sizeof(PIPEDATA), &l_dwByteWrite, (LPOVERLAPPED)NULL))
         {
            MessageBox(hWnd, "終了エラー\n\n名前付きパイプに書き込めません。", ClassName, MB_OK);
            break;
         }

         PostQuitMessage(0);
         break;

      case WM_QUERYENDSESSION:
      case WM_ENDSESSION:
      case WM_DESTROY:
         l_PD.fStop = TRUE;
         if(SetEvent(hEventClToSv) != TRUE)
         {
            MessageBox(hWnd, "終了エラー\n\nイベントを通知できません。", ClassName, MB_OK);
            break;
         }
         if(!WriteFile(hFile, &l_PD, sizeof(PIPEDATA), &l_dwByteWrite, (LPOVERLAPPED)NULL))
         {
            MessageBox(hWnd, "終了エラー\n\n名前付きパイプに書き込めません。", ClassName, MB_OK);
            break;
         }

         PostQuitMessage(0);
         break;

とちょっとはしょりましたがこれであっさりいきました!!

この2つのメッセージをしらなかった自分がちょっと情けないです。
こんな簡単な問題だったのに長々と書き込んですみませんでした。


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加