Delphiアプリケーションにクレジット決済機能を付けるために、決済端末を借用し開発を行うところですが、BCC(Block Checking Code)を求めなければならず、コードを書いてみたのですが上手くいかず悩んでおります。
教えて頂けませんでしょうか。
<BCCの仕様>
<STX>より後ろから<ETX>または<ETB>までを1バイトごとにEX-OR(排他的論理和)をとった値
<求めるBCC>
コマンド電文例①<STX>10110110000001001<ETX> この場合は、2
コマンド電文例②<STX>2011010000200000<ETX> この場合は、<02H>
<書いてみたコード>
const
STX = #$02;
ETX = #$03;
EOT = #$04;
ENQ = #$05;
ACK = #$06;
NAK = #$07;
function BCC(str: String): String
var
i, x: Integer;
begin
x := 0;
for i := 1 to Length(str) do
x := x xor Ord(str[i]); // 最終的なxの値は51と出ました
Result := Char(x);
end;
function TForm1.Button1Click(Sender: Object);
var
bccTarget: String;
sendStr: Striing;
begin
bccTarget := Edit1.Text + ETX;
sendStr := STX + Edit1.Text + BCC(bccTarget);
ShowMessage(sendStr);
}
<教えて頂きたいこと>
ES232Cの電文を作成するのが初めてなので、何かコードが間違っていましたら是非教えて頂けませんでしょうか(m_m)
BCCを手動で計算しました。
BCCは'2'で合っているように思います。
まず数値の表記揺れを統一したほうが理解しやすいですね。
「2」と書かれても それが何を示す2なのかわかりません。
多分文字列の2なのでしょう
>コードが間違っていましたら
不安要素としては string型と char 型が使われていることかな
STXやETXのコードを拡張コードとして認識されてうまく処理が出来ない可能性もあるので
この場合は AnsiString型 AnsiChar型を使った方が良いですね。
1 :0011 0001
0 :0011 0000
1 :0011 0001
1 :0011 0001
0 :0011 0000
1 :0011 0001
1 :0011 0001
0 :0011 0000
0 :0011 0000
0 :0011 0000
0 :0011 0000
0 :0011 0000
0 :0011 0000
1 :0011 0001
0 :0011 0000
0 :0011 0000
1 :0011 0001
ETX :0000 0011
XOR 0011 0010
32H = 50 = '2'
takeさん、ありがとうございます。
大変お手数をお掛けしました。わたしの勘違いですね。結果は正しそうですね。
RS232C接続している端末が反応しなくてBCCの計算が間違っているものと思い込んでおりました。
うまく反応しなかったのは、最後に余計な<EOT>を入れてしまった結果でした。
本当に申し訳ございませんでした。
度々申し訳ございません。
ただ、上で書きましたのはBCCを算出するところまでで、RS232Cテストツールで成功は致しました。
これをDelphi2007で実際の通信を行わなければなりません。通信にはTCommというコンポーネントがあるので、それを使ってみて欲しいと言われてはいますが、使い方が今一つ分からず、次の様にコードを書いてみました。
unit Unit1;
interface
uses
::::::::::::::, Comm;
Type
TFrom1 = class(TForm)
::::::
CommTerm: TComm;
::::::
implementation
proecedure TForm1.SendButtonClick(Sender: TObject);
begin
try
// 接続
CommTerm.Open;
// 電文送信
CommTerm.SendString(SendStr); // SendStrは、<STX>+送るデータ+<ETX>+BCC
except
on Exception do
begin
ShowMessage('ERROR');
end;
end;
end;
という感じですが、SendStringメソッドで例外が発生してしまいます。
ECommReadWriteError(入出力エラーErrorCode=1997865560)が発生してしまいます。
<もしご存知でしたらご教授願いたいこと>
1)このTCommコンポーネントは有名なものでしょうか?
2)この例外はコードの記述の仕方が原因でしょうか?
DEKOさんの情報によると
https://ht-deko.com/ft1302.html
TComm は大野さんが作った16bit版を32bit版に改変したもの
ほかには エックスさんが作ったTCommXというのがあり
元々32bit版でスレッドによる送受信が出来て最終版はWindowsXPでも安定した動作が可能、Windows10でも問題なし
コンポーネントを使ってみたということですが、
1.コンポーネントをDelphiにインストールして、Edit1など他のコンポーネントのようにフォームに配置した
2.プロジェクトファイルのフォルダにComm.pasをコピーして uses に Commを追加
フォームのprivate宣言にCommTerm: TComm;として追加した
(1)の方法だとコンポーネントが正しく設定されていないと思われます。
Commのポート番号は何番か?存在しているポート番号か?
今のPCってシリアルポートがないので
USB変換だと思いますが
設定からデバイスマネージャーを開いて該当する変換器やポートから
シリアルポートが何番なのかを調べて下さい。
(2)の方法だとクラスが生成されていないのでエラーが出たと思われます。
フォームのOnCreateイベントで CommTerm := TComm.Create;として生成
OnDestroyイベントで CommTerm.Free;として破棄
TCommXを紹介しましたが、今現在は配布サイトがないかもしれませんね。
最終バージョンは CommX121.lzhです。
開発関係者の1人として再配布してあげたいとろころですが・・・
内容が異なる質問は別に質問を作成したほうがいいと思います(ここは解決済になってますし)。
それはともかく、Comm.Openをエラーなく通過しているということはコンポーネントのインスタンスは正しく生成されていて、ポート番号などの設定もあっているような気がします。
Comm.SendStringがエラーになるのはWriteFileが失敗してるからですが、試しに
CommTerm.SendString('A');
とか他のデータを送信しても同じエラーになるでしょうか?
takeさん
何度も回答頂きまして本当にありがとうございます。
今回はパッケージ開発環境(Delphi2007)を利用しておりまして、デスクトップPCになります。
PCとクレカ決済端末とはRS232Cのストレート?と思われるケーブルで接続されています。
既に、TCommコンポーネントが開発環境にインストールされていまして、それをフォームに貼り付けてプロパティは次の通り設定しています。
<プロパティ>
BaudRate:4800 // クレカ決済端末と合わせています、デバイスマネージャーの通信ポート設定も同じ値です
BaudRateUserDefined:4800
ByteSize:cbs8 // デバイスマネージャーの通信ポート設定でデータビットに8を選択しています
DsrState:True // すみません用途を理解していませんが、デフォルト値のままです
EventMask:[] // 全てFalseです(デフォルト値です)
FlowControls:[cfcRtsCts] // こちらも用途を理解していないのですがデフォルトでcfcRtsCtsのみがTrueだったかと
InQueueSize:4096 // こちらも用途が不明でしたのでデフォルト値です
Name:CommTerm
OutQueueSize:4096 // こちらもデフォルト値です
ParityBits:cpbNone // デバイスマネージャーの通信ポート設定でパリティ:なしを選択していますので、こちらにしました
Port:1 // COMポートは1つしか存在しませんのと、デバイスマネージャーの通信ポート設定でもCOM1となっております
ReceiveNotifySize:3072 // こちらもデフォルト値です
SendNotifySize:1024 // こちらもデフォルト値です
StopBits:1 // デバイスマネージャーの通信ポート設定でストップビットを1を選択しています
Tag:0 // ここでは関係しないと思いますが
尚、RS232C接続ツールでは、同じ電文を送信して<ACK>(※肯定応答)が受信され、その後に<EOT>を送信して電文が受信できております。
まだまだ勉強不足で大変申し訳ございませんが、何かヒントがございましたら何卒ご教授のほどよろしくお願い致します。
HFUKUSHIさん
すみません、解決済にチェックを入れておきながら、追記してしまいました。
それでも見て頂きまして本当にありがとうございます。
試しに、CommTerm.SendString('A'); を実行してみました。
ErrorCode=1242504というエラーコードで、エラーになりました。
RS-232C接続ツールでポートを開いているときに実行したということはないですか?
シリアルポートはポートをプログラムが占有しますのでアプリは1つしか通信出来ません。
takeさん、返信ありがとうございます。
RS-232C接続ツールは接続を切断またはアプリを落とした上でDelphiアプリケーションを実行しております。
違いましたか
TCommがおかれているサイトがあったので抜粋してみたのですが特におかしいところがありません
https://basicc.exblog.jp/i11/
procedure TComm.SendString(S: string);
begin
Write(PChar(S), Length(S));
end;
procedure TComm.Write(Buffer: PChar; Size: Integer);
var
dwError: DWord;
Stat: TComStat;
dwBytesWritten: DWord;
begin
if FHandle = INVALID_HANDLE_VALUE then
raise ECommError.Create('通信が開始されていません。');
if FOutQueueSize < Size then
raise ECommError.Create('送信データ長が長すぎます。');
repeat
ClearCommError(FHandle, dwError, @Stat);
until (FOutQueueSize - Stat.cbOutQue) >= Word(Size);
if FWinVersion <> VER_PLATFORM_WIN32_NT then begin
if (cfcRtsCts in FFlowControls) and (FRtsControls = crcToggle) then begin
while not IOCheck do ;
if RtsSendHi then begin
EscapeCommFunction(FHandle, SETRTS);
end else begin
EscapeCommFunction(FHandle, CLRRTS);
end;
end;
end;
if not WriteFile(FHandle, Buffer^, Size, dwBytesWritten, @FWriteOs) then begin
if FWinVersion <> VER_PLATFORM_WIN32_NT then begin
if (cfcRtsCts in FFlowControls) and (FRtsControls = crcToggle) then begin
while not IOCheck do ;
if RtsSendHi then begin
EscapeCommFunction(FHandle, CLRRTS);
end else begin
EscapeCommFunction(FHandle, SETRTS);
end;
end else begin
EscapeCommFunction(FHandle, SETRTS);
end;
end;
if GetLastError = ERROR_IO_PENDING then begin
while not GetOverlappedResult(FHandle, FWriteOs, dwBytesWritten, True) do begin
if GetLastError = ERROR_IO_INCOMPLETE then Continue
else
begin
ClearCommError(FHandle, dwError, @Stat);
Break;
end;
end;
end
else begin
ClearCommError(FHandle, dwError, @Stat);
raise ECommReadWriteError.Create(dwError);
end;
end;
end;
takeさん
本当にありがとうございます。
わたしもTCommコンポーネントの挙動を確認していました。
Openすると、直ぐに抜けてしまうので(最初のif文:if FHandle < 0 then)、CreateFileが実行されないのが原因なのでしょうかね。
※FHandleの値の初期値は0です
尚、Writeメソッドの内容もこちらで改ざんされてしまっている模様です。すみません。
<Comm.pas抜粋>
{ 通信ポートのオープン }
procedure TComm.Open;
var
szPort: array [0..9] of Char; // ポート名
CommTimeouts: TCommTimeouts; // タイムアウト構造体
begin
if FHandle < 0 then // 通信中でない時
begin
FReadOs.Offset := 0; // 受信オーバーラップ処理用
FReadOs.OffsetHigh := 0;
// イベントオブジェクトの作成
FReadOs.hEvent := CreateEvent(nil, // ハンドルを継承しない
True, // 手動リセットイベント
False, // 非シグナル状態で初期化
nil); // イベントオブジェクトの名前
if FReadOs.hEvent = 0 then
raise ECommOpenError(-2); // イベントが作成できない
FWriteOs.Offset := 0; // 送信オーバーラップ処理用
FWriteOs.OffsetHigh := 0;
// イベントオブジェクトの作成
FWriteOs.hEvent := CreateEvent(nil, // ハンドルを継承しない
True, // 手動リセットイベント
False, // 非シグナル状態で初期化
nil); // イベントオブジェクトの名前
if FWriteOs.hEvent = 0 then
begin
CloseHandle(FReadOs.hEvent); // 受信用イベントのクローズ
raise ECommOpenError(-3); // イベントが作成できない
end;
StrPCopy(szPort, 'COM'+ IntToStr(FPort)); // ポート名の作成
FHandle := CreateFile(szPort, // ポート名
GENERIC_READ or GENERIC_WRITE,
0, // 排他的使用(*)
nil, // セキュリティー属性なし
OPEN_EXISTING, // 既存(*)
FILE_ATTRIBUTE_NORMAL or // 通常
FILE_FLAG_OVERLAPPED, // オーバーラップ入出力
0); // テンプレートなし(*)
if FHandle < 0 then // エラー発生時
raise ECommOpenError.Create(FHandle); // 例外の生成
// 送受信バッファーサイズの設定
SetupComm(FHandle, FInQueueSize, FOutQueueSize);
// すべてのバッファー情報を破棄する
PurgeComm(FHandle, PURGE_TXABORT or
PURGE_RXABORT or
PURGE_TXCLEAR or
PURGE_RXCLEAR);
// タイムアウトのセットアップ
CommTimeouts.ReadIntervalTimeout := MAXDWORD;
CommTimeouts.ReadTotalTimeoutMultiplier := 0;
CommTimeouts.ReadTotalTimeoutConstant := 1000;
CommTimeouts.WriteTotalTimeoutMultiplier := 0;
CommTimeouts.WriteTotalTimeoutConstant := 1000;
SetCommTimeouts(FHandle, CommTimeouts);
// 通信環境の設定
SetBaudRate(FBaudRate); // 通信速度
SetByteSize(FByteSize); // データビット数
SetParityBits(FParityBits); // パリティービット数
SetStopBits(FStopBits); // ストップビット数
SetFlowControls(FFlowControls); // フロー制御
SetReceiveNotifySize(FReceiveNotifySize);
SetSendNotifySize(FSendNotifySize);
SetEventMask(FEventMask); // イベントマスク
FThread := TCommWatch.Create(Self, FHandle);
// 受信監視スレッドの起動
EscapeCommFunction(FHandle, SETDTR); // DTRをオンにする
end;
end;
{ データの送信 }
procedure TComm.Write(const Buffer; Size: Integer);
var
dwError: DWord;
Stat: TComStat;
dwBytesWritten: DWord;
begin
if FHandle < 0 then
raise ECommError.Create('通信が開始されていない。');
if FOutQueueSize < Size then
raise ECommError.Create('送信データ長が長すぎる。');
repeat // 送信キューが空くのを待つ
// Application.ProcessMessages; // これがあると送信が逆転する
ClearCommError(FHandle, dwError, @Stat);
until (FOutQueueSize - Stat.cbOutQue) >= Size;
if not WriteFile(FHandle, Buffer, Size, dwBytesWritten, @FWriteOs) then
begin
if GetLastError = ERROR_IO_PENDING then // オーバーラップ処理時
begin
while not GetOverlappedResult(FHandle, FWriteOs,
dwBytesWritten, True) do
begin
if GetLastError = ERROR_IO_INCOMPLETE then // まだ完了しない
Continue
else
begin
ClearCommError(FHandle, dwError, @Stat);
Break;
end;
end;
end
else
begin // その他のエラー発生
ClearCommError(FHandle, dwError, @Stat);
raise ECommReadWriteError.Create(dwError);
end;
end;
end;
> Openすると、直ぐに抜けてしまうので(最初のif文:if FHandle < 0 then)、
あーということは ここで引っかかってるのかもしれません。
掲示板で円マークが使えないみたいなのでバックスラッシュになりますが
コード上ではキーボード右上の円マークです
【元ソース】
StrPCopy(szPort, 'COM'+ IntToStr(FPort)); // ポート名の作成
【修正ソース】
StrPCopy(szPort, '\\.\COM'+ IntToStr(FPort)); // ポート名の作成
takeさん
色々と有難うございました。お陰様で問題が解決しそうです。
やはり、自社にありましたソースは何らかの理由で改ざんされている様に思います。
Openメソッドの最初の判定文が悪いため、CreateFileまで辿り着かなくなっております。
takaさんからご提示頂きました情報を元に解決することが出来ました。
本当にありがとうございました。
掲載頂きましたサイトのソースを利用させて頂こうと思います。
<Comm.pas抜粋>
{ 通信ポートのオープン }
procedure TComm.Open;
var
szPort: array [0..9] of Char; // ポート名
CommTimeouts: TCommTimeouts; // タイムアウト構造体
begin
if FHandle < 0 then // ★⇐初期値が0なので、CreateFileに辿りつかない
begin
:::割愛させて頂きます
StrPCopy(szPort, 'COM'+ IntToStr(FPort)); // ポート名の作成
FHandle := CreateFile(szPort, // ポート名
:::割愛させて頂きます
FHandleはファイルハンドルなので、初期値を(0ではなく)INVALID_HANDLE_VALUEに、比較も(0との比較ではなく)if FHandle <> INVALID_HANDLE_VALUE thenまたはif FHandle = INVALID_HANDLE_VALUE thenとするべきかと思います。
HFUKUSHIさん
お忙しいところアドバイス頂きまして有難うございます。
ご指導頂きました点をソースに反映したいと思います。
ツイート | ![]() |