レガシー Windows API(概要)について

ここで紹介しているAPIは、Windows95、Windows NT3.5時代のAPI群であり、最新のAPIは含まれていないことに注しして欲しい。なお、当時の解説書は現在よりもより詳細に解説しているため、参考になるはずである。

  • レイアウトを変換する際、崩れているページがあります。
  • 概要およびSampleコードはありますが、APIの解説はありません。

ご要望があれば旧版のWin32 API(HELP形式)をお譲りします。問い合わせフォームより申し込みください。

Windows API 通信の概要

「通信リソース」とは、 双方向の非同期データ ストリームを提供する物理的または論理的なデバイスです。シリアル ポート、 パラレル ポート、 ファックス、 モデムなどが通信リソースです。各通信リソースについて、 アプリケーションがリソースにアクセスできるようにするためのライブラリやドライバで構成されるサービス プロバイダが存在します。

ファイル入出力 (I/O) 関数 (CreateFileCloseHandleReadFile ReadFileExWriteFile WriteFileEx) は、 通信リソース ハンドルのオープン、 クローズや読み書き操作のための基本的なインターフェイスを提供しています。また、 Microsoft(R) Win32(R)アプリケーション プログラミング インターフェイス (API) には、 通信リソースにアクセスするための通信関数のセットがあります。このトピックでは、 ファイルI/O関数や通信関数を使って次のような作業を実行する方法について説明します。

指定した通信リソースを識別するハンドルのオープン

シリアル通信リソースの構成の設定と問い合わせ

シリアル通信リソースの読み書き

特定のシリアル通信リソースで発生するイベントの監視

通信リソースに関連付けられているデバイス ドライバへの、 拡張機能を実行させる制御コマンドの送出

次に示すトピックでは、 Win32の通信関数について説明しています。

通信リソース ハンドル

通信リソースの構成

I/O操作

通信イベント

制御関数

通信リソースの構成

通信イベントの監視

通信関数

通信リソース ハンドル

プロセスは、 CreateFile関数を使って、 通信リソースのハンドルをオープンできます。たとえば、 COM1を指定するとシリアル ポートのハンドルがオープンされ、 LPT1を指定するとパラレル ポートのハンドルがオープンされます。指定されたリソースがプロセスで使われているときは、 CreateFileは失敗します。プロセスの各スレッドは、 CreateFileが返したハンドルを使って、 リソースをアクセスする関数でリソースを識別できます。

CreateFileを使ってデバイスを直接識別するハンドルをオープンするときは、 特別な文字\.を使ってデバイスを識別してください。たとえば、 ドライブAのハンドルをオープンするには、 CreateFilelpszNameパラメータに\.a:を指定してください。呼び出し側のプロセスは、 DeviceIoControl関数にこのハンドルを指定して、 デバイスに制御コードを送ることができます。

CreateFileを呼び出して通信リソースをオープンするときは、 次に示す属性を指定します。

指定したリソースへの読み書きアクセスの種類

ハンドルを子プロセスが継承するかどうか

ハンドルをオーバーラップ (非同期) I/O操作で使用するかどうか (オーバーラップ操作の記述については同期の概要を参照してください。)

CreateFileを使って通信リソースをオープンするときは、 次に示すパラメータを指定してください。

リソースを排他アクセスでオープンするため、 fdwShareModeパラメータには0を指定します。

fdwCreateパラメータにはOPEN_EXISTINGフラグを指定します。

hTemplateFileパラメータにはNULLを指定します。

通信リソース構成の変更

CreateFile関数でシリアル通信リソースをオープンするとき、 システムは、 リソースが前回オープンされたときに設定されていた値に従って、 リソースを初期化して構成します。前回の設定を流用することによって、 MS-DOS(R) MODEコマンドでの設定を、 デバイスを再オープンしたときにも使うことができます。前回のオープン操作から継承した値には、 I/O操作で使われたデバイス コントロール ブロック (DCB構造体) の構成設定とタイムアウト値が含まれます。デバイスが一度もオープンされていないときは、 システムのデフォルト値で構成されます。

シリアル通信リソースの初期構成を判断するには、 GetCommState関数を呼び出します。この関数は、 シリアル ポートのDCB構造体に現在の構成設定を設定します。この構成を変更するには、 DCBを指定してSetCommState関数を呼び出します。

DCB構造体のメンバは、 ボー レート、 バイト当たりのデータ ビット数、 バイト当たりのストップ ビット数などの構成設定を示します。DCBのメンバは、 このほかにも、 特殊文字の指定や、 パリティ チェックやフロー制御も示します。これらの構成設定の一部だけを修正しなければならないときは、 まず、 GetCommStateを呼び出して、 現在の構成をDCB構造体に設定してください。それから、 DCB構造体の値を修正し、 修正したDCB構造体を指定してSetCommStateを呼び出してください。この処理によって、 DCBの修正されなかったメンバには、 適切な値が設定されます。たとえば、 XonCharメンバとXoffCharメンバが等しいDCB構造体でデバイスを構成すると、 一般エラーが発生します。DCB構造体の一部のメンバは、 以前のバージョンのMicrosoft Windows(TM) とは異なっています。特に、 RTS (送信要求) DTR (データ端末レディ) フロー制御のフラグが変更されています。

BuildCommDCB関数でDCB構造体を修正することもできます。BuildCommDCBには、 Windows NTまたはMS-DOSMODEコマンド ラインと同じ引数を使って、 ボー レート、 パリティ、 ストップ ビット数、 データ ビット数を指定します。DCBのほかのメンバはこの関数では変更されませんが、 XON/XOFFとハードウェア フロー制御を使用不能にするための値は設定されます。BuildCommDCBDCB構造体を修正するだけで、 デバイスの再構成は行いません。

通信リソースを再構成するには、 GetCommProperties関数を使って、 デバイス ドライバがサポートしている構成設定に関する情報を取得します。この情報は、 サポートされていない構成を指定しないようにするために使います。

SetCommState関数は、 通信リソースを再構成しますが、 指定されたドライバの内部的な入出力バッファには影響を与えません。バッファはフラッシュされず、 処理待ち中の読み書き操作もそのままです。

通信リソースを再初期化するには、 SetupComm関数を使います。この関数は、 次に示す処理を実行します。

処理待ち中の読み書き操作があれば、 完了していなくても操作を終了します。

読み取られていない文字を廃棄し、 指定されたリソースに関連付けられているドライバの内部的な入出力バッファを解放します。

内部的な入出力バッファを再割り当てします。

プロセスはSetupCommを呼び出す必要はありません。プロセスが呼び出さなければ、 通信リソース ハンドルを最初に使ったときに、 リソースのドライバがデフォルト設定でデバイスを初期化します。

読み書き操作

Win32 APIは、 シリアル通信リソースに対する同期および非同期 (オーバーラップ) のファイルI/O操作をサポートしています。呼び出し側のスレッドは、 オーバーラップ操作によって、 I/O操作をバックグラウンドで実行しながらほかの作業を実行できます。通信リソースから読み取るにはReadFile関数かReadFileEx関数を、 通信リソースに書き込むにはWriteFile関数かWriteFileEx関数を使います。ReadFileWriteFileは、 操作を同期または非同期で実行できます。ReadFileExWriteFileExは、 操作を非同期でしか実行できません。

これらの読み書き関数の動作は、 関数をオーバーラップ操作として起動したかどうか、 ハンドルにタイムアウト パラメータが関連付けられているかどうか、 ハンドルにフロー制御パラメータが関連付けられているかどうかによって異なります。

また、 通信リソースに書き込むには、 TransmitCommChar関数を使うこともできます。この関数は、 出力バッファ内の処理待ち中データよりも先に、 指定された文字を送信します。これは、 優先順位の高いシグナル文字を送信するときに役立ちます。優先順位の高い文字の送信もフロー制御と書き込みタイムアウトにしたがい、 操作は同期的に行われます。

デバイスの出力バッファや入力バッファの文字をすべて廃棄するには、 PurgeComm関数を使います。また、 PurgeCommは、 処理待ち中の読み書き操作が完了していなくても操作を終了します。PurgeCommを使って出力バッファをフラッシュすると、 削除された文字は送信されません。出力バッファの内容を送信してバッファをからにするには、 FlushFileBuffers関数 (同期操作) を呼び出します。しかし、 FlushFileBuffersはフロー制御には従っても書き込みタイムアウトには従わないため、 処理待ち中の書き込み操作がすべて送信されるまで戻りません。

オーバーラップ操作

スレッドは、 オーバーラップ操作によって、 時間のかかるI/O操作をバックグラウンドで実行し、 その時間を利用してほかの処理を実行できます。通信リソースに対するオーバーラップ操作を使用可能にするには、 CreateFile関数を呼び出してハンドルをオープンするときに、 FILE_FLAG_OVERLAPPEDフラグを指定してください。ReadFile関数やWriteFile関数をオーバーラップ操作として起動するには、 OVERLAPPED構造体を指すポインタを指定してください。NULLポインタを指定すると、 FILE_FLAG_OVERLAPPEDを指定してハンドルをオープンしていても、 操作は同期的に実行されます。OVERLAPPED構造体には、 (自動リセットでない) 手動リセットのイベント オブジェクトのハンドルを設定してください。操作が完了する前にI/O関数が戻ると、 システムは、 イベント オブジェクトを非シグナル状態に設定します。操作が完了すると、 システムは、 イベント オブジェクトをシグナル状態に設定します。スレッドは、 待機関数を使って、 イベント オブジェクトの現在の状態をチェックしたり、 シグナル状態になるまで待機します。

ReadFileEx関数とWriteFileEx関数は、 オーバーラップ操作としてだけ実行できます。FileIOCompletionRoutine関数を指すポインタを呼び出し側のスレッドが指定すると、 その関数は、 オーバーラップ操作が完了したときに実行されます。完了ルーチンは、 呼び出し側のスレッドがアラート可能操作を行っているときだけ実行されます。

イベント オブジェクト、 待機関数、 アラート可能待機、 完了ルーチンについて、 詳しくは同期の概要を参照してください。

タイムアウト

通信リソースのハンドルには、 読み書き操作に影響するタイムアウト パラメータのセットが関連付けられています。タイムアウト時間が経過すると、 指定された個数の文字がまだ読み書きされていなくても、 ReadFile ReadFileExWriteFile WriteFileExなどの操作は終了します。読み書き操作中にタイムアウトが発生しても、 エラーとしては扱われません (つまり、 読み書き関数は正常終了を示す値を返します)ReadFileWriteFile (I/O操作をオーバーラップして実行したときはGetOverlappedResult関数やFileIOCompletionRoutine関数) は、 実際に読み書きしたバイト数を返します。

通信リソースがオープンされるとき、 リソースが前回オープンされたときに設定されていた値に従って、 タイムアウト値が設定されます。リソースが一度もオープンされていないときや、 SetupComm関数を呼び出したときは、 タイムアウト パラメータはすべて0に設定されます。現在のタイムアウト値を取得するには、 GetCommTimeouts関数を呼び出してください。タイムアウト値を変更するには、 SetCommTimeouts関数を呼び出してください。

タイムアウト パラメータを使って、 2種類のタイムアウトを指定できます。間隔タイムアウトは、 2つの文字を受け取る時間の間隔が指定されたミリ秒を超えると発生します。タイムアウトの判定は、 1文字目を受け取ってから次の文字を受け取るまでの時間に対して行われます。総計タイムアウトは、 操作に費やされた時間の総計が計算されたミリ秒を超えると発生します。タイムアウトの測定は、 I/O操作が開始したときから開始されます。書き込み操作では総計タイムアウトしかサポートされません。読み取り操作では間隔タイムアウトと総計タイムアウトの両方がサポートされており、 組み合わせて使うこともできます。

読み書き操作の総計タイムアウト時間 (ミリ秒) は、 GetCommTimeouts関数やSetCommTimeouts関数のCOMMTIMEOUTS構造体で指定した係数 (MULTIPLIER) と定数 (CONSTANT) から計算されます。計算は、 次に示す式を使って行われます。

タイムアウト = (MULTIPLIER * バイト数) + CONSTANT

係数と定数を使うことによって、 要求するデータの量に応じて総計タイムアウト時間を変化させることができます。係数に0を設定すると定数だけが使われ、 定数に0を設定すると係数だけが使われます。定数と係数の両方に0を設定すると、 総計タイムアウトは使われません。

読み取りタイムアウト パラメータがすべて0ならば、 読み取りタイムアウトは使われず、 要求したバイト数を読み取るかエラーが発生するまで読み取り操作は完了しません。同様に、 書き込みタイムアウト パラメータが0ならば、 要求したバイト数を書き込むかエラーが発生するまで書き込み操作は完了しません。

読み取り間隔タイムアウト パラメータがMAXDWORDで、 読み取り総計タイムアウト パラメータがどちらも0ならば、 入力バッファの利用可能な文字を読み取るとすぐに読み取り操作は完了します。バッファが空の場合もすぐに完了します。これは、 以前のバージョンのWindowsReadComm関数と同じです。

間隔タイムアウトを指定することによって、 データの受け取りに小休止があったときに読み取り操作が戻るようにすることができます。間隔タイムアウトを使用する場合、 かなり短い時間を指定することによって、 データが数文字のブロック単位で断続的に送られてくるときにすばやく反応でき、 データが連続的に送られてくるときも、 1回の呼び出しで大量のデータを収集できます。

書き込み操作のタイムアウトは、 フロー制御などによってブロック転送を行うときや、 SetCommBreak関数を呼び出して文字の転送が中断されているときなどに役立ちます。

間隔タイムアウトや総計タイムアウトの値と読み取り操作の要約を次の表に示します。

総計タイムアウト 間隔タイムアウト 動作

00 バッファがいっぱいになると戻ります。タイムアウトは使われません。

T0 バッファがいっぱいになるか、 操作が開始してからTミリ秒が経過すると戻ります。

0Y バッファがいっぱいになるか、 2文字の受け取りの間隔がYミリ秒以上になると戻ります。タイムアウトの測定は、 最初の文字を受け取るまで始まりません。

TY バッファがいっぱいになるか、 いずれかのタイムアウトが発生すると戻ります。

しかし、 タイムアウトの測定は、 物理デバイスを制御しているシステムに対して行われることに注意してください。モデムなどのリモート デバイスの場合、 タイムアウトの測定はモデムが接続されているサーバー システムに対して行われ、 ネットワークの伝播遅延などはタイムアウト時間には入りません。たとえば、 500ミリ秒の総計タイムアウトをクライアント アプリケーションが指定したとします。サーバーで500ミリ秒が経過すると、 タイムアウト エラーがクライアントに返されます。50ミリ秒のネットワーク伝播遅延があると、 実際にタイムアウトが発生してから約50ミリ秒経過してからクライアントにタイムアウトが通知されます。

タイムアウト パラメータは、 通信デバイスのオーバーラップ読み書き操作に影響します。オーバーラップI/Oの場合、 ReadFile関数、 WriteFile関数、 ReadFileEx関数、 WriteFileEx関数は、 操作が完了する前に戻る場合があります。タイムアウト パラメータによって、 操作が完了したかどうかを判断できます。

通信エラー

タイムアウトが発生していないときでも、 要求した文字数に達せずに読み書き操作が完了する場合があります。いくつかの例を次に示します。

一部のドライバは、 特殊文字の使用をサポートしています。特殊文字を受け取ると、 読み取り操作は完了し、 その時点までに読み取った文字だけが返されます。

PurgeComm関数を使って、 処理待ち中の読み書き操作を途中で終了させることができます。また、 この関数は、 入出力バッファの内容を削除します。

読み書き操作中に通信エラーが発生すると、 その通信リソースのI/O操作はすべて終了します。このようなエラーには、 ブレーク状態、 パリティ エラー、 フレーム エラーなどがあります。エラーが発生したら、 プロセスは、 次のI/O操作を開始する前にClearCommError関数を呼び出してエラー フラグをクリアしてください。ClearCommErrorは、 発生したエラーと、 デバイスの現在の状態を報告します。

通信イベント

プロセスは、 通信リソースで発生するイベントを監視できます。たとえば、 イベントを監視することによって、 CTS (clear-to-send) シグナルやDSR (data-set-ready) シグナルの状態の変化を判断できます。

特定の通信リソースのイベントを監視するには、 SetCommMask関数を使ってイベント マスクを作成してください。通信リソースの現在のイベント マスクを判断するには、 GetCommMask関数を使ってください。監視可能なイベントのリストを次の表に示します。

意味

EV_BREAK 入力でブレークが検出されました。

EV_CTSCTS (clear-to-send) シグナルの状態が変化しました。

EV_DSRDSR (data-set-ready) シグナルの状態が変化しました。

EV_ERR 回線状態 エラーが発生しました。回線状態エラーは、 CE_FRAME CE_OVERRUN CE_RXPARITYです。

EV_RING リング インジケータが検出されました。

EV_RLSDRLSD (receive-line-signal-detect) シグナルの状態が変化しました。

EV_RXCHAR 文字を受け取り、 入力バッファに置きました。

EV_RXFLAG イベント文字を受け取り、 入力バッファに置きました。デバイスのDCB構造体で指定したイベント文字は、 SetCommState関数を使ってシリアル ポートに適用されます。

EV_TXEMPTY 出力バッファの最後の文字が送られました。

監視するイベントのセットを指定したら、 WaitCommEvent関数を使って、 イベントのいずれかが発生するまで待機してください。WaitCommEventは、 同期的またはオーバーラップして実行できます。関数のオーバーラップ実行について詳しくは、 同期の概要を参照してください。

イベント マスクで指定したイベントのいずれかが発生すると、 プロセスの待機操作は終了し、 イベント マスク変数には、 検出されたイベントの種類を示す値が設定されます。待機操作中のリソースに対してSetCommMaskを呼び出すと、 WaitCommEventはエラーを返します。

WaitCommEventは、 待機操作が開始した後で発生したイベントしか検出しません。たとえば、 EV_RXCHARイベントを指定した場合、 ドライバが文字を受け取って入力バッファに置くと、 待機終了条件が成立します。しかし、 WaitCommEventを呼び出したときに入力バッファに文字があっても、 終了条件は成立しません。

シグナル (CTS DSRなど) の状態が変化したときに発生するイベントを監視する場合、 WaitCommEventは、 変化は報告しますが、 現在の状態は報告しません。CTS (clear-to-send) DSR (data-set-ready) RLSD (receive-line-signal-detect)、 リング インジケータなどのシグナルの状態を問い合わせるには、 GetCommModemStatus関数を使ってください。

拡張関数

通信関数には、 EscapeCommFunction関数を使って起動するものがあります。この関数は、 拡張関数の実行を指示するコードをデバイスに送ります。たとえば、 SETBREAKコードを送って文字転送を中断させ、 CLRBREAKコードで転送を再開させることができます (この操作は、 SetCommBreak関数とClearCommBreak関数を呼び出して実行することもできます)。また、 EscapeCommFunctionを使ってモデムを手動制御することもできます。たとえば、 CLRDTRコードとSETDTRコードを使って、 手動DTR (data-terminal-ready) フロー制御を実現できます。しかし、 デバイスのDTRハンドシェイクを使用可能に設定しているときにEscapeCommFunctionを使ってDTRラインを操作したり、 RTSハンドシェイクが使用可能になっているときにRTS (request-to-send) ラインを操作すると、 エラーが発生します。

DeviceIoControl関数によって、 指定したデバイス ドライバに拡張関数コードを送り、 特定の操作をデバイスに実行させることができます。DeviceIoControlは、 標準のシリアル通信関数ではサポートされていない機能を持つ通信リソースのデバイスに対するインターフェイスです。この関数によって、 デバイス固有のパラメータを使用するデバイスを構成したり、 デバイス固有の関数を実行できます。

通信関数の使用

このトピックでは、 次に示す処理の実行方法を説明します。

通信リソースの構成

通信イベントの監視

通信リソースの構成

次に示す例は、 COM1のハンドルをオープンして、 現在の構成をDCB構造体に設定します。その後、 DCB構造体を修正し、 その構造体を使ってデバイスを再構成します。

DCB dcb;
HANDLE hCom;
DWORD dwError;
BOOL fSuccess;

hCom = CreateFile("COM1",
GENERIC_READ | GENERIC_WRITE,
0,/* comm devices must be opened with exclusive access */
NULL, /* no security attrs */
OPEN_EXISTING, // comm devices must use OPEN_EXISTING
0,/* not overlapped I/O */
NULL/* hTemplate must be NULL for comm devices */
);

if (hCom == INVALID_HANDLE_VALUE) {
dwError = GetLastError();

/* handle error */
}

/*
* Omit the call to SetupComm to use the default queue sizes.
* Get the current configuration.
*/

fSuccess = GetCommState(hCom, &dcb);

if (!fSuccess) {
/* handle error *
}

/* Fill in the DCB: baud=9600, 8 data bits, no parity, 1 stop bit */

dcb.BaudRate = 9600;
dcb.ByteSize = 8;
dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT;

fSuccess = SetCommState(hCom, &dcb);

if (!fSuccess) {
/* handle error */
}

通信イベントの監視

次に示すコード例は、 シリアル ポートをオーバーラップI/O用にオープンして、 CTSシグナルとDSRシグナルを監視するためのイベントマスクを作成し、 イベントが発生するのを待ちます。待機中にプロセスのほかのスレッドがI/O操作を実行できないようにするため、 WaitCommEvent関数を使います。

HANDLE hCom;
OVERLAPPED o;
BOOL fSuccess;
DWORD dwEvtMask;

hCom = CreateFile("COM1",
GENERIC_READ | GENERIC_WRITE,
0, /* exclusive access */
NULL, /* no security attrs */
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL
);

if (hCom == INVALID_HANDLE_VALUE) {
/* Deal with the error. */
}

/* Set the event mask. */

fSuccess = SetCommMask(hCom, EV_CTS | EV_DSR);

if (!fSuccess) {
/* deal with error */
}

/* Create an event object for use in WaitCommEvent. */

o.hEvent = CreateEvent(NULL, /* no security attributes */
FALSE, /* auto reset event */
FALSE, /* not signalled */
NULL /* no name */
);

assert(o.hEvent);

if (WaitCommEvent(hCom, &dwEvtMask, &o)) {
if (dwEvtMask & EV_DSR) {
/*
* . . .
*/
}

if (dwEvtMask & EV_CTS) {
/*
* . . .
*/
}
}

通信関数

通信デバイスに関する関数を次に示します。

BuildCommDCB

BuildCommDCBAndTimeouts

ClearCommBreak

ClearCommError

EscapeCommFunction

GetCommMask

GetCommModemStatus

GetCommProperties

GetCommState

GetCommTimeouts

PurgeComm

SetCommBreak

SetCommMask

SetCommState

SetCommTimeouts

SetupComm

TransmitCommChar

WaitCommEvent

▲ページトップに戻る

【本を読んでもよくらからない】 … 個別指導でわかりやすくお教えします