OVERLAPPED 구조체를 이용한 비동기 쓰기

 

시리얼 COM 포트 통신을 이용해 데이터를 읽고 쓰는 방법에 대해 알아보자. 이미 이전포스트에서 주욱 설명해 오고 있는것과 마찬가지로 비동기(Overlapped) 읽기, 쓰기에 대해 다룰 것인데 먼저 쓰기부터 알아보려 한다. 왜냐하면 읽기가 좀더 어렵기 때문이다. ㅎㅎ

 

쓰기는 내가 데이터를 모두 가지고 있고 어디부터 어디까지 언제 쓸 것인지를 선택 할 수 있다. 그러나 쓰기에 비해 읽기는 언제 상대방이 나에게 데이터를 전송할지 알 수 없다. 따라서 상대방이 데이터를 전송하여 내가 읽을만한 데이터가 읽기 버퍼에 들어오는 이벤트를 감지하는 쓰레드를 만들어 돌려야 하기때문에 상대적인 난이도가 쓰기에 비해 높다.

 

어찌됐든, 쓰기는 아래와 같은 WriteFile함수를 이용한다.

 

 BOOL WINAPI WriteFile(
  __in         HANDLE hFile,
  __in         LPCVOID lpBuffer,
  __in         DWORD nNumberOfBytesToWrite,
  __out_opt    LPDWORD lpNumberOfBytesWritten,
  __inout_opt  LPOVERLAPPED lpOverlapped
);

 

쓰기가 성공하면 TRUE를 반환하고, 지금 설명하고 있는 비동기 쓰기의 경우에는 무조건 FALSE를 반환한다. 그러나 GetLastError()를 통해 얻어낸 에러가 ERROR_IO_PENDING이면 함수가 실패 한 것은 아니다.

 

인자를 설명 하자면 처음부터, 파일디스크립터 (핸들), 쓸 데이터가 들어있는 버퍼, 몇바이트를 쓸지, 실제로 쓴 바이트 수, OVERLAPPED 구조체의 주소 이다.

 

이전전 포스트에서 설명한 OVERLAPPED가 여기서 쓰이는 것이다.

백문이 불여일견이니 1바이트를 시리얼 통신으로 쓰는 함수를 살펴보자

 

 

BOOL CSerialz::WriteCommByte(unsigned char ucByte)
{
 BOOL bWriteStat;
 DWORD dwBytesWritten;

 

//1바이트를 쓰고 다 쓰면 m_OverlappedWirte.hEvent를 set시킨다. 

bWriteStat = WriteFile(m_hIDComDev,

                              (LPSTR) &ucByte,

                              1,

                              &dwBytesWritten,

                              &m_OverlappedWrite);  

 

//overlapped write는 FALSE를 바로 반환하고 LASTERROR가 ERROR_IO_PENDING

if(!bWriteStat && (GetLastError() == ERROR_IO_PENDING))
{
      if(WaitForSingleObject(m_OverlappedWrite.hEvent, 1000))
          dwBytesWritten = 0;
      else
     {
          GetOverlappedResult(m_hIDComDev,

                                        &m_OverlappedWrite,

                                        &dwBytesWritten,

                                        FALSE);

       

           //사실 이 부분은 잘못된 예이다. 시리얼 통신을 비롯한 모든

           //통신 디바이스 에서는 overlapped.offset이 0으로 설정되어야 한다. 빼도좋다.
           m_OverlappedWrite.Offset += dwBytesWritten;   

      }
 }

 

//의미있는 반환값을 가지려면 쓰기성공한 바이트 수가 0 이상인지 체크해야 한다.

 return(TRUE);

 

 

위 코드에서 쓰인 GetOverlappedResult함수는 write 명령어를 수행할 때 넣었던 OVERLAPPED 구조체를 인수로 하여 해당 write명령어의 결과를 얻을 수 있게 해주는 함수이다.

 

 BOOL WINAPI GetOverlappedResult(
  _In_   HANDLE hFile,
  _In_   LPOVERLAPPED lpOverlapped,
  _Out_  LPDWORD lpNumberOfBytesTransferred,
  _In_   BOOL bWait
);

 

순서별로 인수는 COM 포트 파일핸들, OVERLAPPED 구조체 포인터, 더블워드(4바이트) 값으로 실제로 쓰기 된 바이트 수를 받을 변수에 대한 포인터, 그리고 write가 종료되기까지 기다릴지 말지에 대한 결정을 위한 BOOL 변수이다.

 

마지막 변수 bWait는 아래와 같은 규칙으로 쓰인다.

 

 

1. bWait == TRUE  && OVERLAPPED.internal == STATUS_PENDING

 

   bWait가 TRUE인 경우에 미리 COMMTIMEOUTS에 설정한 타임아웃 값

   만큼 기다린다. 기다리는 도중 완료되면 TRUE 반환 TIMEOUT되면 FALSE 반환

   GetLastError() 함수는 ERROR_IO_INCOMPLETE 반환

 

2. bWait == FALSE : && OVERLAPPED.internal == STATUS_PENDING

 

   bWait가 FALSE인 경우에 완료되길 기다리지 않고 바로 쓰기한 바이트를 넣고 반환된다.

   아직 쓰기작업이 끝나지 않았다면 FALSE를 반환하고 GetLastError() 함수는

   ERROR_IO_INCOMPLETE 반환

 

저 쓰기 구조를 요약해 보자면 아래와 같다.  

 

1. Overlapped로 비동기 write명령을 호출한다.

2. write명령어의 반환이 FALSE이고 GetLastError()의 반환이 ERROR_IO_PENDING이다.

3. write명령어에 쓰인 OVERLAPPED의 이벤트가 SET되길 1000ms 동안 기다린다.

   (참고로 wrtie명령어를 호출 할 때 OVERLAPPED의 이벤트 핸들은 자동으로 RESET됨)

4. write작업이 완료되고 이벤트가 SET되어 다음 단계로 넘어간다.

5. GetOverlappedResult를 이용하여 실제로 쓰기된 바이트 수를 얻는다.

6. 불필요한 통신 IO 이용 파일 핸들에서 OVERLAPPED의 offset 재조정

 

마지막으로 6번이 중요한데 위 코드에서 빨간색으로 색을 칠한 구문은 지난번 포스트에서도 언급한바와 같이 파일핸들이 열고 있는 IO Device가 통신 디바이스 일때는 사실상 쓸 필요도 없거니와 써서도 안된다.

 

어쨋든 요런식으로 1byte를 시리얼 통신으로 쓸 수 있으며, 이 1바이트 쓰기를 반복하여 d아래와 같이 원하는 데이터를 전송 할 수가 있다.

 

// 버퍼로부터 설정된 데이터를 사이즈만큼 쓴다.

int CSerialz::SendData(char *buffer, int size) 

{

    // port가 제대로 설정되어 있지 않을 때 - 쓰기 실패
    if(!m_bPortOnOff || m_hIDComDev == NULL) return(0); 

 

    DWORD dwBytesWritten = 0;

    for(int i=0; i<size; i++)
    {  

        // 여러 바이트를 쓰기 위하여 한 바이트씩 쓰는 함수를 중복 호출
        WriteCommByte(buffer[i]); // 쓰기 위한 함수 호출

 

        //요부분에 writeCommByte의 TRUE FALSE를 분간하는 코드가 있음좋다.
        dwBytesWritten++;
    }

    return((int)dwBytesWritten);  // 쓰기에 성공한 바이트 수 리턴

코드는 지금 공부하고 있는 코드를 복사해서 올리다 보니깐 좀 틀린 부분이 있긴 하지만

일단은 틀린 부분과 추가해야 할 사항들을 추가 하기위해 틀린 코드도 그대로 올리고 있으니 참고하여 코드를 봐야 한다.

 

 

Posted by 굿쟌
,