WindowsでRS232Cを使う


パソコンに標準装備されているI/Oデバイスのうち、シリアルポートの役目は、古くは外部モデムと接続し
電話回線による通信に使われていました。今やモデムはパソコンに内蔵され、その存在は生涯気付かれないで
寿命を終える装置かも知れません。モデムで無くともシリアルポートを装備した外部I/O装置は今でも存在しますし
RS232CというCCITT勧告の規格に守られた、素性正しい通信手段の中心的存在でもあります。
FAの分野では、パソコンを拡張するコストが要らないこともあり、未だ多くの活躍の場を与えられています。

しかしハードウエアの進歩により、新たなUSBという規格に、その座を譲る時期に来ているとも言えます。
実績から言ったら、大人と子供ほどの違いが有るのですが、接続の容易さ、通信速度、利便性から言ってもUSB
が優れているのは明らかです。

更に、RS232Cの本来目的であった外部との通信においても、ケーブルテレビの普及で、一般の家庭にも
ランボードとケーブルモデムを使った512Kbps程度のネット性能を安価で維持出来るようになり、使用目的は
はっきり二分されるようになりました。

RS232C
手がかり
シリアルポートのオープン
シリアルポートのクローズ
シリアルポートの状態を得る
シリアルポートの設定
タイムアウト
データの受信
データの送信




手がかり
マイクロソフトが提供している資料では、MSDNにある、Platform SDK: Files and I/O Communicationsがシリアル
ポートに関するドキュメントです。残念ながら全て英文です。邦訳で参考になりそうな本に、参考文献11があります。
街の書店で一般に出回っているVC関係の書籍には、まずシリアルポートの話は出てきません。
Windowsでシリアルポートを使ったアプリケーションを書くには、いくつかのアプローチがあります。

  1. MicrosoftCommunicatioControllというActiveXをインポートする。
  2. Win32通信APIを使う。
  3. シリアルポートを直接アクセスする。

現実問題として、シリアルポートを直接アクセスする方法は止めた方が無難です。Windowsは、I/Oアクセスを
仮想デバイスドライバが管理していてるため、ルールを無視した直接アクセスは何が起こるか予想が出来ません。

DOS全盛の良き時代では、当たり前のように直接アクセスが出来ましたが、パソコンが単なるTK-80のような
ワンボードのマイコンに毛が生えた程度の代物から、Windows95以降のようなれっきとしたコンピュータシステム
へと変身を遂げた頃から、OSは、I/Oアクセスをユーザーから取り上げ、代わりに仮想デバイスドライバが一手に
管理するようになりました。その弊害で、Windowsに新たなデバイスを追加しようとすると、そのデバイスを制御する
仮想デバイスドライバを作らねばならなくなりました。勿論DOSの時代にもデバイスドライバはありましたが、
Windows用のドライバは、開発TOOLの少なさや、ドキュメントの難解さによりかなり敷居の高いものになってしま
いました。

ちょっと横道にそれましたが、やはり完成されたソフト部品のActiveXをインポートする方法が一番ベストなアプローチ
と思います。しかしブラックボックス化されたコントロールは一旦問題が起こった場合、なかなかその原因がつかみ
にくいという性格があり、保険としての代替手段としてここでは少々古臭いWin32通信APIを使う方法を採りました。


シリアルポートのオープン
95以前のWindowsでは、シリアルポート専用の関数がありましたが95からはファイルと同じ扱いになりました。
シリアルポートをオープンするには、CreatFile()を呼出ます。

HANDLE CreateFile(
LPCTSTR
lpFileName,         // ポート名を指すバッファへのポインタ
DWORD dwDesiredAccess,       // アクセスモード(READ、WRITEなど)
DWORD dwShareMode,          // ポートの共有方法を指定(共有不可:0に設定)
LPSECURITY_ATTRIBUTES lpSecurityAttributes,// セキュリティ属性
DWORD dwCreationDisposition// ポートを開き方を指定(OPEN_EXISTINGで既存を指定)
DWORD dwFlagsAndAttributes // ポートの属性を指定
HANDLE hTemplateFile          // テンプレートファイルへのハンドル(常にNULLを指定)
);

引数:

lpFileName:COM1やCOM2などのシリアルポートの論理名を指定します。この章では詳述しませんがLPT1などの
プリンタポートも扱えます。

dwDesiredAccess:以下の読み書きモードを単独又はORで指定します。

意味
0 デバイスの属性を問い合わせます。
GENERIC_READ 読み取りアクセスです。読み書きアクセスをするには、GENERIC_WRITE と組み合わせて指定します。
GENERIC_WRITE 書き込みアクセスです。読み書きアクセスをするには、GENERIC_READ と組み合わせて指定します。

通常シリアルポートは入出力が出来るので。GENERIC_READ | GENERIC_WRITEと設定します。


dwShareMode
:ポートの共有モードで以下の値を単独又はORで指定します。0を指定すると共有しません。

意味
FILE_SHARE_DELETE Windows NT のみ : 後続のオープン操作で削除アクセスが要求された場合、そのオープンを許可します。
FILE_SHARE_READ 後続のオープン操作で読み取りアクセスが要求された場合、そのオープンを許可します。
FILE_SHARE_WRITE 後続のオープン操作で書き込みアクセスが要求された場合、そのオープンを許可します。

ポートはファイルと違って共有出来ないので、0に指定します。この場合既にオープン済みのポートを、他のプロセス
がオープンしようとすると、CreatFile()はエラーを返します。しかし、同一プロセスの複数スレッドはCreatFile()
の返したハンドルを共有出来ます。


lpSecurityAttributes:セキュリティ属性でSECURITY_ATTRIBUTES構造体へのポインタです。NULLを設定する
とハンドルは子プロセスへ継承されません。

dwCreationDisposition
:ファイルが存在するとき、または存在しないときのそれぞれの動作を指定します。
シリアルポートは既存ファイル以外ありえないので。OPEN_EXISTINGを指定します。

dwFlagsAndAttributes:ファイルの属性およびフラグを指定します。シリアルポートで意味のあるのは、以下の
FILE_FLAG_OVERLAPPEDだけです。

フラグ 意味
FILE_FLAG_OVERLAPPED
時間のかかる処理に対して ERROR_IO_PENDING を返すようにします。処理が終了すると、イベントはシグナル状態に設定されます。
このフラグを指定したときは、ReadFile 関数や WriteFile 関数で OVERLAPPED 構造体を指定しなければなりません。

FILE_FLAG_OVERLAPPEDを指定すると、シリアルポートの読み書きをCPUの空き時間で実行できるようになります。

hTemplateFile:はシリアルポートには関係ないのでNULLを指定します。

シリアルポートのクローズ
クローズは、CreatFile()がの戻すハンドラを引数とて、CloseHandle(hComm)を呼び出します。

BOOL CloseHandle( HANDLE hObject );

戻り値は成功すると0以外、失敗すると0を返します。

シリアルポートの状態を得る
自分のパソコンのシリアルポートはどの程度の性能か、など普通は気にもしませんが、デバイスドライバを含めた
シリアルポートの性能を知ることによって、サポートされていないボーレートを誤って設定してしまうなどの、不正な
設定を予防できます。

シリアルポートに関する情報は、GetCommProperties()の呼出でCOMMPROC構造体変数に読み出します。

   BOOL GetCommProperties(HANDLE hFile 、 LPCOMMPROP lpCommProp );

引数:
hFile:通信デバイスへのハンドル。CreateFile関数はこのハンドルを戻します。
lpCommProp:通信特性情報が戻されるCOMMPROP構造体へのポインタ。

戻り値:関数が成功した場合、は0以外の値。失敗した場合は0です。

LPCOMMPROP
構造体は以下構成のシリアルポート情報です。

typedef struct _COMMPROP{
  WORD wPacketLength;     // 構造体サイズ
  WORD wPacketVersion;    // 構造体バージョン
  DWORD dwServiceMask;    // 実行されたサービス
  DWORD dwReserved1;     // 予約
  DWORD dwMaxTxQueue;   // バイト単位の最大送信バッファサイズ
  DWORD dwMaxRxQueue;   // バイト単位の最大受信バッファサイズ
  DWORD dwMaxBaud;      // ボーレート最大値
  DWORD dwProvSubType;   // 特定プロバイダータイプ
  DWORD dwProvCapabilities; // サポートされた関数
  DWORD dwSettableParams; // 変更可能パラメーター
  DWORD dwSettableBaud;   // 許可されたボーレート
  WORD wSettableData;     // 許可されたバイトサイズ
  WORD wSettableStopParity; // ストップビット/パリティ許可
  DWORD dwCurrentTxQueue; // Txバッファサイズ
  DWORD dwCurrentRxQueue; // Rxバッファサイズ
  DWORD dwProvSpec1;     // プロバイダー特定データ
  DWORD dwProvSpec2;     // プロバイダー特定データ
  WCHAR wcProvChar [1];   // プロバイダー特定データ
}COMMPROP;

dwServiceMask:シリアルポートの場合は常にSP_SERIALCOMMです。
dwReserved1:未使用
dwMaxTxQueue:出力バッファの最大値〈バイトの〉を示します。0は制限されていないことを示します。
dwMaxRxQueue:入力バッファの最大値〈バイトの〉を示します。0は制限されていないことを示します。
dwMaxBaud:以下の常数で設定可能なボーレートの最大値を示します。

意味
BAUD_075 75 bps
BAUD_110 110 bps
BAUD_134_5 134.5 bps
BAUD_150 150 bps
BAUD_300 300 bps
BAUD_600 600 bps
BAUD_1200 1200 bps
BAUD_1800 1800 bps
BAUD_2400 2400 bps
BAUD_4800 4800 bps
BAUD_7200 7200 bps
BAUD_9600 9600 bps
BAUD_14400 14400 bps
BAUD_19200 19200 bps
BAUD_38400 38400 bps
BAUD_56K 56K bps
BAUD_57600 57600 bps
BAUD_115200 115200 bps
BAUD_128K 128K bps
BAUD_USER ユーザー設定可変値

dwProvSubType:以下の常数でCreatFile()によって作成された通信デバイスが何をサポートしているかを示します。シリアルポートの場合はPST_RS232となります。

意味
PST_FAX ファックス
PST_LAT LAT プロトコルl
PST_MODEM モデム
PST_NETWORK_BRIDGE 未定義ネットワークブリッジ
PST_PARALLELPORT パラレルポート
PST_RS232 RS-232 シリアルポート
PST_RS422 RS-422 ポート
PST_RS423 RS-423 ポート
PST_RS449 RS-449 ポート
PST_SCANNER スキャナ
PST_TCPIP_TELNET TCP/IP TelnetR プロトコル
PST_UNSPECIFIED 未定義
PST_X25 X.25 スタンダード


dwProvCapabilities
:以下の常数でシリアルポートがサポートしている機能を示します。
通常のシリアルポートドライバはPCF_16BITMODEとPCF_SPECIALCHARS以外は大方サポートされています。

意味
PCF_16BITMODE 特別な16ビットモードをサポート
PCF_DTRDSR DTR/DSRをサポート
PCF_INTTIMEOUTS インターバルタイムアウトをサポート
PCF_PARITY_CHECK パリティチェックをサポート
PCF_RLSD RLSD(CD)をサポート
PCF_RTSCTS RTS/CTSをサポート
PCF_SETXCHAR 設定可能なXON/XOFFをサポート
PCF_SPECIALCHARS 設定可能な特殊文字をサポート
PCF_TOTALTIMEOUTS トータルタイムアウトをサポート
PCF_XONXOFF XON/XOFFフロー制御をサポート


dwSettableParams:は以下の常数で、設定変更可能なパラメータを示します。

意味
SP_BAUD ボーレート
SP_DATABITS 1キャラクタのビット数
SP_HANDSHAKING ハンドシェーク 方法(フロー制御)
SP_PARITY パリティのビット数
SP_PARITY_CHECK パリティチェックの有無
SP_RLSD RLSD(CD)
SP_STOPBITS ストップビット数


dwSettableBaud:設定可能な全てのボーレートの種類をdwMaxBaudの項目で定義された常数値でORした値を
示します。

wSettableData:は以下常数のORで設定可能な1キャラクタのビット数を示します。シリアルポートでは5〜8が
サポートされています。

意味
DATABITS_5 5 data bits
DATABITS_6 6 data bits
DATABITS_7 7 data bits
DATABITS_8 8 data bits
DATABITS_16 16 data bits
DATABITS_16X シリアル回線を使った特殊なワイドパス


wSettableStopParity:は以下常数のORで設定可能なストップビット数とパリティを示します。
値           意味
STOPBITS_10 1 ストップビット
STOPBITS_15 1.5 ストップビット
STOPBITS_20 2 ストップビット
PARITY_NONE パリティ無し
PARITY_ODD 奇数パリティ
PARITY_EVEN 偶数パリティ
PARITY_MARK マークパリティ
PARITY_SPACE         スペースパリティ


dwCurrentTxQueue:現在の送信バッファサイズを示します。

dwCurrentRxQueue:現在の受信バッファサイズを示します。

シリアルポートの状態を得るデモ
以下例はCOMMPROP構造体の主要な内容を表示するプロパティシートです。

  ファイル一式(WinZip形式)

AppWizardで新規作成し、ダイアログベースをチェックします。
今まで出てきた内容のアイテムで構成しています。

プロパティシートクラスのコンストラクターから以下独自関数を呼出し、シリアルポートをオープンします。
void CMyCommPage::CopyHandle()
{
  CString CommName[4] = {"COM1","COM2","COM3","COM4"};
  for(int i = 0 ; i < 4 ; i++)
  {// COM1からCOM4までのシリアルポートの状態を読み出したハンドルを各ダイアログ変数にコピー
   hComm[i] = CreateFile(CommName[i],GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);
   CommProp1.hAndle[i] = hComm[i];
   CommProp2.hAndle[i] = hComm[i];
  }
}
CommProp1.hAndle[i] = hComm[i];及びCommProp2.hAndle[i] = hComm[i];の2行で、二つのプロパティペー
ジにファイルハンドラをコピーします。
プロパティページからはプロパティシートクラスの変数が見えないのでここで渡します。


各プロパティページのOnInitDialog() でCOMMPROP構造体を読み出しそれぞれのCListBoxに項目を追加します。
BOOL CMyCommProp1::OnInitDialog()
{
CPropertyPage::OnInitDialog();

  COMMPROP* p_ComProp[4];
  CString msg,MaxBrt;
  for(int i = 0 ; i < 4 ; i++)
  {
    p_ComProp[i] = new COMMPROP;
    GetCommProperties(hAndle[i],p_ComProp[i]);  //COMMPROP構造体の読み出し
    switch(i) {
    case 0: //COM1
     //シリアルポートの設定状態
     GedService(p_ComProp[i],m_Comm1DevList,hAndle[i] ); //設定されているデバイスを得る
        以下省略


シリアルポートの設定
ドライバを含めたシリアルポートの主要な性能を知る方法は解りました。次は希望するパラメータの設定です。
シリアルI/Oへのパラメータ設定は、SetCommState()、逆に現在の設定値の読み出しはGetCommState()
を呼び出します。

以下MSDNより抜粋


BOOL SetCommState(
  HANDLE hFile,  // 通信デバイスへのハンドル
  LPDCB lpDCB    // DCB構造体へのポインタ
);

引数:
hFileCreateFile()が返したデバイスへのハンドル.
lpDCB:現在設定されているデバイスの状態を保持しているDCB構造体へのポインタ
戻り値:成功すると非0、失敗すると0を返します。


BOOL GetCommState(
HANDLE
hFile, // 通信デバイスへのハンドル
LPDCB lpDCB // DCB構造体へのポインタ
);

引数:
hFileCreateFile()が返したデバイスへのハンドル.
lpDCB:現在設定されているデバイスの状態を保持しているDCB構造体へのポインタ
戻り値:成功すると非0、失敗すると0を返します。


両関数にある引数のDCB構造体はシリアルポートの主要なパラメータを収めた構造体で以下のメンバーを持ちます。
typedef struct _DCB{ // dcb
  DWORD DCBlength;        //DCBサイズ
  DWORD BaudRate;         //現在のボーレート
  DWORD fBinary:1;         //バイナリーモード、無EOF検査
  DWORD fParity:1;         //可能化パリティチェック
  DWORD fOutxCtsFlow:1;     //CTSアウトプットフロー制御
  DWORD fOutxDsrFlow:1;     // DSRアウトプットフロー制御
  DWORD fDtrControl:2;      // DTRフロー制御タイプ
  DWORD fDsrSensitivity:1;    // DSR敏感さ
  DWORDfTXContinueOnXoff:1; // XOFFはTxを続けます
  DWORD fOutX:1;         // XON/XOFF外部フロー制御
  DWORD fInX:1;          // XON/XOFF内部フロー制御
  DWORD fErrorChar:1;      //エラー置換可
  DWORD fNull:1;          //NULLストリッピング可
  DWORD fRtsControl:2;     // RTSフロー制御
  DWORD fAbortOnError:1;   //リード/ライトエラーでアボート
  DWORD fDummy2:17;      //リザーブ
  WORD wReserved ;        //現在未使用
  WORD XonLim;          //送信XONスレッショルド
  WORD XoffLim;          //送信XOFFスレッショルド
  BYTE ByteSize;          //1バイトのビット数、4-8
  BYTE Parity;           // 0〜4=パリティなし、奇数、偶数、マーク、スペース
  BYTE StopBits;          // 0〜2=1、1.5、2
  char XonChar;          // 送受信 XON文字
  char XoffChar;          //送受信 XOFF文字
  char ErrorChar;         //エラー置換文字
  char EofChar;          //入力終了文字
  char EvtChar;          //受信イベント文字
  WORD wReserved1;       //リザーブ、使用不可
}DCB;

以下メンバーの説明です。
DCBlength
DCB構造体の長さ〈バイトの〉を指定します。
BaudRate
通信デバイスが動作するボーレートを指定します。
fBinary
バイナリーモードが有効であるかどうかを指定します。
Win32APIは非バイナリのモード転送をサポートしせん。従って、このメンバーはTRUEでなければなりません。
FALSEを設定すると動作しません。
fParity
パリティチェックが有効であるかどうかを指定します。
もしこのメンバーがTRUEであるならば、パリティチェックが有効となり、エラーが報告されます。
fOutxCtsFlow
送信フロー制御のために、CTS信号を監視するかどうかを指定します。
もしこのメンバーがTRUEであり、CTSが非アクティブになると、再びCTSがアクティブになるまで送信が中断されます。
fOutxDsrFlow
送信フロー制御のために、DSR信号を監視するかどうかを指定します。
もしこのメンバーがTRUEであり、DSRが非アクティブになると、再びDSRがアクティブになるまで送信が中断されます。
fDtrControl
DTRフロー制御を指定します。
このメンバーは以下の値のうちの1つを取ることが出来ます。
DTR_CONTROL_DISABLE DTR デバイスがオープンされ、それが禁止状態にされたままでいる場合、DTRライン
を非アクティブにします。
DTR_CONTROL_ENABLE デバイスがオープンされ、それがONにされたままでいる場合、DTRラインをアクティブ
にします。
DTR_CONTROL_HANDSHAKE DTR ハンドシュークを可能にします。
もし、DTRハンドシュークを可能に設定すると、アプリケーションにより、EscapeCommFunction関数を使ってラインが
調整されることはエラーとなります。
fDsrSensitivity
通信ドライバーがDSRシグナルの状態に敏感であるかどうかを指定します。
もしこのメンバーがTRUEであるならば、DSRモデムインプットラインがハイでない限り、ドライバーは、受け取られたど
のようなバイトでも無視します。
fTXContinueOnXoff
相手の入力バッファがFULLで、ドライバーがXoffChar文字を送った時に送信を停止するかどうかを指定します。
もしこのメンバーがTRUEであるならば、相手の入力バッファがバッファサイズ-XoffLimバイトになり受信停止ためにX
OFFキャラクタを送って来ても、送信し続けます。
もしこのメンバーがFALSEであるならば、相手の入力バッファがXonLimになり、XONキャラクタを送信再開のために
送って来るまで、送信は行いません。
fOutX
送信の間にXON/XOFFフロー制御を使われるかどうかを指定します。
もしこのメンバーがTRUEであるならば、XoffChar文字を受けた場合送信を停止し、XonChar文字を受けると送信を再
開します。
fInX
受信の間にXON/XOFFフロー制御が使われるかどうかを指定します。
もしこのメンバーがTRUEであるならば、入力バッファ中の受信データがXoffLimになった場合、XoffChar文字が送られ、XonLimになった場合XonChar文字が送られます。
fErrorChar
パリティ誤りによって受け取られたデータを、ErrorCharメンバーにより指定された文字と交換するかどうかを指定します。
もしこのメンバーがTRUEであり、fParityメンバーがTRUEであるならば、パリティエラー時にErrorCharと受信データを
交換します。
fNull
無効な受信データを処分されるかどうかを指定します。
もしこのメンバーがTRUEであるならば、受け取られる時に、無効なデータが処分されます。
fRtsControl
RTSフロー制御を指定します。
もしこの値が0であるならば、デフォルトはRTS_CONTROL_HANDSHAKEです。
このメンバーは以下の値のうちの1つを取ることが出来ます。
RTS_CONTROL_DISABLE DTR デバイスがオープンされ、それが禁止状態にされたままでいる場合、RTSラインを
非アクティブにします。
RTS_CONTROL_ENABLE デバイスがオープンされ、それがONにされたままでいる場合、RTSラインをアクティブに
します。
RTS_CONTROL_HANDSHAKE RTS ハンドシュークを可能にします。
ドラーバーは入力バッファ中に受信データが半分以下になるとRTSラインを立ち上げ、4分の3以上になると立ち下げ
ます。もし、RTSハンドシュークを可能に設定すると、アプリケーションにより、EscapeCommFunction関数を使ってライ
ンが調整されることはエラーとなります。
RTS_CONTROL_TOGGLE 送信可能ならば、RTSラインをハイにすることを指定します。
すべてのバッファ上の送信データが送られた後、RTSラインをLOWにします。
fAbortOnError
エラーが発生した場合、送受信を終了させるかどうかを指定します。
もしこのメンバーがTRUEであるならば、エラーステータスがエラー発生の場合、すべての送受信動作を終了します。
ドライバーは、アプリケーションがClearCommError関数を呼んでエラーに応答するまで、どのような
通信オペレーションにも対応しません。
fDummy2
使わないでください。
wReserved
使われません。
0に設定しなければなりません。
XonLim
XON文字が送られるまでの入力バッファに許される最小バイト数を指定します。
XoffLim
XOFF文字が送られるまでの入力バッファに許される最大バイト数を指定します。
最大許可バイト数は、この値を入力バッファのサイズ〈バイトの〉から引くことによって計算されます。
ByteSize
送受信バイトのビット数を指定します。
Parity
使用されるパリティスキームを指定します。
StopBits
使用されるストップビットの数を指定します。
XonChar
送受信のためのXON文字の値を指定します。
XoffChar
送受信のためのXOFF文字の値を指定します。
ErrorChar
パリティ誤りによって受け取られたバイトを交換するために用いられる文字の値を指定します。
EofChar
データの終わりをシグナルで伝えるために用いられる文字の値を指定します。
EvtChar
イベントをシグナルで伝えるために用いられる文字の値を指定します。
wReserved1
使わないでください。

とまあ、かなり種類があります。さぞ設定は面倒だと思われるでしょうが、全ての項目を設定する必要はありません。
GetCommState()で現状を読み出し、変更したい部分を同じDCB構造体を引数にSetCommState()を呼び出せ
ば変更しないメンバーはデフォルトで設定されます。

DCB構造体の内容を表示するプログラム
以下プログラムは、デフォルトのシリアルポートがどのように設定されているかを表示するものです。
前のCOMMPROP構造体の内容を表示するデモに、プロパティページを追加しました。

  ファイル一式(WinZip形式)


タイムアウト
通信制御プログラムは、相手装置の状況(相手装置の故障、ケーブルが切断された等)により通信が途中で出来な
くなることがあります。
もし、100バイト来るはずのデータを受信中に、送信側装置の故障で80バイトで送信が止まった場合は、受信を
途中で止めて、故障などの表示をする必要があります。
タイマーはこのような目的に使います。

タイマーの設定はSetCommTimeouts( HANDLE hFile, LPCOMMTIMEOUTS lpCommTimeouts を、
読み出しはGetCommTimeouts( HANDLE hFile,  LPCOMMTIMEOUTSlpCommTimeouts  );を呼び出します。


引数
hFileCreateFile()が返すファイルハンドルです。.
lpCommTimeouts:タイマー値の入ったCOMMTIMEOUTS構造体へのポインタです。.
戻り値
関数が成功すると非0、失敗すると0を返します。


COMMTIMEOUTS構造体は、以下のタイマーに関するメンバーを持ちます。

typedef struct _COMMTIMEOUTS{
  DWORD ReadIntervalTimeout;
  DWORD ReadTotalTimeoutMultiplier;
  DWORD ReadTotalTimeoutConstant;
  DWORD WriteTotalTimeoutMultiplier;
  DWORD WriteTotalTimeoutConstant;
}COMMTIMEOUTS、*LPCOMMTIMEOUTS;

メンバー

ReadIntervalTimeout
通信ラインにおいて2文字の間の最大待ち時間をミリ秒単位で設定します。
ReadFileオペレーションの間に、最初の文字が受け取られてからスタートします。
タイムアウトが発生するとReadFileオペレーションは終了し、それまでの受信データが返されます。
この値が0の場合、タイマーが使われないことを示します。
この値が
MAXDWORDReadTotalTimeoutMultiplierReadTotalTimeoutConstantが0の場合、指定した受信数を受信前
でもリードオペレーションを抜け、受信データを返します。


ReadTotalTimeoutMultiplier
リードオペレーションのためのトータルタイムアウト時間を計算するために使用される、ミリ秒単位の掛算係数を指定します。

ReadTotalTimeoutConstant
リードオペレーションのためのトータルタイムアウト時間を計算するためにミリ秒単位で指定する定数に使用されます。
ReadTotalTimeoutMultiplierとReadTotalTimeoutConstantメンバーが0の場合、リードオペレーションにはトータルタイムアウト
タイマーが使われないことを示します。

WriteTotalTimeoutMultiplier
ライトオペレーションのためのトータルタイムアウト時間を計算するための、ミリ秒単位で指定する掛算係数を指定します。

WriteTotalTimeoutConstant
ライトオペレーションのためのトータルタイムアウト時間を計算するための、ミリ秒単位で指定する定数に使用されます。

WriteTotalTimeoutMultiplierWriteTotalTimeoutConstantメンバーの値が0の場合、ライトオペレーションのためにトータルタ
イムアウトタイマーが使われないことを示します。

注釈
もしアプリケーションがReadIntervalTimeoutReadTotalTimeoutMultiplierMAXDWORDに設定し、
MAXDWORDReadTotalTimeoutConstant>0 に設定するならば、ReadFile関数が呼ばれる時には、以下のうちの1つが起
こります:

・入力バッファに受信データがあるならば、ReadFileは直ちにバッファの文字を伴って戻ります。
・入力バッファに受信データがまったくないならば、ReadFileは、受信するまで待って戻ります。
ReadTotalTimeoutConstantにより指定された時間以内に1バイトも受信しないならばReadFileはタイムアウトになります。

タイマーの時間設定
では具体的にどうにタイマーの時間を設定するか。COMMTIMEOUTS構造体のメンバーを見ると解るように、監視する対象
は2種類あります。一つはインターバル時間、今一つはトータル時間です。
インターバル時間は受信するデータ毎にタイマー監視をするためのもので、例えば9600bpsで受信の場合、1キャラクタ
は1mS程度なので、余裕を見ても10mSあれば十分でしょう。(ただし、相手が連続して送信してくる場合です)。
トータル時間は、以下の計算式で計算されます。この二つの監視時間を越えると、タイムアウトが発生します。
タイムアウトの結果受信動作や送信動作は途中で中断され、受信であればタイムアウト発生時点までのデータが読み出され
ます。

受信
ReadIntervalTimeout
は、次に受信するまでの最大待ち時間をmSで指定します。
ReadTotalTimeoutMultiplierReadTotalTimeoutConstantで受信トータル時間を以下の式で計算します。

受信インターバル時間=ReadIntervalTimeout [mS]
受信トータル時間 =( ReadTotalTimeoutMultiplier * 受信予定バイト数 ) + ReadTotalTimeoutConstant [mS]

送信
送信にはデータ間隔タイマーはありません。
WriteTotalTimeoutMultiplierWriteTotalTimeoutConstantで送信トータル時間を以下の式で計算します。
送信トータル時間 =( WriteTotalTimeoutMultiplier * 送信予定バイト数 ) + WriteTotalTimeoutConstant [mS]

タイマーの時間設定実験
以下デモプログラムは、上記タイマーの設定の後、後述のReadFile()関数で、何も接続していないシリアルポートから、
100バイトの受信動作をしてタイムアウトを発生させて、その時間を計ろうというものです。
このデモを実行させるには、何も接続していないシリアルポートをメニューから選択し、タイムアウト設定のダイアログを開き
設定1から3を選択し、テストメニューで実行させます。画面にタイムアウトまでの実行時間が表示されるはずです。

ご注意:ダイアログ中の設定4は、受信完了までReadFile()から戻らない設定です。Ctl+Alt+DELキーで強制終了する
以外に抜けられなくなります。


  ファイル一式(WinZip形式)

以下デモプログラムの補足説明です。

タイムアウト値の設定
void CChildView::OnSetTimeoutDlg()
{
  COMMTIMEOUTS CommTimeout;
  //現状のタイムアウト設定をCommTimeoutに読み出す
  GetCommTimeouts(h_CommHandle,&CommTimeout);
  CCommTimeDlg TimeSetDlg;
  //ダイアログ変数に現状タイムアウト設定をコピー
  TimeSetDlg.m_ReadIntervalTimeout = CommTimeout.ReadIntervalTimeout;
  TimeSetDlg.m_ReadTotalTimeoutMultiplier = CommTimeout.ReadTotalTimeoutMultiplier;
  TimeSetDlg.m_ReadTotalTimeoutConstant = CommTimeout.ReadTotalTimeoutConstant;
  if(TimeSetDlg.DoModal() == IDOK)
  {
   CommTimeout.ReadIntervalTimeout = TimeSetDlg.m_ReadIntervalTimeout ;
   CommTimeout.ReadTotalTimeoutMultiplier = TimeSetDlg.m_ReadTotalTimeoutMultiplier;
   CommTimeout.ReadTotalTimeoutConstant = TimeSetDlg.m_ReadTotalTimeoutConstant;
   //ダイアログ変数に設定されたイムアウト値で再設定
   SetCommTimeouts(h_CommHandle,&CommTimeout);
  }
}
タイムアウト設定ダイアログで設定された値をSetCommTimeouts(h_CommHandle,&CommTimeout)で
再設定します。



タイムアウト時間の測定
void CChildView::OnRec()
{
  CClientDC dc(this);
  CString Time;
  char RecBuff[200];
  DWORD ReadLen;
  DWORD After,Before;

  dc.TextOut(10,10,"実行中 ");
  Before = GetTickCount();//実行前時間を記録
  ReadFile(h_CommHandle,RecBuff,100,&ReadLen,NULL);
  After = GetTickCount();//実行後時間を記録
  Time.Format("処理時間=%dmS ",After - Before);//差の時間(タイムアウト時間)を表示
  dc.TextOut(10,10,Time);
}
Before = GetTickCount()で受信実行前の時間を読み、ReadFile(h_CommHandle,RecBuff,100,&ReadLen,NULL);で
タイムアウトで戻るのを待ち、After = GetTickCount();でタイムアウト後の時間を記録し、その差の時間を表示します。

GetTickCount()はシステムが起動されてからの時間をmSで返します。作者の経験ではGetSystemTime()で読取る
システム時間より、この関数で返される値のが精度が高いように思えます。

ReadFile(h_CommHandle,RecBuff,100,&ReadLen,NULL)WriteFile()と共に実際の読出しと書込みをする関数で、
すぐ後に出てきます。

データの受信
いよいよ、肝心のデータ転送です。これまで長々と出てきた前置きは、このデータ転送のための準備に過ぎません。
まずは受信からです。
タイムアウト実験のデモプログラムでも出てきましたが、ReadFile()がデータの受信の関数です。
以下MSDNからの抜粋


BOOL ReadFile(
  HANDLE hFile,             // ファイルハンドル
  LPVOID lpBuffer,          // 受信データバッファーへのポインタ
  DWORD nNumberOfBytesToRead// 受信要求数
  LPDWORD lpNumberOfBytesRead, // 実際に読み取ったデータ数を格納するDWORD変数へのポインタ
  LPOVERLAPPED lpOverlapped    // オーバーラップ構造体へのポインタ
);

引数
hFile:ファイルのハンドルを指定します。このハンドルは、GENERIC_READ アクセスを持っていなければなりません。
lpBuffer:バッファへのポインタを指定します。このバッファに、シリアルポートから読み取ったデータが格納されます。nNumberOfBytesToRead:読み取るバイト数を指定します。
lpNumberOfBytesRead:DWORD 型の変数へのポインタを指定します。この変数に、実際に読み取られたバイト数が格納され
ます。
lpOverlapped パラメータにNULLを指定した場合は、lpNumberOfBytesRead パラメータにNULLを指定することはできません。
lpOverlapped パラメータに有効なポインタを指定した場合は、lpNumberOfBytesRead パラメータに NULL を指定することができます。

lpOverlapped
OVERLAPPED 構造体へのポインタを指定します。
hFile ハンドルが FILE_FLAG_OVERLAPPED フラグを持つときに、lpOverlapped パラメータに NULL を指定すると、この関数は、読み取り操作が完了したと間違って通知することがあります。FILE_FLAG_OVERLAPPED フラグを持つときは、NULL を指定しないでください。

hFile
ハンドルが FILE_FLAG_OVERLAPPED フラグを持ち、かつ、lpOverlapped パラメータで有効なポインタを指定したときは
、シリアルポートから非同期的に読み取られます。制御はすぐに返りますが、読み取りが完了しているとは限りません。
読み取りが完了していない場合、ReadFile 関数は FALSE を、GetLastError 関数は ERROR_IO_PENDING を返します。
その後、読み取りが完了すると、OVERLAPPED 構造体で指定したイベントがシグナル状態になります。

hFile
ハンドルが FILE_FLAG_OVERLAPPED フラグを持たないとき、シリアルポートから同期的に読み取られます。
読み取りが完了すると、制御が返ります。

戻り値

関数が成功すると、0 以外の値が返ります。
関数が失敗すると、0 が返ります。拡張エラー情報を取得するには、GetLastError 関数を使います。


データを受信する方法は大きく分けて以下の3通りあります。

  1. 同期I/O
  2. 非同期I/O
  3. イベント駆動I/O

それぞれの概要は以下のようになります。

同期I/O

このモードで設定されている場合、ReadFile()を呼び出すと指定したサイズの受信が完了するまで戻りません。
従って、関数から直ちに戻る必要がある場合は、現在の受信バッファ中のデータ数を調べReadFile()の引数にそれ以下を
指定するか、又はタイマー設定を、すぐ戻る設定にしなければなりません。この方式の用途は、比較的単純なデータ受信に向
きます。
CreateFile()の引数dwFlagsAndAttributesをNULLに設定すると、ReadFile()同期I/Oとして動作します。


非同期I/O
このモードで設定されている場合ReadFile()を呼び出すと、関数から直ちに戻ります。実際の受信は、通常バックグウラン
ドと呼ばれる場所で実行され、GetOverlappedResult()で受信結果を得ます。受信中に他の処理をする必要がある時に有効な
モードで比較的CPU資源を消費しません。
CreateFile()の引数dwFlagsAndAttributesを有効な値に設定し、ReadFile()OVERLAPPED構造体を指定すると
同期I/Oとして動作します。

イベント駆動
SetCommMask()でデータ受信などのイベントをセットし、WaitCommEvent()でイベント発生を待ちます。
WaitCommEvent()でデータ受信が発生すると、受信データサイズを得てReadFile()で実際に受信データを読み出します
。このモードの場合も同期I/O非同期I/Oがあり、同期I/Oの場合はWaitCommEvent()
からは、イベントが発生するまで関数から戻りません、しかし効率的な待ち方をするので、CPU資源をほとんど消費しません。
非同期I/Oの場合はWaitCommEvent()からすぐに戻ります。
通常このイベント駆動WaitCommEvent()は受信のためのスレッド中に配置します。

イベントには以下の種類があります

意味
EV_BREAK ブレークを検出.
EV_CTS CTS信号が変化した
EV_DSR DSR信号が変化した
EV_ERR フレーミングエラー又はオーバーランエラー又はパリティエラーが発生した.
EV_RING リンギングを検出した
EV_RLSD CD信号が変化した
EV_RXCHAR データを受信し、受信バッファに格納した
EV_RXFLAG DCB構造体のEvtCharに設定のデータを受信した
EV_TXEMPTY 送信バッファから最後のデータを送信した


受信されたデータ数を実際にReadFile()を呼び出す前に知りたい場合があります。同期I/Oなどの場合は特に必要になりま
す。
ClearCommError()は以下プロトタイプに示すように、COMSTAT構造体へのポインタを返します。COMSTAT構造体の
メンバーには現在までの受信データサイズを示すcbInQueがあり、これを参照することで受信データ数を知る事が出来ます。
2番目のパラメータlpErrorsは、各エラーのビットマスクを設定するDWORD型変数へのポインタで、ここにエラーが書かれま
す。ClearCommError()の本来の目的はエラーを消すことなので、受信数を得る目的でこの関数を呼んだ場合でも、エラー
のチェックを行う必要があります。
又COMSTAT構造体の最初の方のメンバーは、送信が出来ない場合、その理由を示します。

BOOL ClearCommError(
  HANDLE hFile,     // handle to communications device
  LPDWORD lpErrors, // pointer to variable to receive error codes
  LPCOMSTAT lpStat  // pointer to buffer for communications status
);


COMSTAT構造体
typedef struct _COMSTAT {
  DWORD fCtsHold : 1;    // CTS信号待ち 
  DWORD fDsrHold : 1;    // DSR信号待ち 
  DWORD fRlsdHold : 1;   // RLSD 信号待ち 
  DWORD fXoffHold : 1;   // XOFFを受信した 
  DWORD fXoffSent : 1;   // XOFFを送信した
  DWORD fEof : 1;      // EOFを送信した 
  DWORD fTxim : 1;     // 送信待ち 
  DWORD fReserved : 25; // reserved
  DWORD cbInQue;     // 受信バッファ中のデータ数 
  DWORD cbOutQue;    // 送信バッファ中のデータ数
} COMSTAT, *LPCOMSTAT;


イベント駆動を使った通信の例
同期I/Oは、直感的で解りやすい代わりに周期的に受信データ数を問い合わせをする必要があります。
同期I/Oは、その場でないにしてもやはり周期的に受信完了をプログラムが見に行かなければなりません。
その点イベント駆動はWaitCommEvent()で受信イベントの発生を、非常に効率的に待つので、CPUにほとんど負担を
かけません。
以下の例は、このイベント駆動で受信待ちをしたパソコン同士の通信プログラムで、2台のパソコンをRS232Cケーブルで接
続し、同じプログラムを走らせます。画面左側のエディットボックスが送信データの編集用です。
送信用エディットボックスに適当な文字を書いたら、送信メニュー->実行で相手のパソコンに送信されます。

受信スレッドは立ち上げと同時に実行可能状態に遷移し、常にWaitCommEvent()で受信待ちしています。
しかし、他のWindow操作をしてみると解りますが、ほとんど負荷は増えていないことが解ります。
この状態で突然データが到着しても、ちゃんと受信しています。

  ファイル一式(WinZip形式)

何人かの方から、この例題がNT系で動作しない旨のご指摘がありました。(2002年2月)
「こうすれば動くよ」という報告もあり、作者のWindowsXPホームエディションで動作を確認したので、
再度アップします。
変更のポイントは、ReadFile、WriteFile関数の第5引数に、グローバル変数で、オーバーラップ構造体を指定する事です。

例えば、ReadFile、WriteFile関数呼び出しの前に、オーバーラップ構造体変数
 OVERLAPPED recop,sendop を定義しておき、
ReadFile(h_CommHandle,RecBuf.GetBuffer(Comstat.cbInQue),Comstat.cbInQue,&NoOfByte,
&recop);
WriteFile(h_CommHandle,Msg,Msg.GetLength(),&NoOfByte,
&sendop)
のようにすると正しく動作するようになりました。(2005年5月)

  修正ファイル一式(WinZip形式)


以下このデモの主要部分です。


グローバル変数
volatile HANDLE h_CommHandle; //通信ポートハンドル
volatile int GoOut; //スレッドの脱出フラグ
CCriticalSection Key; //通信ポートハンドルにアクセスするクリチカルセクション
HWND vhWnd; //ユーザー定義メッセージをポストする際のウインドウハンドラ

メンバー変数
public:
UINT m_ComPortSel;//通信ポート選択
CWinThread* pThread;//受信スレッドへのポインタ
CEdit m_SendEdit,m_RecdEdit;//エディットボックスオブジェクト
CStatic SendStatic,RecStatic;//スタティックオブジェクト
CString m_MsgStr;//受信表示バッファ

受信スレッド
UINT ReadThread(LPVOID)
{
  HANDLE hAndle;
  DWORD Event;
  while(!GoOut)
  {//脱出フラグが0である間実行
   Key.Lock(); //通信ハンドルにアクセスする場合ロック
   hAndle = h_CommHandle;
   Key.Unlock();// ロック解除
   //データ受信のイベントで待つ
   WaitCommEvent(hAndle,&Event,NULL);
   if(Event == EV_RXCHAR)
   {//受信イベントがEV_RXCHARならウインドウメッセージをポストする
     ::PostMessage(vhWnd,WM_READ_END,0,0);
   }
 }
return 0;
}
受信スレッドは、なんとこれだけです。h_CommHandleを排他アクセスするのは、ユーザー設定でCOMポートの番号を変更
中はスレッドをサスペンドするためです。
このスレッドは大半をWaitCommEvent(hAndle,&Event,NULL);で受信イベント待ちで過ごします。
受信イベントが発生すると::PostMessage(vhWnd,WM_READ_END,0,0);
WM_READ_ENDなるユーザー定義ウインドウ
メッセージをポストします。そして、再び
WaitCommEvent(hAndle,&Event,NULL)で受信イベントを待ちます。

この
::PostMessage(vhWnd,WM_READ_END,0,0);が重要な意味を持ちます。
Windowsアプリケーションはウィンドウメッセージに反応して動作していることは、既に何度も見てきました。
データの受信を待つアプリケーションは、データがRS232C回線に到着した旨のウインドウメッセージが来ることを期待したい
のですが、それは用意されていません。
その代わり、ユーザーが自由にウインドウメッセージを定義して、それをデータ受信
メッセージとして割振ることは可能なのです。上記はまさにそれを実現したものです。

つまりデータ受信もマウスクリックのようなウインドウメッセージとして扱えるようになるということです。

コンストラクタ
CCommTermView::CCommTermView()
{
 m_ComPortSel = 0; //デフォルトのポートは1
 GoOut = 0; //脱出フラグを初期化

 if(FALSE == CommOpen(m_ComPortSel))
 {//COMポートオープン
   MessageBox ("COMポートは使えません","",MB_OK);
 }
 //受信スレッドを生成
  pThread = AfxBeginThread(ReadThread,0,THREAD_PRIORITY_NORMAL,0,CREATE_SUSPENDED);
  //スレッドの自動削除を禁止
  pThread -> m_bAutoDelete = FALSE;
}
ここで通信ポートを開き、受信スレッドを生成しますが、まだサスペンドされたままです。


ウインドウ生成時
int CCommTermView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
     途中省略

  if (CView::OnCreate(lpCreateStruct) == -1)
    return -1;

  //ウインドウハンドルを得る
  vhWnd = GetSafeHwnd();
  //スレッド実行開始
  pThread ->ResumeThread();
  return 0;
}
vhWnd = GetSafeHwnd(); でユーザー定義メッセージをポストする際のウインドウハンドラを得ます。
そして、いよいよ受信スレッド実行可能状態に遷移させます。



ユーザー定義メッセージのハンドラ
LRESULT CCommTermView::OnEndRead(WPARAM m, LPARAM l)
{//受信スレッドが受信したデータを読み取る
  CString RecBuf; //受信バッファ
  CString Msg ; //表示バッファ
  COMSTAT Comstat; //通信状態
  DWORD NoOfByte,Error;
  if(h_CommHandle != INVALID_HANDLE_VALUE)
  {//通信ハンドルが正常なら
    ClearCommError(h_CommHandle,&Error,&Comstat);
    if(Comstat.cbInQue)
    {//受信データサイズ分データをRecBufに読み出す
     ReadFile(h_CommHandle,RecBuf.GetBuffer(Comstat.cbInQue),Comstat.cbInQue,&NoOfByte,NULL);
     RecBuf.ReleaseBuffer();
     m_MsgStr += RecBuf; //受信データ表示バッファに追加コピー
     m_RecdEdit.SetWindowText(m_MsgStr); //受信データ表示エディットボックスにコピー
    }
  }
return 0;
}
ここが、スレッドが受信イベントを検知した際に発行したユーザー定義メッセージのハンドラです。ClearCommError()
で、まず受信サイズを得ます。そしてデ−タをRecBufに格納し、現在の受信表示バッファに追加コピーします。
それを更に画面右にある受信表示用エディットボックスにコピーします。
RecBuf.GetBuffer
CStringクラスのメンバー関数でオブジェクトから、バッファ配列を、その引数分取り出します。
後処理でReleaseBuffer()を呼出て、元の
CStringオブジェクトに戻します。

何故、RecBufからいきなりエディットボックスへ追加コピーしないかと、疑問に思われるかも知れません。
最初作者もそのようにして、漢字変換の罠にはまりました。漢字が混じった文字を受信する場合の共通の問題かと思いま
すが、漢字コードは8ビットデータを2回に分けて送信します。当然、受信も2回に分けて受信します。これが問題なのですが
漢字コードの最初の1バイトと次の1バイトの間で受信イベントが発行される場合があります。その途切れた漢字コードは
もはや漢字ではなくなっています。それをエディットボックスへコピーして表示させたら、どうなるでしょうか。
エディットボックス内部で漢字の1バイト目が来たので2バイト目を読みに行こうとしたら、それが存在しないのだから、
1バイト目を1バイトコードと判断して表示するしかありません。ここでエディットボックスとしての責任である表示は完了します。
その後漢字コードの2バイト目が来てもそれは、最初の文字と判断するはずです。従ってエディットボックス内部でデータが
変換されていると予測されます。
これを避けるには、エディットボックスに漢字コードが連続したデータを与えるしかありません。そこで、受信データ専用の
表示バッファを用意して、受信データのアペンドをします。そのバッファをエディットボックスに与えたら問題は解決しました。


ウインドウ削除時
void CCommTermView::OnDestroy()
{
 CView::OnDestroy();
 //受信スレッドの削除
 GoOut = 1;
 SetCommMask(h_CommHandle,0); //待ちイベントをクリア
 HANDLE hThread = pThread-> m_hThread; //スレッドのハンドルを得る
 ::WaitForSingleObject(hThread,INFINITE);//スレッドが終了するまで待つ
 delete pThread; //スレッドオブジェクトを削除
 CloseHandle(h_CommHandle);//通信ハンドルの開放
}
ここでスレッドを終了させます。

通信ポートの設定
BOOL CCommTermView::CommOpen(UINT CommPort)
{//通信ポートオープン
  CString PortName[] = {"COM1","COM2","COM3","COM4"};
  //同期I/Oでオープン、受信はイベントI/O、送信は同期I/O+タイムアウト
  h_CommHandle = CreateFile(PortName[CommPort],GENERIC_READ|GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);
  SetCommMask(h_CommHandle,EV_RXCHAR);
  if( h_CommHandle== INVALID_HANDLE_VALUE)
    return FALSE;
  else
  {
  //タイマー設定:受信待ち無限、送信待ち100mS + 1文字10mS
  COMMTIMEOUTS CommTimeout;
  CommTimeout.ReadIntervalTimeout = 0;
  CommTimeout.ReadTotalTimeoutMultiplier = 0;
  CommTimeout.ReadTotalTimeoutConstant = 0;
  CommTimeout.WriteTotalTimeoutMultiplier = 10;
  CommTimeout.WriteTotalTimeoutConstant = 100;
  SetCommTimeouts(h_CommHandle,&CommTimeout);
  //通信設定 19200BPS,8BIT,偶数パリティ,STOP1に固定
  DCB Dcb;
  GetCommState(h_CommHandle,&Dcb);
  Dcb.BaudRate = CBR_19200;
  Dcb.ByteSize = 8;
  Dcb.Parity = EVENPARITY;
  Dcb.StopBits = ONESTOPBIT;
  SetCommState(h_CommHandle,&Dcb);
  return TRUE;
  }
}
通信ポートの設定です。GetCommState(h_CommHandle,&Dcb);で一旦デフォルトを読み出し、主要な変更箇所を変更し
SetCommState(h_CommHandle,&Dcb);で再設定しています。
受信タイムアウトはイベント駆動なので、無限に待つ設定です。送信タイムアウトは1文字10mSに設定しています。



データの送信
void CCommTermView::OnSendEx()
{//送信メニュー(実行)のハンドラ
  CString Msg;
  DWORD NoOfByte;
  //送信エディットボックスの内容を送信
  m_SendEdit.GetWindowText(Msg);
  WriteFile(h_CommHandle,Msg,Msg.GetLength(),&NoOfByte,NULL);
}
これもあっさりしてますが、データの送信です。メニューの送信->実行で起動されます。
もう少し細かい送信についての話はこの後すぐ出てきますが、このようにほとんど受信と同じです。



データの送信
送信は、受信と違って何時データに対する処理が発生するかが決まっています。従って受信のように常に監視する必要は無
く、単に短い時間のタイムアウトを設定すれば事足ります。
しかし、信頼性の高いデータ転送を目差すなら、送信が成功したかどうかを受信側の応答を待つなどのプロトコルを備える
必要があります。それはともかく、データの送信は上記デモにもありましたがWriteFile()を呼び出します。

以下MSDNからの抜粋


BOOL WriteFile(
  HANDLE hFile,                // シリアルポートへのハンドラ
  LPCVOID lpBuffer,            // 送信バッファへのポインタ
  DWORD nNumberOfBytesToWrite  // 送信データ数
  LPDWORD lpNumberOfBytesWritten, // 実際に送信したデータ数
  LPOVERLAPPED lpOverlapped       // 非同期I/O動作の場合のオーバーラップ構造体へのポインタ
);

引数
hFile:シリアルポートのハンドルを指定します。このハンドルは、GENERIC_WRITE アクセスを持っていなければなりません。lpBuffer:書き込むデータが入ったバッファへのポインタを指定します。
nNumberOfBytesToWrite:書き込むバイト数を指定します。
lpNumberOfBytesWritten:DWORD 型の変数へのポインタを指定します。この変数に、実際に書き込まれたバイト数が格納されます。
lpOverlapped :パラメータに NULL を指定した場合は、lpNumberOfBytesWritten パラメータに NULL を指定することはできません。
lpOverlapped :パラメータに有効なポインタを指定した場合は、lpNumberOfBytesWritten パラメータに NULL を指定することができます。

lpOverlapped

OVERLAPPED 構造体へのポインタを指定します。
hFile ハンドルが FILE_FLAG_OVERLAPPED フラグを持つとき、かつ、lpOverlapped パラメータで NULL を指定したときは、こ
の関数は、書き込み操作が完了したと間違って通知します。FILE_FLAG_OVERLAPPED フラグを持つときは、NULL を指定し
ないでください。

hFile
ハンドルが FILE_FLAG_OVERLAPPED フラグを持つとき、かつ、lpOverlapped パラメータで有効なハンドルを指定したと
きは、OVERLAPPED 構造体で指定したオフセットからファイルが非同期的に書き込まれます。制御はすぐに返りますが、書
き込みが完了しているとは限りません。書き込みが終了していない場合は、WriteFile 関数は FALSE を、GetLastError
数は ERROR_IO_PENDING を返します。その後、書き込みが完了すると、OVERLAPPED 構造体で指定したイベントがシグ
ナル状態になります。

hFile
ハンドルが FILE_FLAG_OVERLAPPED フラグを持たないとき、かつ、lpOverlapped パラメータで NULL を指定したときは
、ファイルポインタの現在位置からファイルが同期的に書き込まれます。書き込みが完了すると、制御が返ります。

hFile
ハンドルが FILE_FLAG_OVERLAPPED フラグを持たないとき、かつ、lpOverlapped パラメータで有効なポインタを指定し
たときは、OVERLAPPED 構造体で指定したオフセットからファイルが同期的に書き込まれます。書き込みが完了すると、制
御が返ります。

戻り値

関数が成功すると、0 以外の値が返ります。
関数が失敗すると、0 が返ります。拡張エラー情報を取得するには、GetLastError 関数を使います。


このドキュメントのように、送信も受信とまったく同様に動作します。もしもイベント駆動で実現するなら、待つイベントの種類は
送信バッファが空になった時に発生するEV_TXEMPTYが最適でしょう。

送信を途中で一旦停止したい。
送信バッファにまだデータがある場合でも、バッファはそのままでデータ送信を止めるには、SetCommBreak()を呼び出し
ます。
BOOL SetCommBreak( HANDLE hFile );
hFileはシリアルポートへのハンドルで成功すると非0、失敗すると0を返します。

この状態を再開するには、ClearCommBreak()を呼び出します。
BOOL ClearCommBreak( HANDLE hFile );
hFileはシリアルポートへのハンドルで成功すると非0、失敗すると0を返します。

送信バッファに格納しないで即送信したい。
送信バッファにあるデータより先んじて、緊急で1キャラクタだけ割り込んで送信したい場合は、TransmitCommChar()
呼び出します。
BOOL TransmitCommChar( HANDLE hFile, char cChar );
hFileはシリアルポートへのハンドル、cCharは送信したいデータで成功すると非0、失敗すると0を返します。


エラー
通信中は様々なエラーが発生します。それはデータが通信ケーブルを介して相手装置へ伝送する過程でノイズなどの混入で
データが化けたりする場合があるからです。
PCが認識できた通信中のエラーを得るには、ClearCommError()を呼び出します。

BOOL ClearCommError(
HANDLE
hFile, // シリアルポートへのハンドル
LPDWORD lpErrors, // エラーを報告するDWORD型変数へのポインタ
LPCOMSTAT lpStat // COMSTAT構造体へのポインタ
);

pErrorsにDWORD型変数へのポインタを指定して、この関数を呼び出すと、そこに以下のエラーコードが設定されて戻ります。

意味
CE_BREAK ハードウエアがブレークを検出した
CE_DNS Windows 95/98: パラレルデバイスが選択されていない
CE_FRAME ハードウエアがフレーミングエラーを検出した.
CE_IOE 通信中にデバイスエラーを検出した
CE_MODE 要求さあされたモードはサポートできないか又は,hFileが無効。このエラーがある場合
他のエラーは意味を持たない。
CE_OOP Windows 95/98: パラレルポートが給紙切れ状態
CE_OVERRUN 受信データばバッファからオーバーフローした。データは失われた
CE_PTO Windows 95/98: パラレルデバイスがタイムアウトになった
CE_RXOVER 受信バッファのオーバーラン。受信バッファがあふれたか又はEOF受信以降にデータを
受信した。
CE_RXPARITY ハードウエアがパリティエラーを検出した
CE_TXFULL 送信バッファがFULL


制御信号の設定
本来Windowsにお任せの通信制御信号もEscapeCommFunction()の呼出で自由に設定が可能です。
BOOL EscapeCommFunction( HANDLE hFile, DWORD dwFunc);
hFile:シリアルポートへのハンドル。
dwFunc:設定したい制御信号で以下の値で定義されています。

Value Meaning
CLRDTR DTR (data-terminal-ready)を非アクティブにする
CLRRTS RTS (request-to-send) を非アクティブにする
SETDTR DTR (data-terminal-ready)をアクティブにする
SETRTS RTS (request-to-send)をアクティブにする
SETXOFF XOFFを受信時に、その処理を実行する
SETXON XONを受信時に、その処理を実行する
SETBREAK  ブレイク状態に設定する
CLRBREAK ブレイク状態を解除する


以上でWindowsによるRS232Cの初歩的な通信の章を終了します。DOSの時代にはBIOSを使ったり、様々なRS232C
ライブラリがありました。Windowsになって選択の幅は狭まりましたが、Win32通信APIとスレッドの応用で非常に簡潔
に通信プログラムが組めるようになりました。

次の章からは又GUIの話に戻り、そしてWindows開発の中心的な存在であるドキュメント/ビューの話に入ります。