トップ回答者
MSComm通信エラー

質問
-
1.
COMポートを通じてVB6でActiveXアプリを作成しています。
アプリを起動して、ポートオープンしてシリアルデバイスとデータの送受信をして、
ポートクローズして終了させています。
アプリを起動中、別操作を行うと↓エラーがでます。"実行時エラー'8018':ポートが開かれている間にのみ有効な操作です"
現象はIEでアプリ起動中、戻るボタンなどで別操作をするときにおこります。
また、デバッカーでアプリ起動中にIEタスクを閉じるとときにおこります。
コード上、ポートオープン、ポートクローズは各一ヶ所です。考えられる対策を教えていただけませんか?
2.
MSCommオブジェクトのクローズは、"MSComm1.PortOpen = False"で良いのでしょうか。
関連はないと思いますが二重起動の問題がございまして調査しています。
ハンドシェイクは、MSComm1.Handshaking=3で設定しています。ご意見がございましたら宜しくお願いします。
回答
-
ごめんなさい(^_^;) ウソでした。
COMポート付きでVB6の入ったPCを持ち出してきて試してみたのですが、前回のようなコードだと、単に終了しないプログラムになってしまうようです。m(__)m
うろ覚えはいけませんね。\(__ ) ハンセィForm が Unload されても、Click イベントが生きている、というのは間違いなかったのですが、InBufferCount を実行すると MSComm が再ロードされ、それに引きずられて Form も再ロードされるようです。
この時、Form_Load でポートが再オープンされるので、問題のエラーは出ませんでした。
ただし、デバッガ上では実行中の表示になっていますが、Form は消えたままでした。
相手機器までは用意していないので、実際に受信データがあった場合の動きは確認していません。
(データは消えるだろうと思いますが)逆に、通信のたびにポートの Open/Close を行うプログラムで、何フレームかやりとりを繰り返す形にして通信中に Form を閉じると、Output/Input 実行時に問題のエラーが出ました。
こんな感じ↓
Private Sub Form_Load()
MSComm1.CommPort = 1
End SubPrivate Sub Commbutton_Click()
Dim i As Integer, cnt As LongMSComm1.PortOpen = True
For i = 0 to 10
MSComm1.Output = SndStr$ (=送信文字列)
Do
DoEvents
If MSComm1.InBufferCount > 0 Then
A$ = MSComm1.Input
RcvStr$ = RcvStr$ & A$
End if
cnt = cnt + 1
If cnt > 100000 then
Exit Do
End If
Loop While A$ <> vbCrLf
Next iMSComm1.PortOpen = False
End Sub
Do ループ中に Form と MSComm が Unload(ポートも自動的に Close)→ DoEvents から Click イベントに(Do ループ内に)戻って、InBufferCount で Form 再ロード(ポートは再オープンしない)→ その後 Cnt がオーバーカウントして Do を抜け、次の For ループの Output でエラー8018。
という流れでした。
こちらに類似した動きをしていないか、ご確認を。
このレベルであれば、QueryUnload イベントで終了待ちフラグを立てて終了をキャンセル → 通信ループ内でフラグをチェックして、立っていれば通信終了し、Unload ステートメント実行→ 再度 QueryUnload イベントが起きるが、終了待ちの場合は終了続行、という形で正常に終了できます。
マルチスレッドの場合は、全てのスレッドを強制終了してから Unload してください。
(そこまで作り込む根性はなかったので試してはいませんが、たぶんその方が間違いないでしょう。)- 回答としてマーク Umashika 2012年11月19日 12:57
すべての返信
-
状況から見ると、「二重起動」のスレッドと同じプロジェクトのお話でしょうか?
VB6でシリアル通信とファイル操作を行なうIEアドオンを作られている、ということですか?メモリ搭載型計測機器のログデータをシリアル通信で収集して、CSVかXLSに落とす、と言うような用途ですかね?
WEBページ上で行なう必然性のある処理ではなさそうですが...。最近のPCはそもそもシリアルポートを持っていないものが多いので、こういう用途に使うなら、専用のPCを用意して、通常のEXEとしてインストールするのが、一番安全な方法だと思います。
アドインとして作るなら、MSCommは使わず、CreateFileを用いた方が(できればC++で)、コンパクトで高速ですし、余分なランタイムをインストールしないので、後々問題を生じる可能性も少なくなります。
業務上の機密と言うこともあるでしょうから、仕様についてあまり詳しく伺おうとは思いませんが。
ご相談のエラーについてですが、二重起動ではないでしょう。それであれば「ポートはすでに開かれています」というエラーになる可能性が高いはずです。
順序が逆になりますが、2.の「PortOpen = False 」はポートのクローズは行ないますが、MSCommオブジェクト自体は開放しません。
Formに貼り付ける形で使われているなら、MSCommオブジェクトが開放されるのは、フォームのアンロード時(アドオンであればWEBページを閉じるとき、「戻る」ボタンでページを廃棄したときなど)です。アドオンとしての動作はよく分かりませんが、「ポートが開いているときのみに有効な操作です」というのは、EXEでも起こせたと思います。
一番単純なのは、「DoEventsを含むデータ受信ループ中にフォームが閉じられる」という操作でしょうか。
ボタンクリックで通信が行なわれる状況を想定すると、典型的なコードはPrivate Sub Form_Load()
MSComm1.CommPort = 1MSComm1.PortOpen = True
End SubPrivate Sub Commbutton_Click()
MSComm1.Output = SndStr$ (=送信文字列)
Do
DoEvents
If MSComm1.InBufferCount > 0 Then
A$ = MSComm1.Input
RcvStr$ = RcvStr$ & A$
End if
Loop While A$ <> vbCrLf
End SubPrivate Sub Form_QueryUnload(Cancel, UnloadMode)
MSComm1.PortOpen = False
End Subといったものが考えられますが、CommButton_Click イベント の Do ループ中にフォームが閉じられても、いったん始まったイベントプロシージャが途中で消滅するわけではありません。
DoEvents が実行されるごとに「Form_QueryUnload」イベント発生 → 画面上のコントロールを開放 → 「Form_Unload」イベント発生、という経過をたどって、自分自身を消去します。
(ただし、Clickイベントが終了するまで、画面は消えてもプロセスは残ります。)
Form_QueryUnload イベントでポートが閉じられた後に、CommButton_Clickイベント(DoEventsの次の行)へ戻り、MSComm1.InBufferCount か Input が実行されると、「ポートが開いているときのみに有効な操作です」というエラーが出たと思います。手元に試してみる適当な環境がないので、うろ覚えの話ですが...。
CommButton_Click イベント 内で Open/Close 処理まで行なうのは、他のアプリとの絡みでエラーしかねないので、あまりやらないと思いますが、その場合は MSComm1 が開放された後に MSComm1.InBufferCount が行なわれる形になるので、やはり何らかのエラーはでたはずです。
なお、Click でなく Timer イベントで通信を繰り返し行なう場合も多いでしょう。この場合は、イベントの順序が確定できないので再現性が悪く、分かりにくくなりますね。
複数の通信フレームをマルチスレッドで処理していたりすると、さらにイベント発生順序が錯綜するので、どんどん分かりにくくなります。実際のコードを見ていないので何とも言えませんが、Unload 時に起きているように見えますから、上に類することが起きているのではないでしょうか。
ポートクローズの行をブレークポイントにして、そこからステップ実行すると、何が起きているかはつかみ易いと思います。
あとは各種のイベントプロシージャを有効にしてブレークポイント化し、イベントの発生順を確認するのも良いかもしれません。
IDEの「表示」メニューの「呼び出し履歴」を参照するのもお忘れなく。ちなみに、上のような状況を回避するには、
「QueryUnloadイベントが発生したら、いったん終了をキャンセルして全てのイベントやスレッドの終了、作成した全てのオブジェクトの開放を確認してから、コード上で Unload する」
というのが良く使われる手です。あとは、他の部分でのオブジェクト開放漏れなどが悪影響を及ぼす場合もありますから、通信部分のみ抜き出して動作チェックすることも必要かもしれません。
- 編集済み Airihakoshi 2012年11月18日 12:09
-
ごめんなさい(^_^;) ウソでした。
COMポート付きでVB6の入ったPCを持ち出してきて試してみたのですが、前回のようなコードだと、単に終了しないプログラムになってしまうようです。m(__)m
うろ覚えはいけませんね。\(__ ) ハンセィForm が Unload されても、Click イベントが生きている、というのは間違いなかったのですが、InBufferCount を実行すると MSComm が再ロードされ、それに引きずられて Form も再ロードされるようです。
この時、Form_Load でポートが再オープンされるので、問題のエラーは出ませんでした。
ただし、デバッガ上では実行中の表示になっていますが、Form は消えたままでした。
相手機器までは用意していないので、実際に受信データがあった場合の動きは確認していません。
(データは消えるだろうと思いますが)逆に、通信のたびにポートの Open/Close を行うプログラムで、何フレームかやりとりを繰り返す形にして通信中に Form を閉じると、Output/Input 実行時に問題のエラーが出ました。
こんな感じ↓
Private Sub Form_Load()
MSComm1.CommPort = 1
End SubPrivate Sub Commbutton_Click()
Dim i As Integer, cnt As LongMSComm1.PortOpen = True
For i = 0 to 10
MSComm1.Output = SndStr$ (=送信文字列)
Do
DoEvents
If MSComm1.InBufferCount > 0 Then
A$ = MSComm1.Input
RcvStr$ = RcvStr$ & A$
End if
cnt = cnt + 1
If cnt > 100000 then
Exit Do
End If
Loop While A$ <> vbCrLf
Next iMSComm1.PortOpen = False
End Sub
Do ループ中に Form と MSComm が Unload(ポートも自動的に Close)→ DoEvents から Click イベントに(Do ループ内に)戻って、InBufferCount で Form 再ロード(ポートは再オープンしない)→ その後 Cnt がオーバーカウントして Do を抜け、次の For ループの Output でエラー8018。
という流れでした。
こちらに類似した動きをしていないか、ご確認を。
このレベルであれば、QueryUnload イベントで終了待ちフラグを立てて終了をキャンセル → 通信ループ内でフラグをチェックして、立っていれば通信終了し、Unload ステートメント実行→ 再度 QueryUnload イベントが起きるが、終了待ちの場合は終了続行、という形で正常に終了できます。
マルチスレッドの場合は、全てのスレッドを強制終了してから Unload してください。
(そこまで作り込む根性はなかったので試してはいませんが、たぶんその方が間違いないでしょう。)- 回答としてマーク Umashika 2012年11月19日 12:57