シリアルポート(COMポート)の操作方法
その3 非同期通信の巻




今回こそは非同期の説明をしようかなと思ったのですが、やる気がなくなる事実が 判明しました。
それは、COMポートに対しての非同期I/O操作はWinNTでしか使えない のです。Win95/98ではエラーになってしまうのです。
ReadFile()のマニュアルにはWin95の注釈として、ディスク上のファイルに 対しては非同期できませんよ、と書いてあるのですがCOMポートが駄目だとは 思いませんでした。
といっても、パイプとかはWin95でも大丈夫と思うので、参考程度には なると思います。


関数や戻り値の説明は?マークのついたコメントを見れば分かるでしょう。
1.読み込み処理だ。
(File:SimReadThread.cpp)
UINT SimReadThread( 
        LPVOID pParam                       // [IN]  未使用
        )
{
    BOOL        Bool_Ret;                   // 関数戻り値
    ULONG       Read_Size;                  // 読み込みバイト数
    static char	ReadBuff[10];               // 読み込みバッファ
    OVERLAPPED  osReader = {0};             // 非同期読み込み用構造体
    BOOL        fWaitingOnRead = FALSE;     // 非同期中メッセージ待ち状態
    BOOL        Loop_End = FALSE;           // 処理終了状態
    DWORD       dwRes;                      // 非同期読み込み完了待ち関数戻り値



    //-----------------------------------------------------------------------------
    /// 端末からの電文受信処理
    //-----------------------------------------------------------------------------

    // 電文受信待ちループ
    while( !Loop_End )
    {
        // 非同期処理の割り込み用イベントの生成
        // ?よく分からないがこのまま記述すること
        osReader.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );

        // イベント生成失敗
        if( osReader.hEvent == NULL )
        {
            // エラーメッセージ表示
            SimErrMsg( "%s[%d] 受信用イベント生成失敗(%d)",
                       0, __file__, __line__, GetLastError() );

            // ループ終了フラグON
            Loop_End = TRUE;
            break;
        }
		
        // 読み込みバッファ初期化
        memset( ReadBuff,NULL,sizeof(ReadBuff) );

        // 1バイトずつ読み込み
        // ?ここでは1バイト読み込めたかどうかに関わらず、ReadFile()関数はすぐに
        // ?リターンします。
        Bool_Ret = ReadFile( E_ComPort, ReadBuff, 1, NULL, &osReader );
        // 読み込み結果チェック
        // ?1バイト読み込めればTRUEが、読み込み完了待ちまたはエラーの時は
        // ?FALSEが返ります。
        if( Bool_Ret == FALSE )
        {
            // 読み込みエラー
            // ?読み込み完了待ちの時はGetLastError()関数の戻りにERROR_IO_PENDINGが
            // ?返されます。それ以外の場合は本当にエラーです。
            if( GetLastError() != ERROR_IO_PENDING )
            {
                // エラーメッセージ表示
                SimErrMsg( "%s[%d] 受信失敗(%d)",
                           0, __file__, __line__, GetLastError() );

                // ループ終了フラグON
                Loop_End = TRUE;
                break;
            }
            else
            {
                // 非同期中メッセージ待ち状態をONにする
                fWaitingOnRead = TRUE;
            }
        }
        else
        {
            // 読み込みOK
            // バッファリング処理
            Bool_Ret = SimReadSuccess( (char)ReadBuff[0] );

            // バッファリング処理失敗
            if( Bool_Ret != TRUE )
            {
                // エラーメッセージ表示
                SimErrMsg( "%s[%d] バッファリング処理失敗(%d)",
                           0, __file__, __line__, GetLastError() );
            }
        }

        // 非同期読み込み完了までループ
        while( fWaitingOnRead )
        {
            // ReadFile関数の非同期読み込み完了待ち
            // タイムアウトは500ミリ秒
            // ?ここでようやく、非同期モードの本領発揮です。WaitForSingleObject()関数は
            // ?読み込み完了、読み込みエラー発生、タイムアウトのどれかが発生すると
            // ?リターンします。
            dwRes = WaitForSingleObject( osReader.hEvent, 500 );
            switch( dwRes )        // 戻り値で分岐
            {
                case WAIT_OBJECT_0:	// イベント発生
                    // イベントが正常がチェックする
                    // ?読み込み完了と読み込みエラーのリターンコードが同じなので
                    // ?正常終了したかどうかチェックします。
                    if( !GetOverlappedResult( E_ComPort, &osReader, &Read_Size, FALSE ))
                    {
                        // 読み込み中に回線断が発生した場合はここを通ります

                        // エラーメッセージ表示
                        SimErrMsg( "%s[%d] 受信失敗(%d)",
                                   0, __file__, __line__, GetLastError() );

                        Loop_End = TRUE;
                    }
                    else
                    {
                        // バッファリング処理
                        Bool_Ret = SimReadSuccess( (char)ReadBuff[0] );

                        // バッファリング処理失敗
                        if( Bool_Ret != TRUE )
                        {
                            // エラーメッセージ表示
                            SimErrMsg( "%s[%d] バッファリング処理失敗(%d)",
                                       0, __file__, __line__, GetLastError() );
                        }
                    }

                    // 非同期中メッセージ待ち状態をOFFにする
                    fWaitingOnRead = FALSE;
                    break;

                case WAIT_TIMEOUT:  // タイムアウト
                    // ?無通信チェックもできると思います。
                    break;
                default:
                    break;
            }
        }
    }

    // スレッド終了
    return(0);
}


2.書き込み処理だ。
(File:COMMwrite.cpp)
BOOL COMMwrite( 
               char *WriteMsg,      // [IN] 送信電文
               UINT WriteLength     // [IN] 送信電文長
              )
{
    OVERLAPPED  osWrite = {0};              // 非同期通信用OVERLAPPED構造体
    DWORD       dwWritten;                  // 書き込みバイト数
    BOOL        fRes;                       // 内部リターンコード
    BOOL        Bool_Ret;                   // 関数戻り値
    long        Ret;                        // 関数戻り値




    //-----------------------------------------------------------------------------
    /// 非同期通信用の処理完了イベントを設定
    //-----------------------------------------------------------------------------

    // 非同期通信用の処理完了イベントを設定
    // ?読み込みと同じでそのまま記述して下さい。
    osWrite.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL );

    // イベント設定に失敗
    if( osWrite.hEvent == NULL )
    {
        // 内部ログの書き込み
        (long)(*ZZerrlog_VC)( E_Socket, NHK_COM_COMM, "%s %s %d %s(%s:%d)", 0,
                PRCNAM_COM, __file__, __line__, D_LOG_MSG08, "SetTimer", GetLastError() );

        // エラーでリターンする
        return( FALSE );
    }



    //-----------------------------------------------------------------------------
    /// 送出TCSに電文を送信する
    //-----------------------------------------------------------------------------

    // TCSに送信電文を送信する
    // ?WriteFile()関数は書き込みが完了したかどうかに関わらず、すぐにリターンします。
    if( !WriteFile( Com2Port, WriteMsg, WriteLength, &dwWritten, &osWrite))
    {
        // エラーチェック
        // ?書き込み完了待ちならば、GetLastError()の戻りがERROR_IO_PENDINGになります。
        // ?それ以外は書き込みエラーです
        if( GetLastError() != ERROR_IO_PENDING )
        {
            // ライト完了待ち以外のエラー

            // 内部ログの書き込み
            (long)(*ZZerrlog_VC)( E_Socket, NHK_COM_COMM, "%s %s %d %s(%s:%d)", 0,
                    PRCNAM_COM, __file__, __line__, D_LOG_MSG08, "SetTimer", GetLastError() );

            // 内部リターンコードをエラーにする
            fRes = FALSE;
        }
        else
        {
            // ?読み込み時と同じようにイベントの発生待ちを行います
            // ?タイマ秒数は外部変数です
            fRes = WaitForSingleObject( osWrite.hEvent, E_Wri_TimMax );
            switch( fRes )
            {
            case WAIT_OBJECT_0:

                // ?書き込み完了と読み込みエラーのリターンコードが同じなので
                // ?正常終了したかどうかチェックします。
                if( !GetOverlappedResult( Com2Port, &osWrite, &dwWritten, FALSE ) )
                {
                    fRes = FALSE;
                }
                else
                {
                    fRes = TRUE;
                }
                break;
            default:
                // ?ここを通る場合はまず、書き込みタイムアウトの場合でしょう
                // ?COM通信は相手がいなくても、書き込みでエラーにならないし、
                // ?回線が切れていようが、分からないので、ここのタイムアウトにより、
                // ?エラーと判断しています。
                fRes = FALSE;
            }
            // ライト完了待ち
            // ?昔の処理で、タイムアウト値が無限大になっています。
            // ?このバージョンだと、正常に回線断を検出できませんし、
            // ?結局、GetOverlappedResult()関数でWAITになってしまい
            // ?非同期にした意味がありません。
//            if( !GetOverlappedResult( Com2Port, &osWrite, &dwWritten, INFINITE ) )
            if( fRes == FALSE )
            {
                // ライト異常終了

                // 内部ログの書き込み
                (long)(*ZZerrlog_VC)( E_Socket, NHK_COM_COMM, "%s %s %d %s(%s:%d)", 0,
                        PRCNAM_COM, __file__, __line__, D_LOG_MSG08, "GetOverlappedResult", GetLastError() );

                // 内部リターンコードをエラーにする
                fRes = FALSE;
            }
            else
            {
                // ライト正常終了

                // 内部リターンコードを正常にする
                fRes = TRUE;
            }
        }
    }
    else
    {
        // Writeが正常に完了した

        // 内部リターンコードを正常にする
        fRes = TRUE;
    }



    //-----------------------------------------------------------------------------
    /// 電文送信エラー処理
    //-----------------------------------------------------------------------------

    // ライト失敗なら回線を切断する
    if( fRes == FALSE )
    {
        // 回線を切断する
        Bool_Ret = COMMcommclose( );

        if( Bool_Ret != TRUE )
        {
            // 内部ログの書き込み
            (long)(*ZZerrlog_VC)( E_Socket, NHK_COM_COMM, "%s %s %d %s(%s:%d)", 0,
                    PRCNAM_COM, __file__, __line__, D_LOG_MSG09, "COMMcommclose", GetLastError() );
        }

        // 再オープン用タイマを設定する
        Ret = SetTimer( E_WinHdl_MF, D_TIMER_OPEN, E_Opn_TimMax, NULL );
        if( Ret == 0 )
        {
            // 内部ログの書き込み
            (long)(*ZZerrlog_VC)( E_Socket, NHK_COM_COMM, "%s %s %d %s(%s:%d)", 0,
                    PRCNAM_COM, __file__, __line__, D_LOG_MSG08, "SetTimer", GetLastError() );
        }
    }

    // リターンする
    return( fRes );
}




N総研ソフトウェア開発部のTOPに戻る。

このページに関するご意見・質問は
ドンタコスN村 E-mail:wnaka@coco.ned.co.jp
まで送信してください。