ReadFileで正しく受信データを取得するには?

解決


ぺのこ  2006-03-12 04:51:02  No: 60917

シリアル通信を行うアプリケーションを開発しているのですが、受信データの読み込みについて不具合が起こってしまい困っています。
お分かりになる方いらっしゃいましたら、どうかお力添えいただけると嬉しいです。

当方、開発そのものの経験もあまりなく、ほぼ初心者状態です・・・

開発環境はVisual C++ 6.0、Windows XP HOME Editionです。

不具合の現象といいますのが、一言で申し上げますと
『複数バイトのデータを受信しても、ReadFileを行うと一番最後のバイトにしかデータが入っていない』
という現象です。

受信側の作りは
・スレッドにて受信
・非同期でReadFile
・イベント駆動式(1バイト受信したらスレッド内で受信)
・↑WaitCommEventでEV_RXCHARが起きたらデータ受信
というものになっています。

送信側(受信動作のテスト用に作成した送信専用のものです)で1バイトずつデータを送信してみると、スレッド内でイベントが起こるのが1バイト受信した時や3バイト受信した時だったりして、それも「?」という感じなのですが、3バイト受信なら3バイト受信でCOMSTAT構造体でわかるのでいいのですが、その後が問題です。

ReadFileで受信データをこちらで用意したバッファに格納し、その中を見てみると、最後に受信したデータしか入っていないのです。例えば「0x01、0x01、0x01」という3バイトデータを受信してるはずなのに、バッファの中は「00、00、0x01」となっているのです。

最後のデータしか読み出せてないので、1バイト受信時に受信イベントが起こったときは大丈夫なのですが・・・今はテストとして1バイトずつ送信してますが、実際一度に何バイトも受信することになったら話になりませんよね。

うまく不具合のポイントだけ説明できればいいのですが、よく理解していないが為に長くなってしまいすみません。
長くなってしまったので、次のレスで実際のコードを載せますので、おかしな点などありましたらどうかご指摘くださると嬉しいです。


ぺのこ  2006-03-12 05:10:58  No: 60918

実際の受信部分(スレッド)です。
(ちなみにDLLです)

--------------------------------------------------
DWORD WINAPI COM_Thread(LPVOID pData)
{
  DWORD dEvent, dError, dByte;
  COMSTAT Comstat;
  BOOL b = FALSE;
  char cBuf[256] = "";
  while(bThread) {   //←スレッド稼動中はtrueであるbool型フラグ
    WaitCommEvent(h_Com, &dEvent, NULL);  //←h_Comはポートハンドル
    switch( dEvent ) {
     case EV_RXCHAR:
  ClearCommError(h_comHDL, &dError, &Comstat);
  if(Comstat.cbInQue) {
  // 受信データサイズ分バッファから読み出し
  b = ReadFile(h_comHDL, cRevBuf, Comstat.cbInQue, 
                             &dByte, &revop);
    if(GetLastError() == ERROR_IO_PENDING) {
      //★1
    }
    if(!b) {
      //★2
    }
    // ここで受信データを処理する予定
         }
         break;
     default:
  break;
    }

  }
  return 0;
}
--------------------------------------------------

非同期で受信しているせいで、ReadFile時にデータ全部がcBufバッファに入りきっていないのかな?(非同期通信だと受信データを読み込み終わらない内に関数から返ってきてしまうようなので)と思いまして調べてみましたら、
『ReadFileでまだデータを受信し切れてなければReadFileの返り値がFALSE、またはGetLastError()の返り値がERROR_IO_PENDINGになります』とヘルプにあったので、★1、★2部分で確認しましたがどちらのif文にも入ってこないのです。
それなのに、ReadFile後cBufの中身を見てみると最後のデータしか入っていなくて・・・

もうどうしたらいいのかわかりません。どうかご教授いただけるとありがたいです。
どうかよろしくお願いします。


ぺのこ  2006-03-12 05:14:05  No: 60919

連レス申し訳ないです;

上記コードの
ClearCommError()、ReadFile()の第一引数はh_Comの間違いです。
すみません。


ひでらん  2006-03-13 17:28:04  No: 60920

私も人に教えられるほど詳しくないですが、

> WaitCommEvent(h_Com, &dEvent, NULL);

これ、最後の引数NULLでいいんでしょうか?
OVERLAPPED 構造体へのポインタを渡せと
書いてあるけど・・・。


ぺのこ  2006-03-13 22:32:14  No: 60921

ひでらんさん、ご回答ありがとうございます。

・・・

おっしゃる通りです。
初歩以前のミスでした、お恥ずかしいです。すみません。

ということで、早速WaitCommEvent関数の第三引数にOVERLAPPED 構造体(com_ob)を指定し、再度1バイトずつデータを送信してみましたところ、1バイト送信するごとにイベントがちゃんと発生しデータも受け取れていました。

続いて複数バイトを一度に送信してみたところ、また受信データが不思議なことになってしまいました・・・

例えば16バイトのデータを一度に送信してみると、受信側で「16バイトデータがきた」ということは認識できているようなのですが、ReadFile時に受信データの中身を見てみると、今度は”最初の1〜2バイト”しかデータを正しく読込んでいないのです。以降のバイトの中身はどうなっているかというと、初期化状態のままではなく何かしらごちゃごちゃなデータが入ってきてしまっているようなのです。

ヘルプの方のWaitCommEvent関数の説明で
「hFile を開くときに FILE_FLAG_OVERLAPPED フラグをセットして、lpOverlapped に NULL 以外のポインタを渡すと、WaitCommEvent 関数は重複操作として実行されます。このとき、OVERLAPPED 構造体に手動リセットイベントオブジェクト(CreateEvent 関数を使って作成します)のハンドルを指定しておく必要があります。」
とありましたので(重複操作という意味もよくわからないのですが。。)、上記コードのwhileループに入る前に

com_ob.hEvent = CreateEvent(NULL,true, false, NULL);

という一文を追加してみましたが当然ながらこれだけでは動作は変わらず、更に調べたところGetOverlappedResult関数というのを使うといいのかな?とも思うのですが、そもそもヘルプの説明の意味もよく理解できず、再び途方にくれています。
それと、手動イベントオブジェクトに関してはResetEventも行わないといけないのかな?と思うのですが、リセットのタイミングもどこですればいいのかわからないのです・・・

ものわかりが悪くて大変申し訳ないのですが、この辺りおわかりになりましたらご助言・叱咤等どうかお願いします。


tetrapod  2006-03-13 23:18:59  No: 60922

ヘルプ見ても確かに判りにくい、というより、判らないんですよね。
まずはサンプルとか見てそれを研究・理解するほうがいいと思う。

最近あった類似スレ
http://forums.belution.com/ja/cbuilder/000/002/25s.shtml
の中で紹介した MSDN サンプル MTTTY
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnfiles/html/msdn_serial.asp

注意点いくつか
http://support.microsoft.com/default.aspx?scid=kb;en-us;110148
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/synchronization_and_overlapped_input_and_output.asp


ひでらん  2006-03-14 01:21:26  No: 60923

tetrapodさんの仰るとおり、サンプルみて研究したほうが
いいと思います。GetOverlappedResultは必要でしょう。

他人のホームページのリンクを勝手に張るの嫌いなので、
下記でググってください。
おそらく ぺのこさんがやろうとしているのと同じ様な
内容のリンクが出ると思います。
  ・シリアルポート 操作方法 非同期通信
ただ、自己責任で参考にしてください。

あんまり関係ないですが、下記では配列は初期化できて
ないと思います。別に初期化できてなくても困りはしな
いと思いますが、勘違いしてると何ですので・・・。
> char cBuf[256] = "";


オショウ  2006-03-14 04:28:53  No: 60924

1点だけ・・・

    switch( dEvent ) {
     case EV_RXCHAR:

この部分、dEvent には、複数のイベントビットがONして入って
くるので、これでは取りこぼします。
if文にして、
if((dEvent & EV_RXCHAR) != 0)
とでもしてみて下さい。

これで状況変化が無ければ、RS-232Cポートのオープン時の設定に
根本的に間違っているのでは?

以上。


ひでらん  2006-03-14 17:31:01  No: 60925

問題解決とは別なんですが、この作りで非同期にするメ
リットって何かあるのでしょうか。

既に受信済みのデータを数バイトReadFileで読むだけで
すよね・・・。普通に非同期使わないで組んでも同じ様
な気がするんですが。


ひでらん  2006-03-14 19:18:36  No: 60926

> WaitCommEvent(h_Com, &dEvent, NULL);

ここで使うんだから非同期必要ですよね。^^;
なに言ってるんだ、俺。


ぺのこ  2006-03-16 00:03:03  No: 60927

レスくださったみなさん、ご親切に本当にありがとうございます。
報告が遅れてしまって大変申し訳ございませんでした。

というのは、この受信DLLコードのテスト用に使っていたデータ送信用アプリ側にもかなりバグがあることが判明いたしまして・・・^^;
そちらの修正に追われておりました。

送信側を直したところ、だいぶ思うように受信ができるようになりました。
が、みなさんのお察しの通り、なぜできるようになったのかをイマイチ理解できてないのが苦しいところです・・・

>tetrapodさん
紹介していただいたページ拝見しまして、とりあえず注意点として上げられていた
【ReadFile・WriteFile時に起こり得るエラー】(OVERLAPPED構造体のoffset、offsethighの設定)については対策してみました。
類似スレの方のfAbortOnErrorなどについては
http://www.madlabo.com/mad/gid/research/rs232/win32api.htm
なども参考に修正中です。

>ひでらんさん
GetOverlappedResultはやはり必要ですか。
ヘルプを穴が開くほど眺めてみたのですが、私の認識では「非同期イベントの処理が終わっているかどうかが確認できる関数」という感じでしょうか・・・
使いどころがあまりよくわからなかったですが、実際のコードに追加してみたものを次のレスに書きますので、おかしなところがありましたらご指摘くださるとありがたいです。

教えていただいたキーワードでググってみましたら、自分では調べ尽くしたつもりでいましたが^^;非常に参考になりそうなページを何点か見つけられました。ありがとうございます。
それと、配列の初期化はきちんとmemsetを使うようにしました。以前の方法でも配列のメモリ内を見てみると0クリアされているようなので大丈夫かな、と思ってしまっていたのですがやはり確実に処理すべきでしたね。すみません。
ちなみに今回非同期でシリアル通信をする意義というのは、「ある程度の大きさのデータを連続して受信する時に、少なくとも受信命令の取りこぼしが無いように」ということが第一命題となっています。
受信命令はとりこぼさずとも受信データを取りこぼしていたら何の意味もないのですが・・・^^;

>オショウさん
ご指摘ありがとうございます。しかも受信イベントはEV_RXCHARだけでなく、もう何点か追加するつもりでしたので、こちらはきちんと対応いたしました。


ぺのこ  2006-03-16 00:03:52  No: 60928

修正をしたコードの一部です。
ポートオープン時の設定は【fAbortOnError】がtrue(初期状態)のまま、ということ以外は(falseの方がいい場合もありそうなので)大丈夫だと思われます。(パリティや速度etc)

<受信スレッド内>

DWORD WINAPI COM_Thread(LPVOID pData)
{
  DWORD dEvent = 0;
  DWORD dError = 0;
  DWORD dByte = 0;
  DWORD dtrByte = 0;
  BOOL b = FALSE;
  COMSTAT Comstat;
  char cBuf[256];
  memset(cBuf, 0, sizeof(cBuf));
  // ★OVERLAPPED 構造体comop、revopはグローバルで宣言

  comop.hEvent = CreateEvent(NULL,true, false, NULL);
  revop.Offset = 0;
  revop.OffsetHigh = 0;

  while(bThread) {  //←スレッド稼動中はtrueであるbool型フラグ
    WaitCommEvent(h_Com, &dEvent, &comop);
    if(GetLastError() == ERROR_IO_PENDING) {
      // ★WaitCommEvent関数後は常にERROR_IO_PENDINGなので、
      //    イベント受信処理が終わるまで待つ
      GetOverlappedResult(h_Com, &comop, &dtrByte, true);
    }

    // エラー発生
    if(dEvent & EV_ERR) {
      // エラー発生時、エラー通知処理を追加する予定
    }
    // 送信完了
    if(dEvent & EV_TXEMPTY) {
      // データ送信時、送信完了通知を受け取ったら処理をする予定
      // ★1
    }
    // 受信イベント発生
    if(dEvent & EV_RXCHAR) {
      ClearCommError(h_Com, &dError, &Comstat);
      if(Comstat.cbInQue) {
        b = ReadFile(h_Com, cBuf, Comstat.cbInQue, &dByte, &revop);
        // ここで受信データを処理する予定
      }
    }
    ResetEvent(comop.hEvent);
  }
  
  CloseHandle(comop.hEvent);

  return 0;
}

以上です。
★1の部分ですが、GetOverlappedResult関数を追加する前は、こちらから何かデータを送信するとそのデータが確実にテスト用アプリで受け取ってもらえなければ永久にEV_TXEMPTYイベントが起きてしまうという不具合が起きていました。(通信相手もいないのにデータを送信してしまうと・・・という意味です)
ですが、とりあえずGetOverlappedResultの第四引数をtrueとして追加することで、そこで待機してくれるようなので大丈夫かな?という感じです。
(動作としてもとりあえず自分の思うようには動いてくれています、表向きだけかもしれないですが・・・)

ここで、チンタラとなかなか作業が進まないせいか、通信DLLは後に回して違う作業をするようにという命令がきてしまいました・・・
まだ20バイト程度のデータのやり取りテストしかできていないので、この時点では動いてくれてるのですがこれからどうなるかわからないですし非常に心残りです。
ですが暫くはこちらの作業に熱意を傾けることができなさそうですので、暫定的に解決という形を取らせていただこうかと思います。

せっかくご助言してくださった方々、失望させてしまいましたら大変申し訳ないです。
再度こちらの作業に取り掛かれることになりましたら、きっとまた壁にぶちあたりそうですので、勝手ながらその時はどうかお力添えいただけると嬉しいです。
お時間割いてレスしてくださり、本当にありがとうございました。


ひでらん  2006-03-16 03:27:39  No: 60929

もう解決済みで、見てないかもしれませんが
私はWaitForSingleObjectを使って終了待ちして
GetOverlappedResultで終了確認してます。

WaitForSingleObjectを使ったのは、CPU負荷
軽減のためです。


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

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






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