CommXでメモリエラーを解決するには?

解決


きょうこ  2013-04-05 01:44:01  No: 44200

Delphi6(professional)で、CommXコンポーネントを使用してシリアル通信をするプログラムを作成しています。

画面からボタンイベントでTransStringの問い合わせ文を送信し、ReceiveBlockで結果を受信しています。
受信は1byteずつで、結果の伝文の終了コードまでループして取得しています。
処理としては特に問題なくできるのですが、処理が終わりもう一度ボタンをクリックする流れを7,8回繰り返すと、メモリエラーを発生したり、「無効なポインタ操作です」が発生したりします。
このエラーが発生するのは、ボタン処理後になり途中で発生する事はありません。
メモリエラーはボタン処理後の別の処理のDBアクセス(Query操作)で起こります。
処理後はじめのDBアクセスの部分ではなく、2回目の場所だったりと不規則です。

何かアドバイスがあれば教えていただけますでしょうか?

流れとしては下記の通りになります。

procedure SendBtn4Click(Sender: TObject);
var
  Buf : PChar;
  receive : String;
  BLen : Integer;
begin

  CommX.PortOpen;
  
  伝文作成  略
  CommX.TransString(伝文);

  while XX do
  begin
    try
      Buf := AllocMem(1);
      BLen := CX.ReceiveBlock(Buf,1);
    except
      CommX.ClearReceiveBuf;
      CommX.ClearTransBuf;
      FreeMem(Buf);
      CommX.PortClose;
      Exit;
    end;
    if Buf[0] = 終了コード($04) then
    begin
      CommX.ClearReceiveBuf;
      CommX.ClearTransBuf;
      FreeMem(Buf);
      break;
    end
    else
    begin
      receive := receive + Buf[0];
      結果伝文表示処理  略
    end;
    FreeMem(Buf);
  end;
  CommX.PortClose;

end;


take  2013-04-05 02:13:54  No: 44201

CommXコンポーネントの使い方がわかっておられないような・・・
とりあえずDBは関係ないですね。

送信はボタンクリックイベントなりで実行して問題ないですが
受信はイベント内で行うべきです。

受信データが無い状態で受信処理したりするとエラーの元です。


きょうこ  2013-04-07 05:00:55  No: 44202

takeさん、返信ありがとうございます。

イベント内というのは、OnReceiveの事ですよね?
始めにやってみたのですが、まったく受信できずに現在にいたりました。
もう一度見直したところ、OnReceiveイベントで受信ができるようになりました。
しかし、相変わらず処理が終わり、画面を閉じた時に不規則に
「モジュール「XXX.exe」でアドレスXXXXでアドレスXXXXに対する読み込み違反が起きました。」
というエラーが出てしまいます。
絶対という規則性がなく、処理をし続けて4回目だったり、10回目だったり。。。
以前の「無効なポインタ操作です」の方は今のところ出ていません。

処理の流れは下記のとおりです。

処理メッセージ用の画面にCommXを置きました。
ポートのOpenをこの画面表示前に確認し、大丈夫な場合のみ
処理メッセージ用の画面を表示し、FormShowで送信伝文を作成しています。

procedure CommXReceive(Sender: TObject; ReceiveSize: Integer);
var
  Buf : PChar;
  receive : String;
  i : Integer;
begin

  try
    Buf := AllocMem(ReceiveSize);
    CommX.ReceiveBlock(Buf,ReceiveSize);
  except
    Close; 
    Exit;
  end;
  for i := 0 to ReceiveSize - 1 do
  begin
    if Buf[i] = 終了コード($04) then
    begin
      結果設定  略
      Close; 
      Exit;
    end
    else  receive := receive + Buf[i];
  end;
  FreeMem(Buf);
end;  

procedure FormClose(Sender: TObject; var Action: TCloseAction);
begin
  CommX.PortClose;
end;


Nov  2013-04-07 23:55:43  No: 44203

コードが省略されているので分かりませんが、exitで抜けるルート(2か所)でFreeMem忘れてるだけだったりして。
あと、forループが最後まで通ったとき、FormのCloseが無いのは仕様?


take  2013-04-08 17:45:14  No: 44204

書き込み違反ならともかく読み込み違反が起きそうな要因はないですね。

>処理をし続けて4回目だったり、10回目だったり。。。

メモリ確保したらfinallyなどで必ず解放したほうがよいですね。

var
  Buf : PChar;
  receive : String;
  i : Integer;
  f : Boolean; // <-追加
begin

  f := False;
  Buf := AllocMem(ReceiveSize);
  try
    CommX.ReceiveBlock(Buf,ReceiveSize);
    for i := 0 to ReceiveSize - 1 do
    begin
      if Buf[i] = 終了コード($04) then
      begin
        //結果設定  略
        f := True;
      end
      else begin
        receive := receive + Buf[i];
      end;  
    end;
  finally  
    FreeMem(Buf);
  end;
  
  if f then begin
    Close; 
    exit;
  end;


きょうこ  2013-04-08 21:33:42  No: 44205

Novさん、takeさん、回答ありがとうこざいます。

finally処理は記述し忘れで、FreeMemしています。
for文の終わりにCloseがないのは、終了コードを受信するまで待ちが発生するからです。
さらに後出し申し訳ありません、Receiveの中でもう一度TransStringが発生します。

この処理はリリースしているシステムに追加機能として作成したものなので、
一度、開発から切り離し、テスト用のフォーム画面を作成し、
処理画面だけ開発画面をコピーして、通信の連続処理テストをしてみました。
しかし、100回くらいやっても何の問題も出ませんでした。。。。
そうなると、この処理を呼ぶ側の既存システムに怪しい部分があるという事になるのでしょうか。。。。
正直なお話しをしますと、リリースしているのはPOSレジで、今回の追加機能はクレジット端末とのPOSレジの連動の開発です。

メモリエラーを追いかける様な機能などありますでしょうか?


take  2013-04-08 21:54:38  No: 44206

>さらに後出し申し訳ありません、Receiveの中でもう一度TransStringが発生します。
それが原因かな

OnReceiveの中でTransStringを行うと
1.受信イベント発生
2.イベント内で送信処理
3.イベントから抜ける前に次の受信イベントが発生
4.それをさらに処理してしまう

このクラス?を使っているメインが
受信処理に時間がかかると動作に違いが出るでしょう

簡単な回避方法としては
「送信処理をタイマーイベントで行う」
かな?

タイマーイベントに送信処理を書いておいて
送信する必要があるときにTimer.EnableをTrueにする。
タイマーイベント内では何回も送信しないようにFalse

で、どうでしょうか?

追いかける方法としては
処理毎にメッセージを出力する
ログファイルを用意すればおかしなところがわかるかも


Nov  2013-04-08 23:35:01  No: 44207

TransString(受信応答?)のトリガが、
(1) receive文字列の最初の受信
(2) 終了コードの受信
(3) 受信イベント都度発生
のいずれかで対策が変わる気がします。
普通に考えると(2)だと思いますが、もし(3)なら、本当に都度必要か検討すべきかも。
自分なら、メモリがらみのエラーを起こすイベントについては、まず、多重起動を防止(コードの戦闘で処理中exit)してから考えます。
その後、必要に応じて、データのとりこぼし対策とか。


Nov  2013-04-08 23:38:17  No: 44208

失礼しました。
>多重起動を防止(コードの戦闘で処理中exit)
戦闘は誤記、正しくは先頭。おかしいな、最近こんな変換したことないのに...


take  2013-04-09 17:52:18  No: 44209

Novさんのご指摘通り多重起動しないようにしておかないと
メモリエラーがでなくとも
そのうちOSのメモリを食いつぶす
なんてこともあります。

多重起動しているかどうかは
状況に応じてMemoなどに出力すると
ある程度の判断材料にはなります。

Buf := AllocMem(ReceiveSize);
Memo1.Lines.Add('メモリ確保');
//  省略
FreeMem(Buf);
Memo1.Lines.Add('メモリ解放');

と埋め込んでおいて実行したら
Memoが
メモリ確保
メモリ確保
メモリ解放
メモリ解放
とかになっていたら多重起動してます。

>最近こんな変換したことないのに...
うちのATOKもなぜか戦闘と銭湯が有力候補に


きょうこ  2013-04-09 20:26:36  No: 44210

Novさん、takeさん、いつもありがとうございます。

いろいろアドバイスありがとうございます。
根本的な解決にはなっていないかもしれませんが、一応解決しました。

端末とのやり取りですが、
1.POS「処理開始していいですか?」送信  (ボタンイベント)
2.端末「準備OK」受信(Receive内)
3.POS「この金額よろしく」送信(Receive内)
4.端末「処理結果はこうだよ」受信(Receive内)
がワンセットになっています。
これが終わらない限り、別の処理はしません。
ターンの符号の関係で、Recive内で4回発生します。
受信は基本1byteで、ログなどをとっても1byte以上の受信はありません。

takeさんのアドバイスのタイマーなども試してみたのですが、
うまくいきませんでした。
すべてのパターンで行き詰ってしまい、CommXの使用をやめて、
別のコンポーネントを使用してみるという事になりました。

今回使用したのはComPortというコンポーネントです。
処理の流れは同じだったので、CommXとComPortを置き換えて使用してみました。
結果、メモリエラー、「無効なポインタ操作」は一切、発生しなくなりました。
CommX時は6,7回に1回、メモリエラーか「無効なポインタ操作」が発生していたのですが、
ComPortに変更すると100回連続使用しても、エラー無で、
いろいろなテストパターンも試していますが、まだ、まったく問題なしです。

CommXで追及したいところですが、今回はComPort使用でいくことになりました。

本当にありがとうございました。


きょうこ  2013-04-09 20:28:55  No: 44211

締めた後で、すいません。

多重起動の件ですが、ComPortにした事により、
受信がStringになったのでAllocMemをする必要がなくなりました。


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

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






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