none
自アプリケーションの再起動(VC++6.0 MFC ダイアログベース) RRS feed

  • 質問

  • 環境:VC++6.0 MFC ダイアログベース

    ダイアログアプリケーションで作成した自アプリを、
    自アプリ終了後に再起動したいと考えております。


    ※別アプリなどで自アプリの終了の監視などは行わずに実現したいと考えております。
    ※自アプリの2重起動は禁止としたいです。


    例えば自アプリのファイルメニュー→再起動 を選択したときに、
    自アプリを this->EndDialog( ID_REBOOT(ユーザー定義) ); で終了し、
    CxxxApp::InitInstance() でメインダイアログの戻り値がID_REBOOT であれば、
    メインダイアログを再起動(コンストラクタ、OnInitDialogを行いたい)
    したいと考えております。

    下記のような処理を作成したのですが、
    this->EndDialog( ID_REBOOT ); で終了した後(DestroyWindow コール後)に
    2回目の DoModal() を行った場合、すぐに DestroyWindow コールされてしまい
    2回目のダイアログ表示前に終了してしまいます。


    質問1 上記のような再起動処理を実現することはできるのでしょうか
    質問2 下記コードあるいは、私の考え方で誤っている個所をご指摘いただけませんでしょうか

     


    BOOL CxxxApp::InitInstance()
    {
        int nResponse = 0;
        BOOL bRun = TRUE;
        CxxxDlg* dlg;

        // 2重起動禁止処理(Mutexを使用した監視) 記載略

        while( bRun )
        {
            dlg = new CxxxDlg();
            m_pMainWnd = dlg;

            nResponse = dlg->DoModal();
            switch( nResponse )
            {
                case ID_REBOOT :         // ユーザー定義
                    break;

                case IDOK :
                case IDCANCEL :
                    bRun = FALSE;
                    break;
            }
            delete dlg;
            dlg = NULL;
        }
    }

     

    2012年10月17日 1:09

回答

  • ミューテックスで二重起動を禁止している場合の自己再起動は、
    こんな方法でも可能です(まぁ誰でも思いつくオーソドックスな方法)。

    (終了および再起動処理の開始)
    1.何らかの方法でメインウインドウ(DLG)を終了するよう指示する。
      PostMessage()などで、遠回りに終了するのが吉。
    2.再起動であることを、コマンド文字列で指定して、ShellExecute()等で
     自分自身を起動する(この瞬間2つのインスタンスが動作する)。
    (起動処理)
    3.InitInstance()で、再起動であることを、コマンド文字列から認識したら、
     新たなミューテックスが作成できるまで(つまり、自分を起動したインスタンスが
     終了するまで)、Sleep()しながら待つ。
    4.InitInstance()内のミューテックスによる再起動防止処理は、
     ここで行う。

    自分のやりかたにこだわらなければ検討してみてはどうでしょう。

    2012年10月17日 4:06

すべての返信

  • MFCは、ウィンドウが破棄されるとき(正確には、WM_NCDESTROYメッセージの処理)で、m_MainWnd が破棄されると、PostQuitMessage 関数を呼び出すように作られています。

    これにより、メインウィンドウの寿命==アプリケーションの寿命という形をとるように仕立てられています。

    なので、もし、ID_REBOOT ではアプリを終了したくないという場合は、

    m_pMainWnd = NULL;
    EndDialog( ID_REBOOT );
    

    と、ダイアログを閉じる処理に入る直前で、メインウィンドウをNULLしてみてください。

    もしくは、ダイアログで、WM_NCDESTROY を受け取るようにして、

    if( m_pMainWnd == this ) m_pMainWnd = NULL;
    としておき、ループを抜けた後で、AfxPostQuitMessage() を呼び出すか。

    より詳しくは、MFCのソースのwincore.cpp にある、CWnd::OnNcDestory() を参照してどういう実装になっているのかを確認し、自分のアプリに合わせて適宜よけられるような処理を追加するとよいと思います。


    わんくま同盟,Microsoft MVP for Visual C++(Oct 2005-) http://blogs.wankuma.com/tocchann/

    2012年10月17日 2:16
  • ミューテックスで二重起動を禁止している場合の自己再起動は、
    こんな方法でも可能です(まぁ誰でも思いつくオーソドックスな方法)。

    (終了および再起動処理の開始)
    1.何らかの方法でメインウインドウ(DLG)を終了するよう指示する。
      PostMessage()などで、遠回りに終了するのが吉。
    2.再起動であることを、コマンド文字列で指定して、ShellExecute()等で
     自分自身を起動する(この瞬間2つのインスタンスが動作する)。
    (起動処理)
    3.InitInstance()で、再起動であることを、コマンド文字列から認識したら、
     新たなミューテックスが作成できるまで(つまり、自分を起動したインスタンスが
     終了するまで)、Sleep()しながら待つ。
    4.InitInstance()内のミューテックスによる再起動防止処理は、
     ここで行う。

    自分のやりかたにこだわらなければ検討してみてはどうでしょう。

    2012年10月17日 4:06

  • とっちゃん 様

    ご回答いただきありがとうございました。
    具体的な例をご教授下さりありがとうございます。

    > メインウィンドウの寿命==アプリケーションの寿命
    について、
    ・MSDNの記述の確認
    ・ステップ実行で、CWnd::OnNcDestroy で 
     pThread->m_pMainWnd = NULL; していること
    を確認できました。勉強不足でした。

     

    2012年10月17日 11:25
  • 仲澤@失業者 様

    ご回答いただきありがとうございました。
    具体的な対応案についてご教授をいただき、ありがとうございます。

    ・MFCでのコマンドラインの取得
    ・ミューテックスの終了待機
    など、私自身が使ったことが無いため
    まずは教えていただいた方法について試してみたいと思います。

    明日以降コーディングを行い、結果をご報告したいと思います。

    2012年10月17日 11:25
  • 仲澤@失業者 様

    ・コマンドライン引数の使用
    ・InitInstance でのミューテックスを使用した
     前回起動の終了待ちと、今回の起動
    について試作を行い、期待する動作ができることを確認できました。

    具体的には下記の通りです。
    改善箇所等ありましたらご指摘を頂ければと思います。

    とっちゃん 様
    仲澤@失業者 様

    今回はご回答いただきありがとうございました。

     -------------------------------------------------------------------------------

    BOOL CxxxApp::InitInstance()
    {
      HANDLE  hMutex = NULL;
      BOOL  bRetry = TRUE;      // 前回の起動に対して終了待ちを行うか
      BOOL  bReboot = FALSE;    // 再起動か
      long  lLoop = 0;          // デバッグ用カウンタ
      CString strLog;           // ワーク用

      // コマンドライン解析
      //   自アプリからの再起動の場合、コマンドライン引数を以下のようにする
      //   __argv[ 0 ] : 実行ファイルパス
      //   __argv[ 1 ] : /reboot
      if( __argc == 2 )
      {
        CString  strCmdLineParam = __argv[ 1 ];
        if( 0<= strCmdLineParam.Find( "/reboot" ) )
        {
          bReboot = TRUE;
        }
      }

      while( bRetry == TRUE )
      {
        hMutex = CreateMutex( NULL, TRUE, "Cxxx" );
        if( hMutex == NULL )
        {
          AfxMessageBox( "CreateMutex 失敗しました" );
          return FALSE;
        }
        else
        {
          if( GetLastError() == ERROR_ALREADY_EXISTS )
          {
            // コマンドランを使用した再起動指示
            if( bReboot == TRUE )
            {
              strLog.Format( "[ %ld ] 前回の起動 終了待ち\n", lLoop );
              TRACE( strLog );

              Sleep( 100 );

              ReleaseMutex( hMutex );
              CloseHandle( hMutex );
            }

            // 2重起動
            else
            {
              strLog.Format( "[ %ld ] 2重起動禁止\n", lLoop );
              TRACE( strLog );
              AfxMessageBox( strLog );

              ReleaseMutex( hMutex );
              CloseHandle( hMutex );

              // アプリケーションの起動中止
              return FALSE;
            }
          }
          else
          {
            bRetry = FALSE;

            strLog.Format( "[ %ld ] 新規起動\n", lLoop );
            TRACE( strLog );
          }
        }
        lLoop++;
      }
      CxxxDlg dlg;
      m_pMainWnd = &dlg;
      int nResponse = dlg.DoModal();
      if (nResponse == IDOK)
      {
        // TODO: ダイアログが <OK> で消された時のコードを
        //       記述してください。
      }
      else if (nResponse == IDCANCEL)
      {
        // TODO: ダイアログが <キャンセル> で消された時のコードを
        //       記述してください。
      }

      // 新規起動時に対する終了処理
      ReleaseMutex( hMutex );
      CloseHandle( hMutex );

      return FALSE;
    }

    2012年10月18日 2:01