. 파일 저장

다음은 파일 저장 함수를 작성해보자. 이 함수들도 재사용성을 위해 세 개의 함수로 기능이 분할되어 있다.

 

BOOL Save(HWND hChild)

{

     SInfo *pSi=(SInfo *)GetWindowLong(hChild,0);

 

     if (strncmp(pSi->NowFile,"이름없음",8)==0) {

          return SaveAs(hChild);

     } else {

          if (pSi->Ae.GetModified() == FALSE) {

              return TRUE;

          }

          return SaveToFile(hChild,pSi->NowFile);

     }

}

 

BOOL SaveAs(HWND hChild)

{

     OPENFILENAME OFN;

     TCHAR lpstrFile[MAX_PATH]="";

 

     SInfo *pSi=(SInfo *)GetWindowLong(hChild,0);

     memset(&OFN, 0, sizeof(OPENFILENAME));

     OFN.lStructSize = sizeof(OPENFILENAME);

     OFN.hwndOwner=g_hFrameWnd;

     OFN.lpstrFilter="모든 파일(*.*)\0*.*\0텍스트 파일\0*.txt\0";

     OFN.lpstrFile=lpstrFile;

     OFN.nMaxFile=256;

     OFN.lpstrDefExt="txt";

     if (GetSaveFileName(&OFN)==FALSE) {

          return FALSE;

     }

 

     if (_access(OFN.lpstrFile,0)==0) {

          if (MessageBox(g_hFrameWnd, "파일이 이미 있습니다. 덮어쓰시겠습니까?",

              "알림",MB_YESNO)==IDNO)

              return FALSE;

     }

 

     if (SaveToFile(hChild,OFN.lpstrFile) == FALSE) {

          return FALSE;

     }

 

     lstrcpy(pSi->NowFile,OFN.lpstrFile);

     SetWindowText(hChild,pSi->NowFile);

     return TRUE;

}

 

BOOL SaveToFile(HWND hChild,TCHAR *Path)

{

     HANDLE hFile;

     DWORD dwWritten;

     int len;

     TCHAR *TextBuf;

     SInfo *pSi=(SInfo *)GetWindowLong(hChild,0);

     DWORD err;

     TCHAR Mes[512];

 

     hFile=CreateFile(Path,GENERIC_WRITE,0,NULL,

          CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);

     if (hFile==INVALID_HANDLE_VALUE) {

          err=GetLastError();

          switch (err)

          {

          case ERROR_ACCESS_DENIED:

              wsprintf(Mes,"%s 파일을 생성할 수 없습니다. 읽기전용 등의 이유로 "

                   "액세스가 금지되어 있습니다.",Path);

              break;

          case ERROR_SHARING_VIOLATION:

              wsprintf(Mes,"%s 파일을 다른 프로그램이 사용하고 있습니다.",Path);

              break;

          case ERROR_WRITE_PROTECT:

              wsprintf(Mes,"기록 금지된 장치입니다.");

              break;

          default:

              wsprintf(Mes,"알수 없는 에러입니다. 개발자에게 문의하지 마시고 "

                   "알아서 해결하십시오. 에러 코드=%d",err);

              break;

          }

          MessageBox(g_hFrameWnd, Mes,"알림",MB_OK);

          return FALSE;

     }

 

     len=pSi->Ae.GetTextLength();

     TextBuf=(TCHAR *)malloc(len+1);

     pSi->Ae.GetText(TextBuf,len+1);

     WriteFile(hFile,TextBuf,len,&dwWritten,NULL);

     CloseHandle(hFile);

     free(TextBuf);

     pSi->Ae.SetModified(FALSE);

     return TRUE;

}

 

Save 함수는 파일/저장 메뉴항목에 대응되는 함수인데 ConfirmSave 함수에서 이 함수의 결과를 참조하기 때문에 BOOL형을 리턴한다. 이 함수는 새로 만든 파일인지 디스크에서 읽은 파일인지에 따라 SaveAs 또는 SaveToFile 함수를 호출한다. 세 함수가 파일을 저장하는 과정을 순서도로 그려보면 다음과 같다.

, 이런걸 순서도까지 그려가며 생각해야 하느냐고 할지도 모르겠다. 하지만 ConfirmSave 함수에서 Save를 호출하기 때문에 단순히 기능을 구현하는 것뿐만 아니라 모든 경우에 대해 리턴값이 정확하게 전달될 수 있도록 설계하는데 다소 어려움이 있었다. 파일은 메모리 외부에 있기 때문에 입출력 과정에서 에러가 발생할 소지가 아주 높으며 그래서 모든 입출력함수는 철저하게 에러 처리를 하고 호출측으로 그 결과를 제대로 리턴해야 한다.

SaveAs 함수는 hChild에 편집중인 파일을 새 이름으로 저장한다. 파일 저장 공통 대화상자를 열어 파일명을 입력받으며 이 이름으로 SaveToFile 함수를 호출한다. 사용자가 저장을 취소하거나 이미 파일이 있으며 덮어쓰지 않을 경우는 FALSE를 리턴한다. 파일을 저장한 후 차일드의 NowFile을 변경하며 타이틀바에 파일명을 출력한다.

SaveToFile 함수는 hChild에 편집중인 문서를 Path 파일명으로 저장한다. Ae.GetTextLength로 문서의 길이를 조사하고 이 길이에 널 종료문자 길이를 더한 만큼 메모리를 할당하였다. 그리고 GetText 함수로 문서 내용을 읽어 파일로 출력한다. 파일을 저장했으므로 변경플래그는 FALSE가 된다.

참고로 Save 함수는 NowFile의 첫 8바이트가 이름없음이면 저장되지 않은 파일이라고 간주하는데 이것이 다소 위험한 비교같아 보일 수도 있다. New 함수에 의해 만들어진 파일은 이름없음 n이라는 기본 파일명을 가지므로 뒤의 일련번호는 빼고 첫 8바이트만 비교해보고 저장된 파일인지 아닌지를 판단한다. 그런데 만약 사용자가 이름없음이 아님.txt라는 이름으로 파일을 만들었다면 이 파일도 과연 저장되지 않은 파일로 간주되지 않겠는가? 하지만 그럴 위험은 없다. 이 비교가 왜 안전한지는 퀴즈로 남겨 놓기로 한다. 디버깅을 해보면 쉽게 알 수 있을 것이다.