. 변경 감시 및 다시 읽기

다음은 프로그램이 다시 활성화될 때의 처리를 보자. pEditings 배열을 처음부터 순회하며 각 차일드가 편집하고 있는 파일의 크기와 시간을 조사해보고 기록해놓은 것과 조금이라도 달라진 것이 있는지 본다. 파일이 수정되면 보통 크기가 달라지므로 크기만 비교해도 수정했는지 아닌지를 금방 알 수 있다. 하지만 똑같은 길이의 문자열을 대체할 경우는 크기만으로 정확하게 알 수 없으므로 파일의 수정 시간도 비교해보도록 했다.

변경된 파일이 발견되면 사용자에게 다시 읽어올 것인지 질문하는데 이 질문은 사실 사용자의 대답을 듣기 위한 것이라기 보다는 파일이 변경되었다는 것을 확실하게 알려주는 역할을 한다. 사용자가 이 질문에 예라고 대답하면 다른 프로그램에 의해 수정된 파일을 다시 읽어온다. 디스크의 파일을 다시 읽어오는 RevertFile이라는 함수가 이미 만들어져 있으므로 이 함수만 불러주면 된다. 모든 파일에 대한 변경을 확인할 때까지 이 과정을 반복한다.

이 코드에서 한 가지 주의할 점이 있다. 파일의 현재 크기와 날짜를 조사하기 위해 일단 이 파일을 열어야 하는데 이때 늘 하던 방식대로 GENERIC_READ 권한으로 열어서는 안된다. 파일의 정보를 조사하는 것이니까 읽기 권한을 요청해야 한다고 생각하겠지만 이때는 파일의 내용을 읽고자 하는 것이 아니라 파일의 정보를 조사하고자 하는 것이므로 이 플래그는 과다한 권한을 요청하고 있는 것이다. 파일뿐만 아니라 권한이 있는 객체를 다루는 코드는 항상 필요한 최소한의 권한만 요청해야 하는데 이를 최소 권한 요청의 원칙이라고 한다.

만약 다른 프로그램이 이 파일을 읽기 공유없이 열어 놓았다면 파일열기에 실패하게 되고 정보를 조사할 수 없게 된다. 설사 파일열기에 성공했고 변경되었음을 무사히 확인했다 하더라도 문제는 있다. 변경된 파일을 다시 읽어와야 하는데 조사 함수가 이미 파일을 읽기 공유없이 열었기 때문에 RevertFile 함수가 파일을 열지 못하는 상태가 되고 만다. 그래서 파일에 대한 권한을 아예 0으로 줘야 한다. 이 권한은 파일에 대한 정보만 조사하겠다는 뜻이다.

변경 확인이 끝난 후 pEditings 배열은 확실하게 지워야 한다. 이 배열은 프로그램이 비활성활 때와 다음 활성화될 때의 통신을 위해서만 필요하므로 활성화되었으면 더 이상 필요가 없다. 배열을 삭제한 후 pEditings NULL을 대입하여 확실히 지웠다는 것을 표시했다. 이 배열은 OnDestroy에서 다시 한 번 더 해제해야 한다.

 

void OnDestroy(HWND hWnd,WPARAM wParam,LPARAM lParam)

{

     ....

     if (pEditings) {

          free(pEditings);

     }

}

 

비활성화된 후 외부에서 이 프로그램을 종료한다거나 할 때를 위해 확실하게 메모리를 해제하도록 했다. 또한 한 번도 비활성화되지 않았다면 pEditings가 아예 할당되어 있지 않을 수도 있으므로 할당 여부를 반드시 확인한 후 해제해야 한다. 테스트해보면 다른 프로그램이 자신의 파일을 수정했을 때 정확하게 이를 사용자에게 알려줄 것이다.

변경 감시 알고리즘에 한 가지 문제가 있는데 WM_ACTIVATEAPP 메시지는 응용 프로그램이 활성화되었다는 통지 메시지인데도 불구하고 메시지박스를 연다는 점이 문제다. 이 메시지는 키보드나 작업 관리자 또는 태스크 바의 아이콘을 눌러 당근을 선택할 때 보내지는데 이럴 때는 별 문제가 없다. 하지만 마우스로 작업영역을 클릭할 때가 문제가 되는데 이때 메시지의 순서는 WM_ACTIVATEAPP 다음에 WM_LBUTTONDOWN 순서이다.

당근이 활성화될 때 메시지박스를 열어 버렸고 사용자는 메시지박스의 확인 버튼을 클릭하기 위해 마우스를 클릭하므로 WM_LBUTTONDOWN 다음에 이어지는 WM_LBUTTONUP이 전달되지 않는다. 그래서 커서는 캡처된 상태로 계속 남게 되며 마우스 버튼이 눌러져 있지도 않은데 선택영역이 계속 확장되는 것이다. 그래서 이 문제를 다음과 같이 해결하였다.

 

void CApiEdit::OnLButtonDown(HWND hWnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)

{

     ....

     POINT pt;

 

     GetCursorPos(&pt);

     ScreenToClient(hWnd,&pt);

     if (pt.x != x || pt.y != y) {

          return;

     }

 

ApiEdit OnLButtonDown에서 커서의 현재 위치와 실제로 전달된 마우스 위치가 다르면 아무 일도 하지 않도록 했다. 이 조건으로 마우스 메시지가 실시간으로 전달된 것인지 아니면 다른 통지 메시지를 처리한 후 전달된 것인지를 판단하도록 했는데 일단은 큰 문제없이 잘 동작하지만 별로 좋은 모양은 아니다. 이 문제를 해결하기 위해 여러 가지 방법을 시도해 봤는데 제대로 되지 않아 이런 꽁수 미봉책을 쓸 수밖에 없었으며 다음에 좀 다른 구조로 개선할 필요가 있는 것 같다.