先日、こちらの掲示板に質問させていただき、「たらこ」さんから「プロセスIDからウィンドウハンドルの取得」方法をご教授いただきました。おかげでウィンドウハンドルを取得することはできたのですが、私の勘違いで本来の問題が私の力では解決できませんでした。そこで、恐縮ですが、もう一度質問をさせていただいております。
本来の私が解決したかった問題とは、他のアプリケーションをShell関数で起動して、戻り値のプロセスIDからこのアプリケーションの終了状況をチェックしたいというものです。
いろいろなホームページを探し回って、「GetExitCodeProcess」というAPI関数を用いればこの機能を実現できるというところまではたどり着くことができました。確かに、このAPI関数を用いれば終了状況をチェックすることができるのですが、まれに終了状況を誤ることがあることに気がつきました。
「終了状況を誤る」と書きましたが、本当はAPI関数は正しく終了状況をチェックしています。しかし、次のような使い方をすると、まれに終了しているのに終了していないという返事が返ってきました。
1.Shell関数で他のアプリケーションを起動させて、戻り値のプロセスIDを保持しておく。
2.しばらく放置して、関係のない、いろいろなソフトを起動させたり終了させたりする。
3.「1.」で起動させた他のアプリケーションを終了させる。
4.再び、しばらく放置して、関係のない、いろいろなソフトを起動させたり終了させたりする。
5.保持しておいたプロセスIDを使って、「GetExitCodeProcess」を呼び出して終了状況を取得する。
6.すると、「GetExitCodeProcess」は終了していないという値を返す。
私が推測するに、「4.」において「関係のない、いろいろなソフトを起動させたり終了させたり」したために、保持しておいたプロセスIDと同じ値のプロセスIDでアプリケーションが起動して、このアプリケーションの終了状況を返しているのだと思います。一度終了したアプリケーションのプロセスIDが、他のアプリケーションの起動の際に同じプロセスIDが使われると、「GetExitCodeProcess」では厳密な終了状況を把握することができません。
そこで、プロセスIDからウィンドウハンドルを取得して、そこから得られる情報もチェックして、Shell関数で起動させたアプリケーションかどうかをチェックしようとしたのですが、まったく同じアプリケーションであった場合、値が同じになってしまうことがあり、厳密にチェックすることができませんでした。
どなたか厳密にアプリケーションの終了状況を把握できる方法をご教授お願いします。
>3.「1.」で起動させた他のアプリケーションを終了させる。
でアプリケーションが終了したにもかかわらずGetExitCodeProcessで
終了状況を判定していないのが問題なんじゃないでしょうか?
Shell関数でプロセスIDを取得しOpenProcessでプロセスハンドルを取得
する間に起動したアプリケーションを終了されると間違ったアプリケーション
のプロセスハンドルを取得することがありますが。
記述されている文を読む限り「3」の段階で何故終了判定が出来ないのかが
疑問です。
プロセスIDの他にウィンドウハンドルも保持し、そのウィンドウ
の存在もチェックするというのは如何ですか?
# 他アプリが終了するまで待機すれば、改めて終了状況を
チェックする必要もないと思うのですが、それではダメですか?
ご回答ありがとうございます。
>記述されている文を読む限り「3」の段階で何故終了判定が出来ないのかが
>疑問です。
ごもっともなことです。確かに、他のアプリケーションを起動させてから、起動した側で何秒毎かの間隔でずっと他のアプリケーションの終了を常に監視しておけば、終了を把握できて問題解決なのですが、当方では、かなり長い間、他のアプリケーションを起動させることになるので、ずっと終了状況を監視しないでもプロセスID一つだけで、確実に終了を把握出来はしないものかと思ったのです。他に方法がないということになれば、常に終了を監視することにしようと思っています。ずっと終了状況を監視しないでもプロセスID一つだけで、確実に終了を把握できるような方法がもしあれば、ぜひ、お教えしていただきたいと思います。
># 他アプリが終了するまで待機すれば、改めて終了状況を
>チェックする必要もないと思うのですが、それではダメですか?
他のアプリケーションが終了していたら、すぐにもう一度同じ「他のアプリケーション」を起動させたいという状況なので、「他のアプリケーション」が終了して、別のアプリケーションが同じプロセスIDを使用して起動されては、「他のアプリケーション」が終了しているのに再び「他のアプリケーション」が起動しないということになってしまいます。おかしな状況について質問して、恐縮ですが、よい案があれば、ぜひ、ご教授お願いします。
やりたい事がよく読みとれなかったので、外しているかもしれませんが、
以下の方法は利用できませんか?
案1) Shell関数の代わりに、WShShell.Runメソッドによる同期起動を使う。
案2) WaitForSingleObject APIによる待機監視を行う。
案3) WMIのWin32_Processクラスによる監視を行う。
>別のアプリケーションが同じプロセスIDを使用して起動されては
プロセスIDという意味からすると、これはごく普通に起こることであって
なにもおかしな状況でないということは理解されていますでしょうか?
>「他のアプリケーション」が終了しているのに再び「他のアプリケーション」が起動しないということになってしまいます。
これはプロセスIDの意味をよく理解せず作ったロジックがまずいからで
はないですか?
解決方法は既に他の人が書かれていらっしゃるので省略します。
ご回答ありがとうございます。
>やりたい事がよく読みとれなかったので、外しているかもしれませんが、
すみません。
>案1) Shell関数の代わりに、WShShell.Runメソッドによる同期起動を使う。
なるほど、他のアプリケーションを同期起動すれば、絶対に終了を把握することが出来ますね。この方法を単純に使うと、他のアプリケーションが終了するまで起動した側のアプリケーションが止まってしまうということになるんですよね。起動した側のアプリケーションが止まってしまう状態になってほしくないので、起動した側のアプリケーションが止まってしまう状態にならなくて、しかも、他のアプリケーションを同期起動できるなら、とても完璧な解決策になります。しかし、こんなことはできるのでしょうか?(この質問はプロセスIDと関係ないので新しいスレッドに書くべきですが、すみません。)
>案2) WaitForSingleObject APIによる待機監視を行う。
INFINITEを指定すると、監視すべき他のアプリケーションが終了したら、制御が返ってくるという動作をするAPIですよね。このご提案もとても十分な解決策と思います。しかし、制御が返ってこない間は、起動した側のアプリケーションが止まってしまう状態になってしまうので、どうにかして起動した側のアプリケーションが止まらずに、しかも、WaitForSingleObjectから制御が返ってくるのを待つことが出来れば、とてもありがたいです。(この問題は、ほぼ、上記のものと同じですよね。)
>案3) WMIのWin32_Processクラスによる監視を行う。
まったく未知のクラスで、ネットで検索して調べましたけど、まったく知らなかったのでどう使ってよいのかあまりよく分かりませんでした。私の無知で申し訳ありませんが、Win32_Processクラスによる監視というもので、問題が解決できるかどうかを判断できませんでした。すみません。よく勉強いたします。
>プロセスIDという意味からすると、これはごく普通に起こることであって
>なにもおかしな状況でないということは理解されていますでしょうか?
はい。最近プロセスIDを使ってみて理解いたしました。同じプロセスIDが再び使われるということが普通に起こりうることは、理解しています。
>これはプロセスIDの意味をよく理解せず作ったロジックがまずいからで
>はないですか?
そうなのかもしれません。もともと、プロセスIDで非定常的に他のアプリケーションの終了状況を把握しようとしていたのが間違いであったのかもしれません。既にご指摘のように、他のアプリケーションを起動してから常に終了を監視していれば、終了状況は把握できるわけで、私のように、他のアプリケーションを起動してからかなり時間が経った後に不意に終了状況をチェックするという行動自体が間違いなのかもしれません。しかし、もしも、プロセスIDだけで、いつ終了状況をチェックしても、確実に自分の起動した他のアプリケーションの終了状況を把握することが出来るのなら、これが一番近道な解決策だと思い、質問させていただきました。皆様のありがたいご回答を拝見していますと、もともと出来ないことを質問してしまっているのかもしれませんが、もし、プロセスIDだけで、いつ終了状況をチェックしても、確実に自分の起動した他のアプリケーションの終了状況を把握することが出来る解決策がありましたら、ご教授よろしくお願いします。
>…他のアプリケーションが終了するまで起動した側のアプリケーションが止まってしまうということになるんですよね。
微妙…スレッドで管理とかはできないのか?
起動するアプリケーションが分かっているという前提なら、監視側と相手側で交信をするとかはだめなのか?
割り込みスマソ(д
ご回答ありがとうございます。
>微妙…スレッドで管理とかはできないのか?
実装方法をまったく知らないので、教えていただければ幸いです。(もっとも、別スレッドを作って聞くべきですが。)
>起動するアプリケーションが分かっているという前提なら、監視側と相手側で交信をするとかはだめなのか?
最初の頃は、COM仕様でやっていました。しかし、理由はわかりませんが、COMにするとプログラムで使用している他のAPIの一部が機能しなくなってしまったので、COMはやめて、Shellで起動するという方法をとることにしました。起動するアプリケーションが分かっているので、COM以外のアプリケーション間の交信を用いれば、他のアプリケーションの終了は把握できそうです。原始的に、データファイルを作ってそのファイルを読み込み、書き込みすることでアプリケーション間の交信を実現してもよいのですが、もっとよい方法がないか模索中です。
>> 案1) Shell関数の代わりに、WShShell.Runメソッドによる同期起動を使う。
> この方法を単純に使うと、他のアプリケーションが終了するまで
> 起動した側のアプリケーションが止まってしまうということに
> なるんですよね。
そのプロシージャの続行は停止しますが、アプリケーション自体は
引き続き操作可能です。たとえば、
Private Sub Command1_Click()
Dim L As Long
Me.Command1.Enabled = False
With CreateObject("WScript.Shell")
List1.AddItem Format(Now(), "hh:nn:ss YYYY-MM-DD") _
& " : Start 'CALC.EXE'"
L = .Run("CALC.EXE", vbNormalFocus, True)
List1.AddItem Format(Now(), "hh:nn:ss YYYY-MM-DD") _
& " : End 'CALC.EXE' (0x" & Right("00000000" & Hex(L), 8) & ")"
End With
DoEvents
Me.Command1.Enabled = True
End Sub
のようなコードがあった場合、CALC.EXE の起動中も、フォーム等を
操作する事は可能ですし、Timerイベント等も発生しますよ。
> INFINITEを指定すると、監視すべき他のアプリケーションが終了したら、
> 制御が返ってくるという動作をするAPIですよね。
INFINITEだと、相手が終了するまで、こっちは何もできなくなってしまうので、
短めのタイムアウト時間を設定しておき、タイムアウト時にはDoEventsを
呼び出すようにしてみてください。
あるいは、MsgWaitForMultipleObjectsを使う方法もあります。
>> 案3) WMIのWin32_Processクラスによる監視を行う。
> まったく未知のクラスで、ネットで検索して調べましたけど、
とりあえず、Win32_Processクラスの前に、WMIについて調べて見てください。
WMIはもともと、VBScriptやVBからの操作も考慮した設計になっていますので、
VB向けのサンプルも多数見つかるかと思いますよ。
特に、Microsoft の TechNet スクリプト センターは、WMIのサンプルの宝庫ですよ。
http://www.showg.jp/tamper/wmi/index.htm
http://wmifun.atinfinity.net/
> COMにするとプログラムで使用している他のAPIの一部が機能しなくなってしまったので、
この部分の詳細がわからないので、代替案を出しにくいですが、とりあえず思いつきで。
> COM以外のアプリケーション間の交信を用いれば、他のアプリケーションの
> 終了は把握できそうです。
多少制限はありますが、VB同士なら、TextBoxのLink系プロパティ/メンバを
使って、「DDE」で通信するのが簡単かと思います。
あるいは、APIを使う事になりますが、共有メモリの利用とか。
http://techtips.belution.com/ja/vc/0001/
ご回答ありがとうございます。
>そのプロシージャの続行は停止しますが、アプリケーション自体は
>引き続き操作可能です。
本当ですね。何も調べずに書いてしまってすみません。アプリケーションは呼び出したプロシージャ以外、止まりませんでした。とても魅力的な解決策と思ったのですが、当方の勝手な事情により、これだけでは埒があきませんでした。このような当方の事情は初めに細かく言っておくべきだったと反省しております。実は、タイマーを用いてある一定間隔ごとにチェックするアプリケーションの一部だったのですが、一定時間が経った後にチェックして条件が合えば、他のアプリケーションを起動して、条件が合わなかったら再びタイマーで一定時間ごとのチェックを繰り返すという動作を実現したかったのです。そして、再びチェックして条件が合えば、再び他のアプリケーションを起動するのですが、このときに既に他のアプリケーションが起動されている場合は、起動の処理を行わないようにするために、既に起動した他のアプリケーションの終了状況をチェックしたかったのです。このため、ただ単純に「魔界の仮面弁士」さんから教えていただいたWScript.Shell.Runを用いた同期起動をタイマーの中に組み込むだけでは、呼び出したプロシージャが止まってしまうのでその後のタイマーによるチェックが出来ないという状態になってしまいました。当たり前といえば当たり前のことなのですが、WScript.Shell.Runを呼び出したプロシージャは止まっていて、なおかつ、そのプロシージャを呼び出しているタイマーは止まらないというような、このようなわがままなことは出来ますでしょうか。具体的なコードを示しておきました。
Private Sub Timer1_Timer()
Dim L As Long
With CreateObject("WScript.Shell")
List1.AddItem Format(Now(), "hh:nn:ss YYYY-MM-DD") _
& " : Start 'CALC.EXE'"
L = .Run("CALC.EXE", vbNormalFocus, True)
List1.AddItem Format(Now(), "hh:nn:ss YYYY-MM-DD") _
& " : End 'CALC.EXE' (0x" & Right("00000000" & Hex(L), 8) & ")"
End With
DoEvents
End Sub
この状況を打開すべく、イメージ的に考えてみると、タイマーから呼び出されたTimer1_Timer()プロシージャには、そのプロシージャ内のプログラムを実行する1つの制御子のようなものが渡されて(あまりうまく表現できないのですみませんが、「プログラムを実行できるもの」のようなものを制御子と考えて)、この制御子がプロシージャ内の上から下へプログラムを順番に消化していき、WScript.Shell.Runを呼び出す手前で、この制御子を2つに分けて、1つはWScript.Shell.Runの処理を飛ばしてその次の処理へ行きTimer1_Timer()プロシージャを終了する制御子と、もう1つはWScript.Shell.Runを実行して他のアプリケーションを起動し、アプリケーションの終了まで待機していて、他のアプリケーションが終了してWScript.Shell.Runメソッドの呼び出しが終わったらこの制御子は消滅するような、なんとも無理やりで分かりにくい表現で申し訳ありませんが、このようなことが出来ればタイマーも止まらずに、終了状況も確実にチェックできると思いました。しかし、実際に、制御子を2つに分けるということや、制御子が消滅するなどということが出来るのかどうか、私は実現するすべも思いつきません。あたたかくご回答していただいている皆様に、私が思いつきで名付けた「制御子」を分かっていただけていないのかもしれません。本当にアマチュアな意見で申し訳ありません。「も」さんから以前、「スレッドで管理」と言われましたけれど、制御子を2つに分けるということが「スレッドで管理」することなのだろうかとも考えました。
この解決策を用いるなら、WScript.Shell.Runメソッドを使うので、Windows Scripting Hostの機能を使うということですよね。WSHはWindows98から標準装備とあったのですが、WindowsNTは4.0から標準装備なのでしょうか。
「魔界の仮面弁士」さんから教えていただいたソースコードでWScript.Shell.Runメソッドを用いて他のアプリケーションを起動させてみました。「計算機」が起動されて、「計算機」を終了させるとリストに終了表示が出ました。
①そこで、ためしに、起動した「計算機」を、「×」をクリックして閉じるのではなくて、強制終了させてみると、リストに終了表示が出ませんでした。そして、起動する側のアプリケーションも終了しようとして「×」をクリックするとウィンドウは消えるのですが、まだアプリケーションは実行している状態で残ってしまいました。
②次に、WScript.Shell.Runメソッドを用いて他のアプリケーションを起動して、そのまま、他のアプリケーションの終了を待たずに、起動した側のアプリケーションを「×」をクリックして閉じようとすると、ウィンドウは消えるのですが、この場合も、まだアプリケーションは実行している状態で残ってしまいました。
私の実現しようとしているアプリケーションでは、上のどちらの場合もありうることなので、アプリケーションが終了出来ずに実行している状態のまま残ってしまうのはあまりうれしくありません。
Private Sub Form_Unload(Cancel As Integer)
End
End Sub
このようにしてEndをいれると、②の方は、他のアプリケーションの終了とともに終了しましたが、①の方は実行している状態のまま残ってしまいました。同期起動をさせようとすると、必ずこのような終了に関する問題が出てきてしまうなら、同期起動は止めようかとも考えているのですが、これを回避できるよい案がありましたら、ご教授お願いします。
>INFINITEだと、相手が終了するまで、こっちは何もできなくなってしまうので、
>短めのタイムアウト時間を設定しておき、タイムアウト時にはDoEventsを
>呼び出すようにしてみてください。
この方法を用いると、WaitForSingleObjectを短めのタイムアウト時間で呼び出して、タイムアウト時にDoEventを呼び出すという処理をDo...Loopで反復して、監視するということになりますよね。GetExitCodeProcessで終了チェックすることと動作的には同じことになるのかなと思います。
Do...Loopで反復して監視すると、当方の事情でタイマーから呼び出しをしているので、ここでも、Do...Loop反復の監視に入ってしまうと、Do...Loopに制御を奪われて、タイマーが一定時間ごとにチェックするということが出来なくなってしまうので、Do...Loop反復の監視と、タイマーの作動が同時に出来れば、WaitForSingleObjectやGetExitCodeProcessで監視する方でもよさそうです。
> WScript.Shell.Runを呼び出したプロシージャは止まっていて、なおかつ、
> そのプロシージャを呼び出しているタイマーは止まらないというような、
『一定間隔ごとに(何かを)チェックするタイマープロシージャ』と、
『WshShell.Run を呼び出すタイマープロシージャ』を分けるとか。。。
> WindowsNTは4.0から標準装備なのでしょうか。
NT4用のWSHは、Windows NT Option Packに含まれています。
> WScript.Shell.Runメソッドを用いて他のアプリケーションを起動させてみました。
> 「計算機」が起動されて、「計算機」を終了させるとリストに終了表示が出ました。
ここでいう『計算機』という言葉は、先のサンプルで使ったcalc.exe(電卓)、
すなわち、「Runメソッドにより実行された他のアプリケーション」の事を
指しているのでしょうか?
> 強制終了させてみると、リストに終了表示が出ませんでした。
「終了表示が出なかった」という事は、そのアプリが終了していない可能性も考えられます。
ちなみに当方では、Runメソッドで起動したアプリを[×]ボタンで閉じた場合も、
タスクマネージャから強制終了した場合も、呼び出し側の動作に変化はありませんでした。
> 起動する側のアプリケーションも終了しようとして「×」をクリックすると
> ウィンドウは消えるのですが、まだアプリケーションは実行している状態で残ってしまいました。
コーディングに問題が無いか、確認してみてください。
御存知とは思いますが、VB6では、フォームのプロパティやメソッド、
あるいはコントロール等にアクセスすると、そのフォームは自動的に
Loadされる事になります。
http://www.gj.il24.net/~nakasima/vb/tech/end/index.htm
たとえば、以下のようなコードを考えてみて下さい。
Option Explicit
Private Sub Command1_Click()
Dim S As String
S = CStr(CreateObject("WScript.Shell").Run("CALC.EXE", vbNormalFocus, True))
List1.AddItem S '……★
MsgBox "表示中のフォーム数=" & CStr(DoEvents()) & vbCrLf & _
"ロード中のフォーム数=" & CStr(Forms.Count)
End Sub
Private Sub Form_Load()
Debug.Print "Load"
End Sub
Private Sub Form_Unload(Cancel As Integer)
Debug.Print "Unload"
End Sub
このコードにて、
1. ボタンを押下して、電卓を起動させる。
2. 電卓を起動したまま、フォームを閉じる。
3. その後、電卓を閉じる。
という処理を行った場合について考えてみますと……
この場合、2の処理(フォームを閉じる)で Unload イベントが実行されますが、
続く3の処理(電卓終了)では、Runメソッドの続きが実行され、★の行にて、
「List1」という『フォームのコントロール』にアクセスする事になります。
この時点で、Form1は自動的に再ロードされるため、プログラムは終了しません。
この問題を解決するには、以下のようにします。
Option Explicit
Private mIsLoaded As Boolean
Private Sub Command1_Click()
Dim S As String
S = CStr(CreateObject("WScript.Shell").Run("CALC.EXE", vbNormalFocus, True))
If mIsLoaded Then
List1.AddItem S
End If
MsgBox "表示中のフォーム数=" & CStr(DoEvents()) & vbCrLf & _
"ロード中のフォーム数=" & CStr(Forms.Count)
End Sub
Private Sub Form_Load()
mIsLoaded = True
Debug.Print "Load"
End Sub
Private Sub Form_Unload(Cancel As Integer)
Debug.Print "Unload"
mIsLoaded = False
End Sub
> このようにしてEndをいれると
正常な終了処理の阻害となるので、可能な限り、Endは使わない事をおすすめします。
もし、上記コードで、Unload 時に End を使った場合、
S = CStr(CreateObject("WScript.Shell").Run("CALC.EXE", vbNormalFocus, True))
の部分は、Runメソッド完了後の処理が打ち消されてしまいます。
(「変数 S への代入」や「CStr関数の実行」さえ行われません)
> (1)の方は実行している状態のまま残ってしまいました。
こちらでは再現できないので、判断しかねますが、やはり、呼び出したプログラムの
「強制終了」に失敗しているような気がします。
呼び出したプログラムが生存中だとすれば、Runメソッドが終了せず、すなわち、
「リストに終了表示が出ない」結果となり、ひいては、呼び出し側プログラム自体が
実行中のままになってしまう事も説明が付きますので。
> GetExitCodeProcessで終了チェックすることと動作的には同じことになるのかなと思います。
GetExitCodeProcessには、以下のような問題があります。
http://nienie.com/~masapico/doc_AppExec.html
また、タイムアウト設定無しで、即座に結果を返してしまうため、ループの
実行回数が多くなり、CPU負荷も高くなります。
替わりに、先に回答した MsgWaitForMultipleObjects APIを試してみてください。
こちらであれば、INFINITEで待機しつつ、かつ、マウスやキーボード等の処理を
割り込ませるようなコーディングも可能かと思います。(試していませんけれども)
# 以下は、MsgWaitForMultipleObjects のサンプルです。
# VBのコードではありませんが、処理の概要はつかめるでしょう。
# http://homepage2.nifty.com/Mr_XRAY/Halbow/Notes/N001.html
ご回答ありがとうございます。
>『一定間隔ごとに(何かを)チェックするタイマープロシージャ』と、
>『WshShell.Run を呼び出すタイマープロシージャ』を分けるとか。。。
そうですよね。簡単に実装できますし、2つのタイマーで別々に動かせば、実現できることでしたよね。何か難しく、ことを考えていました。タイマーを2つ使って別々に監視しようと思います。こうすると、ご教授いただいたWshShell.Runを使用して終了を監視することも出来ますし、MsgWaitForMultipleObjectsを使用して終了を監視することも出来ますし、ことが簡単に進みました。
>ここでいう『計算機』という言葉は、先のサンプルで使ったcalc.exe(電卓)、
>すなわち、「Runメソッドにより実行された他のアプリケーション」の事を
>指しているのでしょうか?
その通りです。計算機でなく電卓の誤りでした。すみません。
>御存知とは思いますが、VB6では、フォームのプロパティやメソッド、
>あるいはコントロール等にアクセスすると、そのフォームは自動的に
>Loadされる事になります。
WshShell.Runで同期起動させてから、起動した側のアプリケーションの方が早く終了しても、その後、他のアプリケーションが終了してから、WshShell.Runから後のプロシージャ内のプログラムは実行されるのですね。なので、List1のメソッドが実行されて、フォームが非表示でロードされて終了しないということですね。納得しました。
>正常な終了処理の阻害となるので、可能な限り、Endは使わない事をおすすめします。
はい。なるべく使わないようにしています。
>GetExitCodeProcessには、以下のような問題があります。
>http://nienie.com/~masapico/doc_AppExec.html
知りませんでした。CreateProcessで起動させると同時にプロセスのオープンとプロセスハンドルの取得をしておいて、MsgWaitForMultipleObjectsで終了を監視しながら、メッセージを同時に処理して固まらないようにするという方法は、ずいぶん勉強になりました。ただ、コードを見ていて不思議に思うところが1つありました。コードがDelphiであまりなれていないので、重要な構文を見落としているのかもしれませんが、ご教授いただいたhttp://homepage2.nifty.com/Mr_XRAY/Halbow/Notes/N001.html
ページの下の辺りのサンプルコード内で、
repeat
ret := MsgWaitForMultipleObjects(1, { 1 handle to wait on }
processHandle, { the handle }
False, { wake on any event }
INFINITE, { wait without timeout }
QS_PAINT or { wake on paint messages }
QS_SENDMESSAGE { or messages from other threads }
);
if ret = WAIT_FAILED then Exit; { can do little here }
if ret = (WAIT_OBJECT_0 + 1) then
begin
{ Woke on a message, process paint messages only. Calling
PeekMessage gets messages send from other threads processed. }
while PeekMessage(Msg, 0, WM_PAINT, WM_PAINT, PM_REMOVE) do
DispatchMessage(Msg);
end;
until ret = WAIT_OBJECT_0;
とあるのですが、MsgWaitForMultipleObjectsはINFINITEを指定しているので、MsgWaitForMultipleObjectsから以降のプログラムは実行されずにMsgWaitForMultipleObjectsでプログラムが止まってしまって、ずっと待ちつづけてしまうのではと思いました。ここで止まると、その後のメッセージ処理PeekMessageとDispatchMessageは実行されずに固まってしまいますよね。しかし、repeat〜untilで処理が繰り返しになっていますし、MsgWaitForMultipleObjectsはINFINITE指定なのに、処理を次へ渡してしまうのでしょうか。少し疑問に思いました。
>替わりに、先に回答した MsgWaitForMultipleObjects APIを試してみてください。
教えていただいたCreateProcessとMsgWaitForMultipleObjectsで、タイマーを2つ用いて、厳密に終了を監視することにします。
前回の私の回答で、「制御子」などと訳の分からないことを言っておりましたが、スレッドのことでした。そして、スレッドについて調べていったところ、CreateThreadでスレッドが作成できるということが分かりました。サンプルも見つかって
http://www.geocities.co.jp/SiliconValley-Cupertino/5872/DownLoad/
http://www.geocities.co.jp/SiliconValley-Cupertino/5872/DownLoad/CreateThread.lzh
を参考にして同じようにプログラムして実行したのですが、CreateThreadは恐ろしい関数ですね、コンピュータ自体が復帰不能になってしまいました。何度も少しずつプログラムを書きかえていくと以下の2つについてうまくいきませんでした。スレッドを2つにすればタイマーを2つ用意することと同じことができそうなので、終了の監視に直接関係があるものではありませんが、どうかご教授お願いします。
1.サンプルのコード内のModule1中です。
Public Sub What_Time_is_it_now()
Do While bFlag = True
Form1.Label1.Caption = Now
Loop
End Sub
という部分で、Sleepをいれて
Public Sub What_Time_is_it_now()
Do While bFlag = True
Form1.Label1.Caption = Now
Sleep 200
Loop
End Sub
のように書きかえると、「×」をクリックしてプログラムを終了する際に強制終了になってしまいます。新しいスレッド内ではSleepは使えないのでしょうか。
2.CloseHandleを呼び出してスレッドを終了していますが、この呼び出しをCommand1_Click()内からではなくて、What_Time_is_it_now()内から呼び出そうとして
Public Sub What_Time_is_it_now()
Do While bFlag = True
Form1.Label1.Caption = Now
Loop
Call CloseHandle(Form1.hThread)
End Sub
のように書きかえました。hThread変数は、Form1フォーム内のDim宣言から同じForm1フォーム内のPublic宣言へ書きかえました。このようにすると正常に終了するのですが、
Public Sub What_Time_is_it_now()
Do While bFlag = True
Form1.Label1.Caption = Now
Loop
Call CloseHandle(hThread)
End Sub
のように書きかえて、hThread変数は、Form1フォーム内のDim宣言からModule1モジュール内のPublic宣言へ書きかえました。このようにするとスレッド終了時に強制終了になってしまいました。変数の宣言場所を変えただけなのですが、終了が正常に出来たり、強制終了になったりします。「ここがいけない」というポイントが見当たらないのですが、いけない点がありますでしょうか。
> コードがDelphiであまりなれていないので、
私も、Delphiはわからないです。(^^;
# Turbo Pascalなら読めるのですけれども。
> ずっと待ちつづけてしまうのではと思いました。
ここで使われているのは、最初に使っていた「WaitForSingleObject」ではなく、
http://www.microsoft.com/japan/msdn/library/ja/jpdllpro/html/_win32_waitforsingleobject.asp
http://yokohama.cool.ne.jp/chokuto/urawaza/api/WaitForSingleObject.html
「MsgWaitForMultipleObjects」という関数となります。
http://yokohama.cool.ne.jp/chokuto/urawaza/api/MsgWaitForMultipleObjects.html
http://www.microsoft.com/japan/developer/library/jpwinpf/_win32_msgwaitformultipleobjects.htm
MsgWaitForMultipleObjects では、最後の引数に、「待機する入力イベントの種類」を追加で指定することができるのです。
たとえば、QS_PAINTを指定すれば、指定したプロセスが終了した時だけでなく、「画面の再描画が必要な時」にも処理が戻されますので、適宜、DoEvents 等を挟む事で、画面が凍ってしまう事を防ぐ事ができます。
(プロセスが終了して復帰したのか、再描画要求等による割り込みなのかは、関数の戻り値にて判断できます)
………といいつつ、私自身はVB6から MsgWaitForMultipleObjects を呼び出した事が無いので、確信は無いのですけれども。(^_^;)
> CreateThreadでスレッドが作成できるということが分かりました。
VB.NETならばともかく、VB6でのマルチスレッドは、現実的には使えません。
以前、某所で K.J.K.さんが書かれていた言葉を引用させていただくと、
》 EXEをNative-Codeコンパイルをする場合:
》 1,シングルスレッド用のモジュールとしてコンパイル/リンクします。
》 よって、無理にマルチスレッド化すると、予期しない動作をする
》 可能性があります。
》
》 スレッド毎の変数の独立
》 VB6は、各スレッド毎に変数領域を作成/利用します。よって、
》 2,共有のグローバル変数は使えません。
》 3,他のスレッドに参照渡しができません。
》 => 参照渡しができない。文字列/配列/オブジェクトが使えない。
》 4,新スレッドからは、Globalなオブジェクトが使えない。
》 => つまり、標準的な関数/オブジェクト等が利用できない。
》
》 と、VBのメリットをほぼ破壊し尽くします。
なのだそうです。どうしてもやりたいなら止めませんが…茨の道ですよ。
ご回答ありがとうございます。
>MsgWaitForMultipleObjects では、最後の引数に、「待機する入力イベントの種類」を追加で指定することができるのです。
!。そうなんですか。そうすると、ご教授いただいたDelphiのコードが一応すべて理解できそうです。MsgWaitForMultipleObjectsの引数の意味もようやく全部分かったような気がします。とても強力で汎用性のあるコードを教えていただき、ありがとうございました。これと、タイマーを2つ用いる方法で、なんとか終了を厳密にチェックするプログラムを作ってみます。WshShell.Runを使用して終了を監視する方も参考に覚えておきます。私のこのスレッドでの問題はすべて解決しました。あたたかくご教授くださった皆様に感謝します。
>VB.NETならばともかく、VB6でのマルチスレッドは、現実的には使えません。
私の提示した書き換えプログラムがうまくいかなかったのもなんとなく分かりました。VB6にそのような性質があったとは知りませんでした。VB.NETならうまくマルチスレッドを実現できる方法でもあるんでしょうか。VB.NETと言われると、VB6とはかなり違ったイメージがあるので、VB6からVB.NETに移行しにくいように思っているのですが、しかし、私にはVB6で十分ですし、マルチスレッドが実現できなくても、「ActiveXで擬似的にやろうと思えば出来る」という文献も見つけたので、あきらめがつきました。