Mutexによる二重起動防止で2回目起動しなくなる原因は?

解決


ほんちゃん  2008-05-13 10:02:52  No: 68300  IP: 192.*.*.*

どなたか教えてください。お願いします。

VS2005 MFC SDI アプリです。
二重起動防止のため次のコードを入れています。

<Komatta.cpp>

#define APP_NAME _T("KOMATTA-APP")
HANDLE m_hMutexApp;

BOOL CKomattaApp::InitInstance()
{
    ・・・・・
    m_hMutexApp = CreateMutex(NULL, TRUE, APP_NAME);
    if (GetLastError() == ERROR_ALREADY_EXISTS)
    {
        MessageBox(NULL, _T("すでに起動しています"), _T("二重起動の警告"), MB_ICONWARNING);
        ReleaseMutex(m_hMutexApp);
        return FALSE;    // ここで終了
    }
    ・・・・・
}

int CKomattaApp::ExitInstance() 
{
    ReleaseMutex(m_hMutexApp);
    CloseHandle(m_hMutexApp);

    return CWinApp::ExitInstance();
}

自分の環境(WindowsXP SP3)では問題なく機能するのですが、あるユーザーから「終了したのに2回目の起動で”すでに起動しています”のメッセージが出て終了する”との報告がありました。
このユーザーの環境も同じ WindowsXP SP3 です。
状況から Mutex が解放されていないような気がするのですが、上記のコードのどこがいけないのでしょうか?

編集 削除
tetrapod  2008-05-13 12:54:35  No: 68301  IP: 192.*.*.*

バグっていないと思うぞ
ユーザーのクレームの詳細を確認して味噌。たぶんこう使っているのではないかな

・1個目を起動(正常に通常画面が開く)
・2個目を起動(警告画面が開く)
・1個目を終了させるが、2個目はそのまま(警告画面だけ残っている)
・3個目を起動※=ここでどうなって欲しいのかをユーザに確認

現コードだと3個目を起動したときには警告画面が出る
ユーザは1個目を終わらせたから3個目は動いて欲しいと思っているに違いない
# なんとなく妄想してみたレベルなのでぜんぜん違っているかもしれないけどさ

編集 削除
ほんちゃん  2008-05-13 14:56:40  No: 68302  IP: 192.*.*.*

ご応答有り難うございます。

> バグっていないと思うぞ
ですよね。
しかしこのユーザは、1個目を終了した後、再度このアプリを使用しようとしたとき  ”すでに起動しています”  が出ると言っています。
ウィンドウズを再起動すればいい、とまで言っています。

#define APP_NAME _T("KOMATTA-APP")
HANDLE m_hMutexApp;

を、グローバル位置に置くとまずいことがある、なんてことがあるでしょうか?

編集 削除
tetrapod  2008-05-13 15:18:19  No: 68303  IP: 192.*.*.*

昔書いた俺ちゃんの多重起動防止ソースを探してみた
BOOL CHogeApp::InitInstance() {
  ...
  // アプリケーション終了時にOSがクローズするはずなので放置プレイする
  HANDLE hMutex=CreateMutex(0, TRUE, MUTEX_TEXT);
  if (hMutex==0 || GetLastError()==ERROR_ALREADY_EXISTS) {
    AfxMessageBox(IDS_MULTIPLE_INVOKE, MB_ICONEXCLAMATION|MB_OK);
    return FALSE;
  }
  ...
}
ぎゃふん。

提示コードは hMutex==0 の判定がないけどその辺かも。

編集 削除
ほんちゃん  2008-05-13 15:54:58  No: 68304  IP: 192.*.*.*

ご提示のコード
if (hMutex==0 || GetLastError ・・・

OR になっていますね。CreateMutex が成功しなかったときもメッセージを出す。
問題のユーザの場合はアプリを終了しても Mutex が残っている感じで、逆ですね。

Mutex の代わりに、FindWindow を使うのはいけないのでしょうか?

if (::FindWindow(NULL, _T("Komatta")) != NULL){
    AfxMessageBox(_T("すでに起動しています"), MB_OK);
    return FALSE;
    }

編集 削除
tetrapod  2008-05-13 16:36:22  No: 68305  IP: 192.*.*.*

複数個同時に起動すると FindWindow では検出できない可能性があるけど
それでもいいんだったら。

本当に「アプリケーションを終了させても Mutex が閉じていない」のであれば
そっちのほうがより大きな問題になりかねないな
他に同一名称の Mutex を開いている奴がいる、ということだから。
# 俺の提示したソースのアプリで問題になったことは無いんだが

Mutex の識別文字としてもうちょっと長い unique なものを使うほうが良いかもしれない
guidgen で作った GUID { XXXXX-YYYY-ZZZZ-... } とかどうだろうか
# 提示例は単なるサンプルです、っつーことならまあいいんだけど

編集 削除
みけ  2008-05-13 16:44:45  No: 68306  IP: 192.*.*.*

アプリケーション終了後にMutexが解放されていないのではなく、
アプリケーション終了時の処理で何か問題があって
「ウィンドウ閉じる→Mutex解放」までの間で止まっている、とか。

編集 削除
ほんちゃん  2008-05-13 18:54:07  No: 68307  IP: 192.*.*.*

terapod さん、みけさん、有り難うございます。

> Mutex の識別文字としてもうちょっと長い unique なものを・・・
このユーザに、識別文字を変えてビルドしたものも試して貰いましたが、結果は同じでした。

> 「ウィンドウ閉じる→Mutex解放」までの間で止まっている、とか。
このアプリを終了する操作は、次の2つだけです。
①メニューの「ファイル」→「アプリケーションの終了」
②タイトルバー右端のX印
影響があるかもしれないと思うコードが入っています。

void CMainFrame::OnClose()
{
    if ((m_lpfnCloseProc != NULL) && !(*m_lpfnCloseProc)(this)) return;
}

これは、印刷プレビューをそのタイトルバーのXで閉じると、アプリ本体も閉じてしまう不都合を避けるために入れています。
ネット上で教えて戴いたコードで、意味は理解できていません。 (^^;)
VC6 でビルドしたときは、印刷プレビューをXで閉じてもアプリ本体は閉じなかったのですが、VC .NETになってからこの不都合が出ています。
このコードは影響あるでしょうか?

ほかには、OnDestroy で(CMainFrame および CKomattaView)レジストリへの記憶を入れているだけです。

編集 削除
ほんちゃん  2008-05-13 18:59:20  No: 68308  IP: 192.*.*.*

tetrapod さん、お名前の綴りを間違えて済みません。

編集 削除
tetrapod  2008-05-13 23:02:33  No: 68309  IP: 192.*.*.*

えーと・・・かなりありえない状況なのだが・・・

正しくプログラムが終了しているのであれば、そのプログラムの中で開いたリソースは自動的に閉じられるので
CreateMutex したハンドルも自動的に閉じられる
(俺のコードはそれを期待しているので、得たハンドルを閉じずに放置してるわけだ)
なので Mutex による多重起動防止が誤動作する可能性として俺は3つしか思いつかなくて
1.同一名称の Mutex が別プログラムインスタンスで開きっぱなし
2.プログラムが終了しているように見えて、実は終了していない
3.Windows の不具合 (客先マシンの不具合)

1.の可能性は薄い (18:54 の発言によるなら)
2.かどうかを客先マシンのタスクマネージャで確認してみる (これも薄そうな気が)

3.であるならほんちゃん氏のマシンで再現しないのも納得かな
客先マシンの WindowsUpdate の状況であるとか
客先マシンのウイルス感染の有無であるとか
その辺を疑ってみる必要があるかもしれないな

編集 削除
ほんちゃん  2008-05-14 06:49:54  No: 68310  IP: 192.*.*.*

2.
ユーザーがタスクマネージャのスナップショット画像を送ってきましたが、「(タスク)二重起動の警告、(状態)実行中」だけが出ています。

3.
ユーザーのマシンは、最初 Windows SP2 でこの問題が出て、最近 SP3 にUpdateした後も同じだそうです。

ユーザーに、MFC SDI の空のアプリにMutex判定だけを入れたプログラムを送ってテストして貰うことにします。識別文字はユニークなものにして。

編集 削除
tetrapod  2008-05-14 08:48:39  No: 68311  IP: 192.*.*.*

2.タスクマネージャの「アプリケーション」タブでみても意味ないぞ
「プロセス」タブ+全ユーザのプロセス表示で見る必要がありそう

3−1.その昔俺が経験した内容としてはこんなのもあったな(遠い目)
「動かん」と文句言ってきたユーザーがいて折衝の結果上司が出張した
動かないマシンのウイルス検索したら汚染されてた
スタンドアローンマシンは問題なく動いた(感染してなかったので)
後でLAN内全部のマシンに感染が広がっていたと判明したらしい

3−2.WindowsUpdate だけど、もしかしたらそのお客さん
何かの beta/RC 版をインストールしていないかな
.NET Framework の beta 版とか VisualStudio2008 の beta 版とか
結構ハマルらしいよ > beta/RC 版
うまく動かなくなるとかアンインストールできないとか

編集 削除
そだ  2008-05-14 21:24:20  No: 68312  IP: 192.*.*.*

解放時に確認してみたらどうだろう。
int CKomattaApp::ExitInstance() 
{
    BOOL bl;
    bl = ReleaseMutex(m_hMutexApp);
    if(!bl) {
        /*エラー時の処理*/
        ...
    }
    CloseHandle(m_hMutexApp);

    return CWinApp::ExitInstance();
}

あとはプロセス一覧を取得することで同じ実行ファイルから起動した
プロセスを探し当てることができるみたいだからそこでダメ押しするか。
もっともここまでやるならMutexいらないような気もするなぁ

.NET でプロセスを探す例
http://program.station.ez-net.jp/special/visual_studio/csharp/2005.mutex.asp

MSサポートにあるプロセス一覧取得の例
http://support.microsoft.com/kb/175030/ja
↑のやたら長い関数

編集 削除
そだ  2008-05-14 21:33:57  No: 68313  IP: 192.*.*.*

長い関数というより長いソースコードか。
そのまま空のcppファイルにぺたってはっつけて使ったような。
BOOL WINAPI EnumProcs( PROCENUMPROC lpProc, LPARAM lParam );
だけヘッダーに出すと使いやすいかも。

編集 削除
ほんちゃん  2008-05-15 14:57:09  No: 68314  IP: 192.*.*.*

問題のユーザーから報告がありました。
1.タスクマネージャの「プロセス」タブを見ると、終了しても Komatta.exe が残っている。2回目の起動で二重起動警告メッセージが出て、これをOKで閉じると、「プロセス」タブ中の Kamatta.exe が増えていく。
2.また、MFC SDI の空のアプリにMutex判定(上記と同じコード)だけを入れたプログラムを送ってテストして貰いましたが、こちらは正常な動作をし、終了すると「プロセス」タブには残らない。

この結果から、Komatta プログラムのどこかの要因で、終了したように見えて終了していない、ということでしょうか。
しかし自分の環境では生じない訳で、ほんとに困ってしまいました。

なお、ユーザーのOSが beta/RC 版かどうかは問い合わせ中です。

編集 削除
ほんちゃん  2008-05-15 15:03:07  No: 68315  IP: 192.*.*.*

そだ  さん、有り難うございます。
前記のように、プロセスが残留しているので、プロセスを取得する方法でも同じ現象になると考えていいでしょうか。
ReleaseMutex(m_hMutexApp)の戻り値でメッセージを出すようにしたテストバージョンを、このユーザーに送ったところです。

編集 削除
tetrapod  2008-05-15 15:28:00  No: 68316  IP: 192.*.*.*

タスクマネージャがプロセス実行中と言っているのなら、疑う理由も無いし
間違いなく実行中なんでしょ

終了できない・終了しないってのは致命傷だったりするので直さないとだめそうだな
ExitInstance に到達していれば Close されているはずなので、到達していないと考えるべきで
> ReleaseMutex(m_hMutexApp)の戻り値でメッセージを出すようにしたテストバージョンを、このユーザーに送ったところです。
メッセージが出ないに100ペリカ

m_lpfnCloseProc で goole 先生に聞いてみた
http://m--takahashi.com/bbs/pastlog/08000/07975.html
違いは CFrameWnd::OnClose(); を呼んでいるかいないかにありそうな気がする

編集 削除
そだ  2008-05-15 22:43:37  No: 68317  IP: 192.*.*.*

>前記のように、プロセスが残留しているので、プロセスを取得する方法でも同じ現象になると考えていいでしょうか。
プロセス列挙しても終了したはずの1度目のプロセスが残っているなら
意味ないでしょうねぇ。1度目と2度目のプロセスが同時に列挙されます。
Mutexがどうしても使えなかった時の代変え案だと思ってください。

>> ReleaseMutex(m_hMutexApp)の戻り値でメッセージを出すようにしたテストバージョンを、このユーザーに送ったところです。
>メッセージが出ないに100ペリカ
ゾンビが問題なんでExitInstanceでもメッセージ出ないでしょうなぁ。

>1.タスクマネージャの「プロセス」タブを見ると、終了しても Komatta.exe が残っている。2回目の起動で二重起動警告メッセージが出て、これをOKで閉じると、「プロセス」タブ中の Kamatta.exe が増えていく。
>2.また、MFC SDI の空のアプリにMutex判定(上記と同じコード)だけを入れたプログラムを送ってテストして貰いましたが、こちらは正常な動作をし、終了すると「プロセス」タブには残らない。

InitInstanceで終了させたときもプロセスが残っているってことですよねぇ。気持ち悪いですねぇ。2重起動を確認する前に何か処理が始まっているのでは?
終了させるタイミングもあるようで。
http://homepage2.nifty.com/DSS/VCPP/MFC/Other/InitEnd.htm

編集 削除
ほんちゃん  2008-05-16 06:40:00  No: 68318  IP: 192.*.*.*

tetrapod さん、そだ  さん、有り難うございます。

> 終了させるタイミングもあるようで。
いやー難しい!

> 違いは CFrameWnd::OnClose(); を呼んでいるかいないかにありそうな気がする
>>> ReleaseMutex(m_hMutexApp)の戻り値でメッセージを出すようにしたテストバージョンを、このユーザーに送ったところです。
この送ったテストバージョンでは、CMainFrame::OnClose() 内に記述していたコード(m_lpfnCloseProc・・・)は削除しています。
報告を待っているところです。

編集 削除
rin  2008-05-16 10:45:08  No: 68319  IP: 192.*.*.*

>void CMainFrame::OnClose()
>{
>    if ((m_lpfnCloseProc != NULL) && !(*m_lpfnCloseProc)(this)) return;
>}
基本クラスのOnClose()は?

編集 削除
ほんちゃん  2008-05-16 16:21:33  No: 68320  IP: 192.*.*.*

rin さん
> 基本クラスのOnClose()は?
OnClose を呼び出しているのは CMainFrame だけです。
CKomattaApp, CKomattaView ・・・いずれも OnClose はありません。

編集 削除
rin  2008-05-16 17:00:02  No: 68321  IP: 192.*.*.*

void CMainFrame::OnClose()
{
    if ((m_lpfnCloseProc != NULL) && !(*m_lpfnCloseProc)(this)) return; ←条件が合致すれば終了させずに抜ける

    CMainFrameの基本クラス::OnClose()   ←正常に終了させるために基本クラスのOnCloseを呼び出す
}

こうなってないといけないはず
基本クラスのOnCloseの呼び出しは、VCの機能でOnCloseを追加した場合、デフォルトで入ってる。
それを消してないでしょうか?

編集 削除
ほんちゃん  2008-05-16 18:32:50  No: 68322  IP: 192.*.*.*

そういうお訊ねだったんですね。アサッテの答えをしてしまいました。
基本クラスのOnCloseの呼び出しは、デフォルトで入ったままです。

void CMainFrame::OnClose()
{
    // 印刷プレビュー対策
    if ((m_lpfnCloseProc != NULL) && !(*m_lpfnCloseProc)(this)) return;

    // ダーティ処理
    CFrameWnd* pFrame = (CFrameWnd*)AfxGetMainWnd();
    CKomattaView* pView = (CKomattaView*)pFrame->GetActiveView();
    if (pView->・・・

    CFrameWnd::OnClose();
}

編集 削除
rin  2008-05-16 20:32:16  No: 68323  IP: 192.*.*.*

なるほど了解です

編集 削除
 2008-05-16 21:34:58  No: 68324  IP: 192.*.*.*

ありそうなのがスレッドを作成していて
正常に終わっていないとか。

編集 削除
ほんちゃん  2008-05-16 22:27:53  No: 68325  IP: 192.*.*.*

皆さん色々アドバイス戴き感謝しています。
とさん、スレッドは作成していません。

自分の WindowsXP では異状なく動作し、このユーザーの WindowsXP でハンパな終了の仕方をしているというのが、解明を難しくしています。

ユーザーから応答がまだありません。嫌われたかも・・・?

編集 削除
ほんちゃん  2008-05-17 19:43:23  No: 68326  IP: 192.*.*.*

2日半経ちましたが、ユーザーから応答がありません。

ネット上で調べていたら、タスクマネージャのアプリケーションタブでは消えているが、プロセスタブには残るという現象はあるようですね。
TerminateProcess や ExitProcess という関数があることも知りました。
だが、あまり使わない方がいいような感じ・・・。
いずれにしても自分のOSで問題の現象が生じないと、対策の検証ができません。このアプリは、自分の Vista および 98SE でも確認しています。
発想を変えて、アプリケーションタブでは消えてプロセスタブには残るようにするにはどうしたらいいか・・・と思いましたが、自分のスキルでは無理です。

時間つなぎに無駄口を申しました。

編集 削除
ほんちゃん  2008-05-18 06:41:25  No: 68327  IP: 192.*.*.*

ユーザーから報告がありました。忙しかったそうです。

1.印刷プレビュー対策のコードを外したバージョンでも、同じ現象である。
    (起動→終了でプロセスタブに残り、2回目の起動ができない)

2.次のコードを入れていましたが、「Mutexは解放されません」というメッセージが出る。
int CKomattaApp::ExitInstance() 
{
    // 多重起動防止用 Mutex の解放
    if (!ReleaseMutex(m_hMutexApp))  MessageBox(NULL, _T("Mutexは解放されません"), _T("Mutex解放判定"), MB_ICONWARNING);
    CloseHandle(m_hMutexApp);

    return CWinApp::ExitInstance();
}

さて、どう考えたらいいのでしょうか。先生方、お助けください。 m(_ _)m

編集 削除
n  2008-05-18 12:00:17  No: 68328  IP: 192.*.*.*

多重起動防止にはミューテックスの所有権は関係有りません。
bInitialOwner に FALSE を、ReleaseMutex() は無しにして、
CloseHandle(m_hMutexApp) の戻り値を調べるべきです。

編集 削除
rin  2008-05-18 13:45:42  No: 68329  IP: 192.*.*.*

>2.また、MFC SDI の空のアプリにMutex判定(上記と同じコード)だけを
>入れたプログラムを送ってテストして貰いましたが、
>こちらは正常な動作をし、終了すると「プロセス」タブには残らない。
これとは逆に
多重起動処理を抜いたアプリケーションをつくり
一回の起動・終了後に、プロセスに名前が残ってるかどうか
確認してもらうのはどうでしょうか?

ExitInstanceにたどり着いてるとはいえ、
多重防止の問題ではなく、
アプリケーションの終了処理に問題があるように見えます

ところで
>    // ダーティ処理
これがちょっと気になります。

編集 削除
ほんちゃん  2008-05-18 17:02:49  No: 68330  IP: 192.*.*.*

n さん、ありがとうございます。
> bInitialOwner に FALSE を、
これ、ちょっと理解できないのですが、もうちょっと補足して戴けませんか?

rin さん
> 多重起動処理を抜いたアプリケーションをつくり
> 一回の起動・終了後に、プロセスに名前が残ってるかどうか
> 確認してもらうのはどうでしょうか?
これ、調べて貰います。
ダーティ処理も抜いたバージョンにします。

このユーザーさんの協力が必要で、ゆっくりやりますので、先生方ご容赦ください。

編集 削除
n  2008-05-18 21:07:05  No: 68331  IP: 192.*.*.*

bInitialOwner は CreateMutex() の引数です

編集 削除
ほんちゃん  2008-05-19 22:39:58  No: 68332  IP: 192.*.*.*

n さん、愚かな質問で済みませんでした。ありがとうございました。

只今、ユーザーから報告がありました。
やはり、起動→終了を繰り返すたびにプロセスタブに Komatta.exe が増えていくそうです。
今回テストして貰ったのは、二重起動防止、ダーティ処理および印刷プレビュー対策をすべて削除したバージョンです。
この結果で、rin さんが言われた
> 多重防止の問題ではなく、アプリケーションの終了処理に問題があるように見えます
ことが明らかになりました。
終了処理に関して特別なことはしていないつもりですが、コードをチェックして、またご相談させて戴きます。

編集 削除
ほんちゃん  2008-06-06 09:55:13  No: 68333  IP: 192.*.*.*

問題点が判りました。

このアプリは、CFormView で作っています。
フォームビューに139個のコントロールを配置しています。
コントロールは、リソースファイルに記述しています。
種類は、押しボタン、ラジオボタン、チェックボタン、エディット、コンボボックス、スピンおよびテキストです。
149個を3通りのメニュー(A,B,C)で切り替えています。
切り替えは、ShowWindow(SW_HIDE) および ShowWindow(SW_SHOW) で行っています。
メニューAのとき使うのは60個、Bのとき57個、Cのとき22個です。

このユーザーに5通りのプログラムをテストして貰った結果は、次の通りです。
①Aに使う60個のみをリソースファイルに記述
②Bに使う57個のみをリソースファイルに記述
③Cに使う22個のみをリソースファイルに記述
④A+Bの117個をリソースファイルに記述
⑤A+B+Cの139個をリソースファイルに記述
【テスト結果】
①②③の場合は、起動→終了でタスクマネージャーのプロセスタブから消える。
④⑤の場合は、起動→終了してもプロセスタブに残る。

すなわち、このユーザーの場合「リソースファイルに記述してあるコントロールの数が、ある程度以上多いと、終了してもプロセスタブから消えない」ということです。

タイトルの”Mutexによる二重起動防止”とは関係ないようなので、このスレッドを「解決」にして、別のスレッドで、この原因についてご相談することにします。
先生方、どうもありがとうございました。

編集 削除
ほんちゃん  2008-06-06 09:58:35  No: 68334  IP: 192.*.*.*

「解決」のチェックを入れるのを忘れました。  (*^_^*)

編集 削除