7.도큐먼트와 파일 입출력

본장에서는 템플레이트를 구정하는 하나의 클래스인 CDocument 에 대한 전반적인 설명을합니다. AppWizard를 이용하여 프로젝트를 만들면 파일 메뉴가 설정되고 □열기□,□저장□ 등의 항목이 설정됩니다. 이 항목과 연계하여 파일에 데이터를 저장하고 데이터를 읽는 방법을 Serialize함수를 사용하는 방법과 OnOpenDocument와 OnSaveDocument를 사용하는 방법을 설명합니다. 또한 CDocument의 기능을 이용하지 않고 직접 파일을 열고 데이터를 저장하기 위한 SDK파일 입출력 함수와  MFC의 CFile클래스에 대해서도 설명합니다.

본장을 통해서 파일에 데이터를 기록하는 전반적인 방법을 알수 있을것입니다.

대상자: 초급,중급,고급

Chapter 등급: 필수

공부방법: 본 Chapter내용은 이해하기가 쉽습니다. 꼭 이해하시기를 바랍니다.

1.CDocument 클래스

지금까지는 메인프레임, View 클래스에서 프로그래밍하는 부분들을 전반적으로 설명하였습니다. 템플레이트를 구성하는 4개의 클래스중에서 데이터를 저장하거나 로드하는 클래스가 도큐먼트 클래스입니다. 이도큐먼트 클래스가 CDocument입니다. CDocument는 템플레이트를 구성하는데 필수적인 클래스이며 이클래스 때문에 템플레이트가 구성된다고 하여도 과언이 아닐것입니다. 프로그램을 작성할때는 두가지 부분으로 크게 나뉘어 집니다. 필요한 정보 데이터와 이 데이터를 사용자가 쉽게 분석할수 있는 화면처리 이두개의 부분에서 도큐먼트 클래스는 필요한 정보 데이터를 파일에 저장하거나 로드하는 부분을 담당하게 되는 것입니다.

본항목에서는 CDocument클래스의 기능에 대한 전반적인 내용을 설명하겠습니다.

<1>도큐먼트 클래스에서 파일 입출력

AppWizard를 이용하여 프로젝트를 만들게 되면 그림 1-7-1과 같은 형태로 데이터가 도큐먼트에 저장이되고 도큐먼트에서 로드됩니다.

(그림 파일)

(그림 1-7-1)파일 입출력 전체 구성도

AppWizard를 이용하여 프로젝트를 만들면 파일 메뉴에서 열기와 저장 메뉴가 설정됩니다. 이때 열기 항목을 선택하면 자동적으로 파일 대화상자가 실행되고 이때 파일명을 선택할수 있습니다. 파일 대화상자가 실행되는 부분까지가 그림 1-7-1의 ①과 ② 부분입니다. 메뉴에서 열기 항목을 선택하면 CWinApp::OnFileOpen함수가 실행됩니다. 만일 여러분이 ID_FILE_OPEN 메뉴 항목을 선택하였을 때 수행되는 함수를 메인 프레임이나 또는 View 클래스등 다른 부분에 설정을 하게 되면  CWinApp의 OnFileOpen함수가 실행되지 않고 이렇게 되면 그림 1-7-1의 ③항목과 ④항목이 실행되지 않습니다. OnFileOpen에 의해서 파일 대화상자가 수행되고 사용자가 파일을 선택하면 이 파일명을 도큐먼트 클래스에 넘겨주면서 OnOpenDocument 함수가 실행됩니다. 이함수의 인자인 lpszPathName 에 파일 대화상자에서 선택한 파일명이 선택됩니다.  OnOpenDocument함수가 실행되면 도큐먼트안에 현재 데이터 파일명과 전체 경로명이 설정되며 CFile클래스를 이용하여 해당 파일을 오픈 합니다. 파일을 오픈하고 CArchive 클래스를 이용하여 이파일과 메모리간의 데이터 연결통로를 만든다음 도큐먼트 클래스이 Serialize 함수가 실행됩니다. Serialize함수에서는 리다이렉션을 이용하여 파일에 있는 데이터를 바로 메모리로 읽을수 있으며 또한 메모리에 있는 데이터를 바로 파일에 저장할 수가 있습니다.  파일 메뉴의 저장 항목을 선택하여 현재 데이터를 저장하고자 할 때 는 도큐먼트이 OnSaveDocument가 실행됩니다. 만일 현재도큐먼트에 파일명이 저장되어 있지 않을경우에는 파일 대화상자가 실행되어서 저장할 파일명을 얻은다음 이 파일명에 의해서 OnSaveDocument가 실행되고 이함수에서는 CFile클래스를 이용하여 해당 파일을 오픈한다음 CArchive클래스를 이용하여 통로를 만들고 데이터를 저장할수 있도록 하는것입니다. 그림 1-7-1의  ⑤,⑥,⑦ 항목이 바로 파일을 저장하는 단계입니다. 그림 1-7-1의 내용은 AppWizard를 이용하여 프로젝트를 만들었을 때 기본적으로 수행되는 루틴입니다. 이 루틴을 그대로 유지하고 파일 입출력을 이용하는 어플리케이션은 그리 많지가 않습니다. 그이유는 파일을 오픈하는 부분에서 일반적인 파일 대화상자보다는  파일 대화상자를 상속받은 자신의 대화상자를 쓰며,Serialize함수를 이용한 데이터 읽기와 저장은 복잡한 데이터를 저장하는데는 다소 불합리한 면이 있는등, 여러 부분에서 수정을 해야할 부분의 많기 때문입니다.

기억해야 할 것은 그림 1-7-1의 MFC 클래스에가 서로 연결된 상태에서  어느부분내용을 삭제하고 대신 자신만의 프로그램을 설정할것인가를 생각해야 한다는 것입니다. 예를 들어서 Serialize 함수를 이용하고 싶지않다면 (그림 1-7-1의 ④,⑦ 항목)  CDocument클래스를 상속받은 도큐먼트 클래스에서 OnOpenDocument나 또는 OnSaveDocument 함수를 만들고 이함수에서 CDocument::OnOpenDocument나 또는 CDocument::OnSaveDocument함수를 수행하지 않도록 하면 되는 것입니다.

<2>Serialize

파일을 저장하거나 오픈할 경우 최후에 데이터가 저장되는 부분은 Serialize함수에 의해서 설정됩니다. 도큐먼트 클래스의 Serialize함수는 다음과 같습니다.

void CExMyEdit2Doc::Serialize(CArchive& ar)

{

if (ar.IsStoring())

{

//파일 저장부분

}

else

{

//파일 열기 부분

}

}

Serialize함수는 인자로 CArchive 클래스의  변수인 ar 이 인자로 넘어옵니다. CArchive클래스는 쉽게 파이프 라고 생각하시면 됩니다. 도큐먼트 클래스에서 OnOpenDocument 함수가 수행되면 Serialize함수가 실행된다고 전에 설명을 하였습니다. OnOpenDocument나 또는 OnSaveDocument 두 개의 함수내부에서는 CFile클래스를 이용하여 파일을 오픈합니다. 이렇게 파일일 오픈한다음 파일과 메모리간에 데이터 전송 통로를 만들어줍니다. 이전송 통로가 바로 CArchive입니다. CArchive가 파이프라고 설명한 이유가 바로 여기에 있습니다. 예를 들어 상수도 에  어떤 파이프를 연결하면 물이 이파이프를 통해서 들어오고 하수도에 파이프를 연결하면 물이 이 파이프를 통해서 나가게 됩니다. 같은 의미로 저장형태로 CArchive를 연결하면 메모리에 있는 데이터가 파일로 들어가게 되고 열기 형태로 CArchive를 연결하면 파일에 있는 데이터가 메모리로 전송되는 것입니다. 예를 들어서 위의 Serialize에 □안녕하세요□ 라는 문자열을 입출력하고자 한다면 다음과 같이 할수 있습니다.

void CExMyEdit2Doc::Serialize(CArchive& ar)

{

CString data="안녕하세요□;

if (ar.IsStoring())

{

//파일 저장부분

ar << data;

}

else

{

//파일 열기 부분

ar >> data;

}

}

CArchive 클래스의 맴버 함수인 IsStoring함수는 현재 저장 형태로 연결되었을 경우 TRUE를 리턴하며 열기 형태로 연결되었을 경우 FALSE를 리턴합니다. 위의 예는 현재 저장형태 이면 data를  ar로 리다이렉션 시킨것입니다. ar << data;가 바로 이런 의미입니다. 이렇게 되면 data의 값이 바로 ar로 들어가게 되고 ar자체는 하나의 파이프라는 개념이므로 이파이프를 통해서 파일로 전달되게 되는 것입니다. 만일 현재 오픈상태로 연결되었을경우에는  ar >> data; 함수를 이용하여 파이프와 연결된 파일에서 문자열을 얻어서 data라는 변수에 저장하게 되는 것입니다.

간단한 데이터들은 Serialize 함수를 이용하여 쉽게 설정할수 있습니다. 리다이렉션 연산을 사용하면 순서적으로 데이터를 넣고 읽을수 있습니다. 예를 들어서 이름,전화번호,주소 의 문자열을 Serialize를 통해서 저장하고 읽고자 한다면 다음과 같이 할수 있습니다.

//헤더부

CString name;

CString tel;

CString address;

void CExMyEdit2Doc::Serialize(CArchive& ar)

{

if (ar.IsStoring())

{

//파일 저장부분

ar << name;

ar << tel;

ar << address

}

else

{

//파일 열기 부분

ar >> name;

ar >> tel;

ar >> address

}

}

파일에 이름,전화번호,주소를 저장할 때 ①과 같은 순서로 설정하면 읽을때도 같은순서로 ②와 같이 해야 합니다. 혹자는 들어갈 때 name,tel,address로 들어갔으니까 나올 때는 address,tel,name로 생각할지도 모릅니다. 스텍에 데이터가 들어가는 형태는 이런 식으로 될 수 있으나 파일에 데이터를 읽는 부분은 이와 다릅니다. Serialize 함수는 하나이지만 그림 1-7-1 처럼 읽기기 위해서 실행되는부분과 저장하기 위해서 실행하는 부분이 분리되어 있습니다. 저장후에 읽고자 할 때 파일을 오픈하고 파일의 최상단에 파일포인터가 위치하게 됩니다. 이상태에서 ar를 통해서 데이터가 읽어지기 때문에 저장하는 순서대로 다시 읽게 되는 것입니다.

<3>Serialize를 이용한 예제 ExMyEdit2

ExMyEdit2 는 지금까지 설명한 Serialize를 이용한 예제입니다. 이예제의 전체 구성도는 그림 1-7-2와 같습니다.

(그림 파일)

(그림 1-7-2)ExMyEdit2의 전체 구성도

(1)View윈도우에 에디터 박스 설정

ExMyEdit2는 MDI형태로 제작합니다. 먼저 도큐먼트클래스 (CExMyEdit2Doc)에 에디터 윈도우를 맴버로 설정합니다. 그림 1-7-2의 ①항목이 도큐먼트에 CEdit 클래스의 맴버 변수로 m_Edit로 설정한것입니다. 이렇게 설정한 이유는 그림 1-7-2의 ⓐ에서 View윈도우에 에디터 박스를 설정하고 이 에디터 박스의 데이터를 파일에 저장하기 위해서입니다. 도큐먼트에 설정한 m_Edit를 View윈도우의 OnCreate함수에서  View윈도우의 차일드 윈도우로 Create합니다. 그림 1-7-2의 ②항목 이 바로 이형태입니다. OnCreate함수가 수행되었을 때  에디터 윈도우를 (0,0,0,0) 영역에서 새로 만드는 형태입니다. 그림 1-7-2의 ②항목에서 도큐먼트안에 있는 에디터 윈도우를 받기 위해서 GetDocument()함수를 이용하였습니다. 이 함수는 현재 View와 연결되어 있는 도큐먼트 클래스의 포인터를 리턴합니다. 즉 현재 가동중인 CExMyEdit2View와 연결된 CExMyEdit2Doc의 포인터를 받는것입니다. 이내용이 ②항목에서 다음과 같은 내용입니다.

CExMyEdit2Doc *pDoc=GetDocument();

위와 같이 한후에 pDoc->m_Edit라고 선언을 하게 되면 도큐먼트 헤더에 정의한 m_Eidt를 가르키게 되는 것입니다. 이렇게 한후 이 에디터 박스를 (0,0,0,0) 영역에 설정한것입니다. OnCreate함수가 수행한후에는 에디터 박스가 View윈도우에 나타나지 않습니다. 그이유는 (0,0,0,0) 에 설정하였기 때문입니다. 이렇게 하고 에디터박스를 View윈도우의 전체 영역에 설정하기 위해서는 WM_SIZE 메시지가 발생될 때 수행되는 함수 인 OnSize에서 윈도우 영역을 바꾸면 됩니다. 이렇게 하는 이유는 View윈도우가 변경될 때마다 에디터박스□또한 View윈도우의 클라이언트 영역의 크기로 변경되어야 하기 때문입니다. 그림 1-7-2의 ③항목이 WM_SIZE 메시지가 발생될 때 수행되는 함수를 만들고 이함수에서 에디터 박스를 View윈도우의 클라이언트 영역으로 설정한 형태입니다. View 클래스에서 GetClientRect함수를 이용하면 현재 윈도우의 클라이언트 영역이 설정됩니다.  m_Edit의 SetWindowPos함수를 이용하여 이영역에 에디터박스를 설정하는 것입니다.

대부분의 어플리케이션 프로그램에서 데이터를 수정하였을 경우 저장을 하지 않고 끝내게 되면 그림 1-7-3과 같은 메시지 박스가 출력됩니다.

(그림 파일)

(그림 1-7-3)데이터 수정후 저장을 하지 않고 종료시 메시지

그림 1-7-3과 같은 메시지 박스를 출력하기 위해서는 CDocument클래스의 맴버함수인 SetModifiedFlag 함수를 이용하면 됩니다. SetModifiedFlag(TRUE);라고 하면 현재 데이터가 수정상태 이라는 의미입니다. 이상태에서 현재 윈도우를 종료하면 그림 1-7-3과 같은 메시지가 발생됩니다. 반대로 SetModifiedFlag(FALSE); 라고하면 현재 데이터 수정이 되지 않았다는 의미로 이상태에서 윈도우를 종료하면 그림 1-7-3과 같은 메시지박스가 출력되지 않습니다. 에디터박스에서 키가 눌려 졌거나 또는 수정이 되었을 경우 SetModifiedFlag 함수를 이용하여 현재 도큐먼트가 수정되었다는 것을 알려주어야 합니다. SetModifiedFlag 함수를 어느 부분에 설정해야 하는지가 문제입니다. 에디터박스에서 데이터가 수정되었을 경우 발생되는 메시지이벤트 는 EN_CHANGE입니다. 이메시지에 의해서 수행되는 함수를 클래스 위저드로는 만들수가 없습니다. 이럴경우에는 ON_EN_CHANGE 메시지 매크로를 이용할수 있습니다. 이부분에 대해서는 지금까지 많은 부분에서 설명을 하였습니다. 본예제에서는 이와 다르게 WM_COMMAND메시지가 발생될 때 수행되는 함수 OnCommand를 만들고 이부분에서 처리를 해보겠습니다. 그림 1-7-2의 ④항목이 WM_COMMAND에 메시지가 발생될 때 수행되는 함수 OnCommand를 만들고 이함수에서 SetModifiedFlag함수를 사용한것입니다. 차일드 윈도우에서 이벤트가 발생되면 WM_COMMAND가 발생됩니다. 이메시지와 함께 wParam의 LOWORD에는 차일드 윈도우 ID번호가 HIWORD에는 차일드 윈도우에서 발생한 이벤트 메시지가 설정됩니다. 그림 1-7-2의 ④항목에서 보면 LOWORD(wParam)의 스위치 문이 보일것입니다. 이때 이값이 1일 때 내용만 수행하였습니다. 1일때라는 의미는 에디터박스입니다. 에디터박스를 Create함수를 이용하여 만들 때 설정한 ID값이 1이기 때문입니다.(그림 1-7-2의 ② 항목을 참조하세요) 즉 에디터박스에서 HIWORD(wParam) 이 EN_CHANGE라는 의미는 에디터박스의 내용이 수정되었을때를 의미입니다. 이럴 경우 SetModifiedFlag를 실행하여 현재 에디터박스의 내용이 수정되었다는 의미입니다.

(3)편집 메뉴에 대응하기

ExMyEdit2 프로젝트를 만들면 그림 1-7-3과 같이 편집메뉴가 있습니다.

(그림 파일)

(그림 1-7-4)ExMyEdit2의 편집 메뉴

그림 1-7-4과 같이 편집메뉴에는 취소,잘라내기,복사,붙여넣기 4개의 항목이 있습니다. 에디터 박스는 이항목과 연계된 함수들을 제공합니다. 취소항목과 연결된  CEdit의 맴버 함수는 Undo() 이며, 잘라내가는 Cut(),복사는 Copy(), 붙여넣기는 Paste()함수입니다. 4개의 항목을 선택하였을때의 함수를 클래스 위저드로 만든후에 다음과 같이 에디터박스의 맴버 함수를 호출하여 편집메뉴와 연결을 시킵니다.

//편집메뉴 취소항목

void CExMyEdit2View::OnEditUndo()

{

CExMyEdit2Doc* pDoc = GetDocument();

pDoc->m_Edit.Undo();

}

//편집메뉴 붙여 넣기 항목

void CExMyEdit2View::OnEditPaste()

{

CExMyEdit2Doc* pDoc = GetDocument();

pDoc->m_Edit.Paste();

}

//편집 메뉴의 잘라내기 항목

void CExMyEdit2View::OnEditCut()

{

CExMyEdit2Doc* pDoc = GetDocument();

pDoc->m_Edit.Cut();

}

//편집 메뉴의 복사 항목

void CExMyEdit2View::OnEditCopy()

{

CExMyEdit2Doc* pDoc = GetDocument();

pDoc->m_Edit.Copy();

}

(4)Serialize 함수 수정

이제 본항목의 주된 요점인 Serialize 함수를 수정하겠습니다. 수정된 Serialize함수는 다음과 같습니다.

void CExMyEdit2Doc::Serialize(CArchive& ar)

{

CString data;

//파일 저장시

if (ar.IsStoring())

{

//에디터박스에 입력 된 데이터를 data에 받는다.

m_Edit.GetWindowText(data);

ar << data;

}

else

{

//data에 파일에서 문다열을 읽는다.

ar >> data;

//data를 에디터 박스에 설정한다.

m_Edit.SetWindowText(data);

}

}

Serialize 함수에서는 에디터박스에 설정된 데이터를 data 에 받은다음 이 데이터를 그대로 파일에 저장하고 또는 파일에서 문자열을 data로 읽은다음 이 데이터를 에디터 윈도우에 설정되는 것이 전부입니다. 위와 같이 하고 프로그램을 실행시키면 간단한 메모장 처럼 텍스트 데이터를 읽거나 저장할수 있는 프로그램이 됩니다.

<4>예제 ExMyEdit2 소스

다음은 ExMyEdit2의 도큐먼트와 View클래스의 소스입니다. 나머지 부분은 제공되는 CD를 참조하시기 바랍니다.

(1)도큐먼트 소스

(프로그램 소스)

// ExMyEdit2Doc.h : interface of the CExMyEdit2Doc class

//

/////////////////////////////////////////////////////////////////////////////

#if !defined(AFX_EXMYEDIT2DOC_H__62E9DA44_2921_11D2_A0D9_006097AEB8A7__INCLUDED_)

#define AFX_EXMYEDIT2DOC_H__62E9DA44_2921_11D2_A0D9_006097AEB8A7__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

class CExMyEdit2Doc : public CDocument

{

protected: // create from serialization only

CExMyEdit2Doc();

DECLARE_DYNCREATE(CExMyEdit2Doc)

// Attributes

public:

// Operations

public:

CEdit m_Edit;

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CExMyEdit2Doc)

public:

virtual BOOL OnNewDocument();

virtual void Serialize(CArchive& ar);

//}}AFX_VIRTUAL

// Implementation

public:

virtual ~CExMyEdit2Doc();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Generated message map functions

protected:

//{{AFX_MSG(CExMyEdit2Doc)

// NOTE - the ClassWizard will add and remove member functions here.

//    DO NOT EDIT what you see in these blocks of generated code !

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_EXMYEDIT2DOC_H__62E9DA44_2921_11D2_A0D9_006097AEB8A7__INCLUDED_)

// ExMyEdit2Doc.cpp : implementation of the CExMyEdit2Doc class

//

#include "stdafx.h"

#include "ExMyEdit2.h"

#include "ExMyEdit2Doc.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// CExMyEdit2Doc

IMPLEMENT_DYNCREATE(CExMyEdit2Doc, CDocument)

BEGIN_MESSAGE_MAP(CExMyEdit2Doc, CDocument)

//{{AFX_MSG_MAP(CExMyEdit2Doc)

// NOTE - the ClassWizard will add and remove mapping macros here.

//    DO NOT EDIT what you see in these blocks of generated code!

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// CExMyEdit2Doc construction/destruction

CExMyEdit2Doc::CExMyEdit2Doc()

{

// TODO: add one-time construction code here

}

CExMyEdit2Doc::~CExMyEdit2Doc()

{

}

BOOL CExMyEdit2Doc::OnNewDocument()

{

if (!CDocument::OnNewDocument())

return FALSE;

// TODO: add reinitialization code here

// (SDI documents will reuse this document)

return TRUE;

}

/////////////////////////////////////////////////////////////////////////////

// CExMyEdit2Doc serialization

void CExMyEdit2Doc::Serialize(CArchive& ar)

{

CString data;

//파일 저장시

if (ar.IsStoring())

{

//에디터박스에 입력 된 데이터를 data에 받는다.

m_Edit.GetWindowText(data);

ar << data;

}

else

{

//data에 파일에서 문다열을 읽는다.

ar >> data;

//data를 에디터 박스에 설정한다.

m_Edit.SetWindowText(data);

}

}

/////////////////////////////////////////////////////////////////////////////

// CExMyEdit2Doc diagnostics

#ifdef _DEBUG

void CExMyEdit2Doc::AssertValid() const

{

CDocument::AssertValid();

}

void CExMyEdit2Doc::Dump(CDumpContext& dc) const

{

CDocument::Dump(dc);

}

#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////

// CExMyEdit2Doc commands

(프로그래 소스끝)

(2)View클래스 소스

(프로그램 소스시작)

// ExMyEdit2View.h : interface of the CExMyEdit2View class

//

/////////////////////////////////////////////////////////////////////////////

#if !defined(AFX_EXMYEDIT2VIEW_H__62E9DA46_2921_11D2_A0D9_006097AEB8A7__INCLUDED_)

#define AFX_EXMYEDIT2VIEW_H__62E9DA46_2921_11D2_A0D9_006097AEB8A7__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

class CExMyEdit2View : public CView

{

protected: // create from serialization only

CExMyEdit2View();

DECLARE_DYNCREATE(CExMyEdit2View)

// Attributes

public:

CExMyEdit2Doc* GetDocument();

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CExMyEdit2View)

public:

virtual void OnDraw(CDC* pDC);  // overridden to draw this view

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

protected:

virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam);

//}}AFX_VIRTUAL

// Implementation

public:

virtual ~CExMyEdit2View();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Generated message map functions

protected:

//{{AFX_MSG(CExMyEdit2View)

afx_msg void OnSize(UINT nType, int cx, int cy);

afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

afx_msg void OnEditUndo();

afx_msg void OnEditPaste();

afx_msg void OnEditCut();

afx_msg void OnEditCopy();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

#ifndef _DEBUG  // debug version in ExMyEdit2View.cpp

inline CExMyEdit2Doc* CExMyEdit2View::GetDocument()

{ return (CExMyEdit2Doc*)m_pDocument; }

#endif

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_EXMYEDIT2VIEW_H__62E9DA46_2921_11D2_A0D9_006097AEB8A7__INCLUDED_)

// ExMyEdit2View.cpp : implementation of the CExMyEdit2View class

//

#include "stdafx.h"

#include "ExMyEdit2.h"

#include "ExMyEdit2Doc.h"

#include "ExMyEdit2View.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// CExMyEdit2View

IMPLEMENT_DYNCREATE(CExMyEdit2View, CView)

BEGIN_MESSAGE_MAP(CExMyEdit2View, CView)

//{{AFX_MSG_MAP(CExMyEdit2View)

ON_WM_SIZE()

ON_WM_CREATE()

ON_COMMAND(ID_EDIT_UNDO, OnEditUndo)

ON_COMMAND(ID_EDIT_PASTE, OnEditPaste)

ON_COMMAND(ID_EDIT_CUT, OnEditCut)

ON_COMMAND(ID_EDIT_COPY, OnEditCopy)

//}}AFX_MSG_MAP

// Standard printing commands

ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview)

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// CExMyEdit2View construction/destruction

CExMyEdit2View::CExMyEdit2View()

{

// TODO: add construction code here

}

CExMyEdit2View::~CExMyEdit2View()

{

}

BOOL CExMyEdit2View::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

return CView::PreCreateWindow(cs);

}

/////////////////////////////////////////////////////////////////////////////

// CExMyEdit2View drawing

void CExMyEdit2View::OnDraw(CDC* pDC)

{

CExMyEdit2Doc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

// TODO: add draw code for native data here

}

/////////////////////////////////////////////////////////////////////////////

// CExMyEdit2View printing

BOOL CExMyEdit2View::OnPreparePrinting(CPrintInfo* pInfo)

{

// default preparation

return DoPreparePrinting(pInfo);

}

void CExMyEdit2View::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add extra initialization before printing

}

void CExMyEdit2View::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add cleanup after printing

}

/////////////////////////////////////////////////////////////////////////////

// CExMyEdit2View diagnostics

#ifdef _DEBUG

void CExMyEdit2View::AssertValid() const

{

CView::AssertValid();

}

void CExMyEdit2View::Dump(CDumpContext& dc) const

{

CView::Dump(dc);

}

CExMyEdit2Doc* CExMyEdit2View::GetDocument() // non-debug version is inline

{

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CExMyEdit2Doc)));

return (CExMyEdit2Doc*)m_pDocument;

}

#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////

// CExMyEdit2View message handlers

void CExMyEdit2View::OnSize(UINT nType, int cx, int cy)

{

CView::OnSize(nType, cx, cy);

CExMyEdit2Doc* pDoc = GetDocument();

CRect rect;

GetClientRect(rect);

pDoc->m_Edit.SetWindowPos(&wndTop,0,0,rect.right-rect.left,

rect.bottom-rect.top,SWP_SHOWWINDOW);

}

int CExMyEdit2View::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CView::OnCreate(lpCreateStruct) == -1)

return -1;

CExMyEdit2Doc* pDoc = GetDocument();

CRect rect(0,0,0,0);

pDoc->m_Edit.Create(ES_AUTOVSCROLL | ES_MULTILINE | ES_WANTRETURN |

WS_VISIBLE

|WS_CHILD|WS_VSCROLL,rect,this,1);

return 0;

}

BOOL CExMyEdit2View::OnCommand(WPARAM wParam, LPARAM lParam)

{

CExMyEdit2Doc* pDoc = GetDocument();

switch(LOWORD(wParam))

{

case 1:

switch(HIWORD(wParam))

{

case EN_CHANGE:

pDoc->SetModifiedFlag(TRUE);

}

break;

}

return CView::OnCommand(wParam, lParam);

}

//편집메뉴 취소항목

void CExMyEdit2View::OnEditUndo()

{

CExMyEdit2Doc* pDoc = GetDocument();

pDoc->m_Edit.Undo();

}

//편집메뉴 붙여 넣기 항목

void CExMyEdit2View::OnEditPaste()

{

CExMyEdit2Doc* pDoc = GetDocument();

pDoc->m_Edit.Paste();

}

//편집 메뉴의 잘라내기 항목

void CExMyEdit2View::OnEditCut()

{

CExMyEdit2Doc* pDoc = GetDocument();

pDoc->m_Edit.Cut();

}

//편집 메뉴의 복사 항목

void CExMyEdit2View::OnEditCopy()

{

CExMyEdit2Doc* pDoc = GetDocument();

pDoc->m_Edit.Copy();

}

(프로그램 소스끝)

2.CEditView와 CRichEditView

Serialize 함수는 CDocument 클래스의 함수가 아니라 CObject클래스의 함수입니다. 결국 CObject 클래스를 상속받은 모든 클래스는 Serialize함수가 있으며 이함수를 현재 클래스의 정보를 그대로 파일에 저장할수 있다는 의미가 됩니다. 물론 모든 클래스들의 필요정보가 Serialize에 의해서 설정되는 것은 아닙니다. 그러나 데이터를 저장해야 할 필요가 있는 클래스들은 이 함수를 이용하여 데이터를 파일과 연결하고 있습니다. 그중에 대표적인 것이 CEditView클래스를 가지고 있는 에디터 뷰와 CRichEditView를 가지고 있는 리치 에디터 뷰입니다. 이 두 개의 클래스를 사용한 프로그램은 메모장이나 워드 패드 처럼 텍스트 문서를 작성할수 있습니다. 텍스트 문서를 작성할 때 가장 중요한 기능은 그 데이터를 저장하거나 읽고자 하는 것입니다. 따라서 Serialize함수가 매우 중요하게 활용됩니다.

<1>CEditView

CEditView는 CView를 상속받은 View 윈도우 클래스입니다. 보통 에디터 뷰 라고 하는 이프로그램을 만들고자 한다면 AppWizard Stpe 6에서 View클래스의 baseclass를 CEditView로 설정하면 됩니다. 그림 1-7-5는 ExMyEdit라는 프로젝트를 만들 때 AppWizard Step 6에서 View클래스의 base class를 CEditView로 설정한 화면입니다.

(그림 파일)

(그림 1-7-5)ExMyEdit 프로젝트 제작시 AppWizard Step6

위와 같이 하고 아무런 코딩 없이 컴파일하고 실행하면 ExMyEdit2와 같은 기능을 합니다. CEditView는 ExMyEdit2처럼 하기 위해서 Base class를 CEditView로 설정한 것입니다. ExMyEdit의 도큐먼트 클래스에서 파일 입출력은 어떻게 햇을까? 이부분의 답을 알고자 한다면 Serialize 함수를 참조하시면 됩니다. CExMyEditDoc의 Serialize함수는 다음과 같습니다.

void CExMyEditDoc::Serialize(CArchive& ar)

{

// CEditView contains an edit control which handles all serialization

((CEditView*)m_viewList.GetHead())->SerializeRaw(ar);

}

m_viewList.GetHead() 하는 함수는 도움말에는 보이지 않으나 이렇게 하면 현재 도큐먼트와 연결된 첫번째 View윈도우 포인터를 리턴하게 됩니다. 이 View윈도우가 CEditView이므로 이포인터로 전환하고 SerializeRaw라는 함수를 이용하여 Serialize를 하면 됩니다. SerializeRaw라는 함수는 CEditView의 맴버 함수로써 Serialize와 같은 기능을 합니다. 즉 CEditView의 Serialize 계열 함수를 이용하여 윈도우 클래스 내부에 있는 정보를 저장하는 형태가 되는 것입니다.

윈도우 뿐만 아니라 많은 MFC 클래스들이 Serialize를 지원합니다. 또한 OLE를 이용하고자 할때는 이함수를 이용하여 데이터를 저장하고 로드하는 부분을 매우 많이 사용합니다. Serialize라는 함수가 모든 클래스의 정보를 파일에 저장하고 로드할수 있는 기능이라는 것이 본 항목의 요점입니다.

<2>CRichEditView

CRichEditView클래스를 View로 설정한 프로그램을 만들면 간단한 워드 패드 프로그램을 만들수 있습니다. 이프로그램은 굉장히 많은 기능을 제공합니다. OLE 객체삽입,다양한 에디터 기능 ,찾기 바꾸기 기능능 쉽게 이야기 하면 워드 패드의 거히 모든 기능이 설정된 프로그램이 되는 것입니다. 본항목에서 CRichEditView를 이용하여 만든 프로그램은 ExRichEdit입니다. 이프로그램을 만들때 AppWizard Step6에서 View의 Base class를  CExRichEditView로 설정하여야 합니다. 그림 1-7-6은 AppWizard Step6에서 CRichEditView 로 설정한것입니다.

(그림 파일)

(그림 1-7-6)ExRichEdit 프로젝트 제작시 AppWizard Step6

그림 1-7-6과 같이 설정한후에 프로젝트를 만들고 컴파일하고 난후에 프로그램을 실행하면 대단히 많은 기능을 가지고 있는 에디터 프로그램이 만들어 질것입니다.

본장에서 주목할점은 입력된 데이터가 어떻게 파일로 저장되는가 입니다. 이부분또한 ExRichEdit의 도큐먼트 클래스에서 Serialize 함수를 보면 알수 있습니다.

void CExRichEditDoc::Serialize(CArchive& ar)

{

if (ar.IsStoring())

{

// TODO: add storing code here

}

else

{

// TODO: add loading code here

}

// Calling the base class CRichEditDoc enables serialization

//  of the container document's COleClientItem objects.

// TODO: set CRichEditDoc::m_bRTF = FALSE if you are serializing as text

CRichEditDoc::Serialize(ar);

}

Serialize의 함수안에 CRichEditDoc::Serialize(ar) 함수가 설정되어 있습니다. CRichEditView를 View윈도우로 설정하게 되면 도큐먼트 클래스의 Base class는 CRichEditDoc로 설정됩니다.  CExRichEditDoc::Serialize 는 먼저 CExRichEditDoc에서 저장할 필요가 있는 데이터를 ar에 의해서 입력하거나 로드한다음 Base클래스인 CExRichEditDoc의 Serialize를 호출함으로써 리치 에디터에 입력된 많은 데이터를 그대로 로드할수 있는 것입니다. 그림 1-7-7은 ExRichEdit의 실행결과 입니다.

(그림 파일)

(그림 1-7-7)ExRichEdit 실행결과

본항목에서 필자가 이야기 하고 싶은것은 모든 클래스에는 Serialize라는 함수를 포함하고 있다는 것입니다. 때로는 어떤 MFC클래스의 데이터를 파일로 저장하고 싶을 경우가 있을것입니다. 이럴경우 클래스의 기본 구조를 알지 못한다 하더라도 Serialize 함수를 이용하면 파일 입출력이 가능하다는 것입니다.

3.SDK 파일 입출력

필자는 지금까지 (이전 5.x,4.x 포함)  파일입출력을 설명할때는 _open 이나 _lopen같은 C언어에서 제공하는 파일입출력에 대한 설명을 많이 하였습니다. 본책에서는 SDK용 표준 파일 입출력함수 CreateFile에 대해서 설명을 하겠습니다.

Serialize라는 함수가 매우 강력하고 우리가 파일에 대한 개념이 없이 데이터를 디스크에 저장할수 있다는 매우 좋은점을 가지고 있습니다. 그러나 언제나 Serialize함수를 이용할수는 없습니다. 조금 복잡한 프로그램을 만들거나 또는 특정 분야에서 사용하는 전문적인 프로그램들을 제작한다고 할때, 필요한 데이터를 블럭단위로 또는 헤더와 데이터를 알맞게 설정하여 저장하는등 다양한 파일 입출력 기능이 필요하게 됩니다. 이럴경우에는 직접 파일을 오픈하고 이오픈된 파일에 데이터를 넣고 또 읽고 해야 합니다. 과거 표준 파일 입출력에서는 이부분을 open 이나 _open,write, _write 등등의 함수를 이용하였습니다. 이 함수들이 모두 Visual C++에서 사용가능합니다. MS사에서는 위의 표준 입출력 함수 외에 CreateFile,ReaeFile,WrieFile,CloseHandle 등의 함수를 제공 하였습니다. 이 함수들은 파일에서 데이터를 입출력하는 것 외에 많은 기능을 가지고 있는 파일 입출력 함수입니다.

<1>파일만들기와 열기

파일 만들기와 열기를 위해서 사용하는 함수는 CreateFile입니다.

CreateFile의 기본형태는 다음과 같습니다.

HANDLE CreateFile(

LPCTSTR lpFileName,          // 파일명 문자열 포인터

DWORD dwDesiredAccess,       // 읽기 쓰기 접근모드

DWORD dwShareMode,           // 공유 모드

LPSECURITY_ATTRIBUTES lpSecurityAttributes,

// 보안 구조체 포인터

DWORD dwCreationDisposition,  //파일 생성 플러그

DWORD dwFlagsAndAttributes,  // 파일 속성 플러그

HANDLE hTemplateFile         // 파일 속성을 복사할 핸들 포인터 );

CreateFile의 설정되는 인자가 매우 많은것을 보실수 있을것입니다. 인자가 많다는 것은 그만큼 복잡하다는 의미가 됩니다. 또 인자가 많다는 것은 그만큼 기능이 많다는 것입니다. CreateFile은 일반적인 파일 뿐만아니라 메모리 파일,포트 등 다양한 형태를 파일로 취급하고 오픈합니다. Unix나 또는 Linux 같은 OS에서는 모든 부분을 파일로 처리합니다. 디렉토리도 파일이며,메모리 포인터도 파일이고,프린터포트,시리얼 포트 등등 모든것을 파일의 개념으로 설정하기 때문에 각종 하드웨어를 파일 열듯이 오픈하고 사용할수 있습니다. 이와 비슷한 개념으로 CreateFile 함수가 생긴것입니다. 하드웨어 자원이나 또는 시스템 파일 메모리 등을 파일의 개념으로 오픈하고자 할때 CreateFile 함수를 사용합니다.

CreateFile의 첫번째 인자는  파일명입니다. 이곳에 파일명을 설명을 설정하면 해당 파일을 만들거나 오픈하며 시스템 포트를 설정하면 해당 포트를 오픈합니다. 예를 들어서 시리얼 포트 2번을 오픈하고자 할경우에 lpFileName에 □COM2" 하면 해당포트를 파일 형태로 오픈하게 됩니다.

두번째 인자 dwDesiredAccess 는  읽기 쓰기 접근 모드입니다. 이모드 값이 GENERIC_READ 이면 읽기 용이며 GENERIC_WRITE 이면 쓰기용입니다. 이둘값을 OR연산으로 설정하게 되면 읽기와 쓰기모드가 동시에 가능합니다.

세번째 인자 dwShareMode 는 공유 모드값입니다. 공유 모드란 현재 파일이 오픈상태에서 다른 파일들의 접근 허용을 설정하는 모드입니다. 특정 파일을 오픈하였을때 이파일이 다른 쪽에서 오픈할수 없도록 접근을 막는경우가 있습니다. 이렇게 하여야만 데이터를 정확하게 보존을 할수 있기 때문입니다. 예를 들어서 □test.dat"라는 파일을 한쪽에서 오픈하여서 □마징가제트□ 라고 쓰고 있는데 다른쪽에서는 □그렌다이저□ 라고 썼을경우 최후에 저장한 값이 설정되고 이전의 값이 사라지게 될수가 있기 때문에 쓰기모드만 접근을 금지하는 경우가 있으며, 또는 읽기 자체도 접근을 금지하때도 있게 됩니다. 이값이 0이면 어떤 공유도 허가하지 않는것이며 이값이 FILE_SHARE_READ 일경우에는 읽기는 공유하는것이고 FILE_SHARE_WRITE 경우에는 쓰기를 공유하는 것입니다.

네번째 인자 lpSecurityAttributes 는  보안에 대한 구조체 변수입니다. 보통 Window NT에서 사용권한자 모드를 설정하기 위한 구조체로써 윈도우 98에서는 NULL를 사용하여 이부분을 무시합니다. 다섯번째 인자 dwCreationDisposition 는 파일을 존재하는 것을 오픈할것인가? 아니면 새로 만들것인가에 대한 플러그입니다. CreateFile와 함께 사용하는 ReaeFile,WriteFile,CloseHandle 등의 파일 컨트롤 함수중에 OpenFile 이라는 의미의 함수는 없습니다. 즉 CreartFile의 dwCreationDisposition 인자에 값을 설정하여 오픈과 생성을 결정합니다. 이플러그값이 CREATE_NEW 이면 새로 파일을 만든다는 의미입니다. 이플러그를 사용했을때 현재 파일이 존재하면 새로 만들지 않고 에러를 리턴합니다. 현재 존재하는 파일을 무시하고 파일을 만들고자 할경우에는 CREATE_ALWAYS 플러그를 사용하면 됩니다. 존재하는 파일을 오픈하고자 할경우에는 dwCreationDisposition  값에 OPEN_EXISTING 플러그를 사용하면 됩니다. 여섯번째 인자 dwFlagsAndAttributes  는 그밖의 파일 속성을 설정하는 플러그입니다. 이플러그 리스트는 표1과 같습니다.

(표1)dwFlagsAndAttributes  플러그 리스트

플러그	설명
FILE_ATTRIBUTE_ARCHIVE	현재 파일이 archived 된다는 의미 이플러그를 설정하면 현재 파일이 이동되거나 사라졌을경우 이때의 상태를 마크한다.
FILE_ATTRIBUTE_HIDDEN	감추어져 있는 형태
FILE_ATTRIBUTE_NORMAL	일반 파일
FILE_ATTRIBUTE_OFFLINE	현재 파일을 다른 곳에서 사용하기가 불가능한 상태로 설정
FILE_ATTRIBUTE_READONLY	읽기 전용파일
FILE_ATTRIBUTE_SYSTEM	시스템 파일

마지막인자 hTemplateFile 는 Windows 95 에서는 사용하지 않고 NULL로 설정합니다. NT에서는 읽기전용으로 파일을 오픈하였을때 오픈하고자 하는 핸들을 hTemplateFile에 설정합니다.

CreateFile를 이용하여 □test.dat" 라는 파일을 오픈하고자 할경우 다음과 같이 할수 있습니다.

HANDLE fd=CreateFile("test.dat",GENERIC_READ|GENERIC_WRITE,

0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);

위와 같이하면 □test.dat" 파일이 읽기와 쓰기 형태로 만들어지고 현재 파일이 존재하여도 이를 무시하고 만들어지게 됩니다. 만일 현재 존재하는 파일을 오픈하고자 한다면 CreateFile함수를 다음과 같이 이용할수 있습니다.

HANDLE fd=CreateFile("test.dat",GENERIC_READ|GENERIC_WRITE,

0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

<2>파일에 읽고 쓰기

파일에서 데이터를 읽고자 할경우에는 ReadFile함수를 사용합니다. 이함수의 형태는 다음과 같습니다.

BOOL ReadFile(

HANDLE hFile,     // 파일 핸들

LPVOID lpBuffer,    // 읽고자 하는 데이터를 저장 버퍼 포인터

DWORD nNumberOfBytesToRead,  // 읽고자 하는 데이터의 길이

LPDWORD lpNumberOfBytesRead ,  // 읽혀진 데이터 길이

LPOVERLAPPED lpOverlapped   // OVERLAPPED 구조체 포인터);

ReadFile의 첫번째 인자 hFile은 읽고자 하는 파일 핸들입니다. 이핸들을 CreateFile에 의해서 리턴된 핸들을 의미합니다. lpBuffer은 읽은 데이터가 저장되는 버퍼입니다.

nNumberOfBytesToRead,  는 읽고자 하는 데이터 길이이며 lpNumberOfBytesRead  길이입니다. nNumberOfBytesToRead값은 함수사용시 설정하는 값이며 lpNumberOfBytesRead  는 함수가 실행된후 리턴된 값입니다. nNumberOfBytesToRead에 설정한 값만큼 파일에서 데이터를 읽는것은 아닙니다. 예를 들어서 파일 크기가 512인데 1024를 읽고자 하여 lpNumberOfBytesRead에 1024값을 설정하였다 하여도 크기가 512이기 때문에 lpNumberOfBytesRead  에는 512가 설정됩니다. 이런 이유때문에 읽고자하는 변수와 읽혀진 데이터값을 설정하는 변수가 따로 있는것입니다. lpOverlapped는 OVERLAPPED 구조체 포인터입니다. 이구조체는 주로 파이프를 이용한 프로그램을 작성하거나 또는 통신포트에 데이터를 쓰고자 할때 사용하는 구조체입니다. 일반 파일에서는 파일 포인터를 설정하기 위해서 OVERLAPPED구조체의 맴버 변수 Offset 값에 파일 위치값을 설정하여 특정 부분의 위치의 데이터를 읽을수 있습니다.

ReadFile를 이용하여 데이터를 80바이트 읽고자 할때의 예는 다음과 같습니다.

char temp[80];

int len;

ReadFile(fd,temp,80,&len,NULL);

파일에 데이터를 쓰고자 할경우 WriteFile함수를 사용합니다. 이함수의 형태는 다음과 같습니다.

BOOL WriteFile(

HANDLE hFile,               // 파일 핸들러

LPCVOID lpBuffer,           // 쓰고자 하는 데이터가 설정된 버퍼 포인터

DWORD nNumberOfBytesToWrite,  // 쓰고자 하는 데이터 길이

LPDWORD lpNumberOfBytesWritten,//쓰여진 데이터길이

LPOVERLAPPED lpOverlapped   //OVERLAPPED 구조체 포인터);

WriteFile과 ReadFile 에서 사용한 인자와 비교해서 생각하시면 이해가 쉬울것입니다. lpBuffer은 쓰고자 하는 데이터가 있는 버퍼 포인터이며 nNumberOfBytesToWrite는 쓰고자 하는 데이터길이입니다. lpNumberOfBytesWrittten는 파일에 기록하였을때 실제 기록된 길이입니다.

WriteFile함수를 사용하여 파일에 데이터를 기록하는 예는 다음과 같습니다.

char temp[80];

int len;

strcpy(temp,"this is test");

WriteFile(fd,temp,strlen(temp),&len,NULL);

위와 같이 하면  temp의 문자열 길이만큼 파일에 temp의 값이 저장되게 됩니다.

<3>파일 닫기

CreateFile함수에 의해서 오픈된 파일을 닫고자 할경우에는 CloaseHandle를 사용합니다. 이함수는 다음과 같습니다.

BOOL CloseHandle(  HANDLE hObject);

hObject는 CreateFile에 의해서 설정된 핸들값입니다. 위에서 사용한 fd라는 파일핸들러를 이용하여 파일을 닫고자 하는 예는 다음과 같습니다.

CloseHandle(fd);

<4>SDK 파일 입출력을 이용한 예제 MExDiary

MExDiary 는 일기장 예제 입니다. 예제는 MDI형태이며 View 클래스를 CFormView로 설정하였습니다. 그림 1-7-8은 MExDiary의 전체 형태 입니다.

(그림 파일)

(그림 1-7-8)MExDiary 전체 형태

그림 1-7-8 의 ⓐ항목이 MExDiary의 실행 형태입니다. MExDiary프로그램이 실행되면 3개 의 에디터 박스가 있는 FormView가 실행됩니다. 이각각의 에디터박스 ID와 이  ID와 연결된 View의 맴버 함수는 그림 1-7-8의 ①,②,③과 같습니다.

3개의 에디터 박스에서 데이터가 수정되면 수정된 데이터가 그대로 도큐먼트에 설정되어야 합니다. 이렇게 하는 이유는 도큐먼트에서 파일로 데이터를 저장하는 기능을 설정하기 위해서입니다. View의 에디터 박스에 기록한 데이터를 저장하는 도큐먼트 클래스의 맴버 변수는 그림 1-7-8의  ④항목입니다. ④항목에 있는 맴버들에 데이터를 저장하기 위해서 호출되는 함수가 그림 1-7-8의 ⑤,⑥,⑦ 입니다. 파일을 오픈하였을때 파일에 있는 데이터는 먼저 도큐먼트의 맴버 즉 그림 1-7-8의 ⑤,⑥,⑦에 저장이 됩니다. 이렇게 저장된 데이터는 다시 View의 3개의 에디터 박스에 출력이 되어야 합니다. 이렇게 출력하기 위해서 도큐먼트의 맴버의 값을 얻고자 하는 함수가 그림 1-7-8의 ⑧,⑨,⑩ 입니다. 이런 루틴으로 View와 도큐먼트가 연결됩니다.

본예제의 프로그래밍 방법은 다음과 같습니다.

(1)도큐먼트 변수와 함수 설정

날짜와 제목을과 내용을 입력하고 이 입력된 데이터가 바로 파일에 저장하도록 할려고 한다면 현재 View에 있는 데이터를 복사하여서 저장할 도큐먼트의 맴버 변수들이 필요합니다. MExDiaryDoc.h 즉 CMExDiaryDoc는 다음과 같이 View에서 입력된 데이터를 저장할 변수를 만들었습니다.

char m_strContent[MAXCONTENTSIZE];//내용을 저장하는 변수

char m_strDate[MAXDATESIZE];//날짜를 저장하는 변수

char m_strSubject[MAXSUBJECTSIZE];//제목을 저장하는 변수

위의 3개의 변수에 설정된 MAX계열의 값은 MExDiaryDoc.h에 다음과 같이 설정하였습니다.

#define MAXCONTENTSIZE 1024

#define MAXSUBJECTSIZE 256

#define MAXDATESIZE 80

맴버 변수가 있다면 이 변수에 데이터를 설정하는 Set계열 함수와 설정된 데이터를 얻는 Get계열 함수를 만든다는것은 C++의 기본입니다. 따라서 위의 3개의 변수에서 데이터를 읽고 데이터를 쓰는 함수를 MExDiaryDoc.cpp에 ( CMExDiaryDoc클래스의 맴버변수로) 다음과 같이 기록합니다.

//제목 설정하는 함수

void CMExDiaryDoc::SetSubject(char *subject)

{

strcpy(m_strSubject,subject);

}

//내용 설정하는 함수

void CMExDiaryDoc::SetContent(char *content)

{

strcpy(m_strContent,content);

}

//날짜 설정하는 함수

void CMExDiaryDoc::SetDate(char *date)

{

strcpy(m_strDate,date);

}

//날짜를 얻는 함수

char* CMExDiaryDoc::GetDate()

{

return m_strDate;

}

//제목을 얻는 함수

char * CMExDiaryDoc::GetSubject()

{

return m_strSubject;

}

//내용을 얻는 함수

char * CMExDiaryDoc::GetContent()

{

return m_strContent;

}

만일 도큐먼트에 저장된 내용을 얻고 싶다면 GetContent함수를 이용하면 되고 도큐먼트이 m_strContent에 데이터를 넣고 싶다면 SetContent 함수에 넣고자 하는 문자열을 설정해 주면 됩니다.

(2)View에서 도큐먼트로 데이터 전송

View에서는 항목에 문자열이 입력될때마다 이값을 도큐먼트에 입력하면 됩니다. 제목,날짜,내용 3개의 항목에서 EN_CHANGE메세지가 발생될경우 바로 각항목마다 수정이 되어 있다는 의미입니다. 이때 수정된 데이터를 바로 도큐먼트에 설정해 주면 됩니다. 클래스 위저드를 이용하여 IDC_CONTENT,IDC_SUBJECT,IDC_DATE 3개의 항목에서 EN_CHANGE 메세지가 발생될때 수행되는 함수를 다음과 같이 만든후에 다음과 같이 수정합니다.

//입력된 내용이 변경되었을때

void CMExDiaryView::OnChangeContent()

{

UpdateData(TRUE);

CMExDiaryDoc *pDoc=(CMExDiaryDoc *)GetDocument();

pDoc->SetContent((LPSTR)(LPCSTR)m_strContent);

}

//입력된 날짜가 변경되었을때

void CMExDiaryView::OnChangeDate()

{

UpdateData(TRUE);

CMExDiaryDoc *pDoc=(CMExDiaryDoc *)GetDocument();

pDoc->SetDate((LPSTR)(LPCSTR)m_strDate);

}

//입력된 제목이 변경되었을때

void CMExDiaryView::OnChangeSubject()

{

UpdateData(TRUE);

CMExDiaryDoc *pDoc=(CMExDiaryDoc *)GetDocument();

pDoc->SetSubject((LPSTR)(LPCSTR)m_strSubject);

}

위와 같이하면 View에서 변경된 데이터가 그대로 도큐먼트에 저장됩니다.

(3)도큐먼트에서 View로 데이터 전송

이제 도큐먼트에 있는 내용을 View화면으로 옴기는 내용을 만들겠습니다. 도큐먼트에 있는 내용을 View로 옮길때는 새로운 윈도우가 만들어질경우일것입니다. 즉 File Open이나 File New항목일경우 이에 해당하는 데이터를 Document에서 로드함과 동시에 View를 만들기 때문입니다. FormView에서 자원을 초기화할대는 OnInitialUpdate()  라고 하였습니다. 이함수를 만든다음에 다음과 같이 수정을 하면 도큐먼트에 있는 데이터가 바로 View로 전송이 됩니다.

void CMExDiaryView::OnInitialUpdate()

{

CFormView::OnInitialUpdate();

// TODO: Add your specialized code here and/or call the base class

CMExDiaryDoc *pDoc=(CMExDiaryDoc *)GetDocument();

m_strSubject=pDoc->GetSubject();

m_strDate=pDoc->GetDate();

m_strContent=pDoc->GetContent();

UpdateData(FALSE);

}

OnInitialUpdate함수는 먼저 도큐먼트를 받은 다음 이 도큐먼트의 각각의 맴버변수 m_strSubject,m_strDate,m_strContent를 Get계열 함수를 이용하여 바로 View의 변수에 저장하였으며 UpdateData라는 함수를 이용하여 자원에 전송하였습니다.

만일 Open항목을 통해서 파일에 저장된 데이터가 도큐먼트에 로드되면 이 로드된 데이터가 View항목의 맴버로 연결되고 이것이 다시 자원으로 연결되기 때문에 파일에 있는 데이터가 화면에 보이는 것입니다.

(4)OnOpenDocument 와 OnSaveDocument

File 항목에서 Open 항목을 통해서 새로운 데이터를 로드하면 OnOpenDocument가 실행되고 이함수는 다시 Serialize함수를 호출한다고 설명하였습니다. 클래스 위저드를 이용하여 MExDiaryDoc에서 OnOpenDocument를 선택하여 함수를 만들면 다음과 같이 설정될것입니다.

BOOL CMExDiaryDoc::OnOpenDocument(LPCTSTR lpszPathName)

{

if (!CDocument::OnOpenDocument(lpszPathName))

return FALSE;

}

OnOpenDocument는 다시 상위 클래스인 CDocument의 OnOpenDocument를 호출하고 이호출된 OnOpenDocument는 CFile클래스를 이용하여 파일을 열고 이파일에 데이터를 기록 또는 읽는 전송라인(파이프라인이라고 했습니다) CArchive 를 만든다음 이 클래스의 변수 ar를 넘겨주는 Serialize 를 실행한다고 하였습니다.

위의 OnOpenDocument 함수에서 Serialize함수를 실행하지 않고 자신만의 파일 기록함수를 실행하고자 한다면 위의 if 문의 모든 내용을 삭제하면 됩니다. 이렇게 되면 Serialize함수가 실행되지 않습니다. 다음 내용은 Serialize함수를 실행시키지 않고 CreateFile함수와 ReadFile함수를 이용하여 입력된 데이터를 파일에서 읽는 OnOpenDocument함수입니다.

BOOL CMExDiaryDoc::OnOpenDocument(LPCTSTR lpszPathName)

{

memset(m_strContent,0,MAXCONTENTSIZE);

memset(m_strDate,0,MAXDATESIZE);

memset(m_strSubject,0,MAXSUBJECTSIZE);

HANDLE fd=CreateFile(lpszPathName,

GENERIC_READ|GENERIC_WRITE,

0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

if(fd<0)

{

AfxMessageBox("파일을 읽는 데 실패하였습니다.");

return FALSE;

}

DWORD len;

ReadFile(fd,m_strDate,sizeof(m_strDate),&len,NULL);

ReadFile(fd,m_strSubject,sizeof(m_strSubject),&len,NULL);

ReadFile(fd,m_strContent,sizeof(m_strContent),&len,NULL);

CloseHandle(fd);

return TRUE;

}

OnOpenDocument함수는 File항목에서 설정한 파일의 전체 경로이름이 lpszPathName로 넘어옵니다. 이 넘어온 파일명으로 파일을  오픈합니다.그리고

ReadFile 함수를 이용하여 m_strDate,m_strSubject,m_strContent를 차례로 읽는 것입니다.

클래스 위저드를 이용하여  OnSaveDocument를 만들계 되면 다음과 같은 함수가 만들어 집니다.

BOOL CMExDiaryDoc::OnSaveDocument(LPCTSTR lpszPathName)

{

// TODO: Add your specialized code here and/or call the base class

return CDocument::OnSaveDocument(lpszPathName);

}

OnSaveDocument또한 상위 클래스인 CDocument의 OnSaveDocument를 호출하기 때문에 Serialize 함수가 바로 연결됩니다. 따라서 return 문장을 모두 제거해야만 Serialize 함수가 호출되지 않습니다.

위의 OnOpenDocument와 같은 형식으로 파일에 데이터를 저장하는 OnSaveDocument 는 다음과 같습니다.

BOOL CMExDiaryDoc::OnSaveDocument(LPCTSTR lpszPathName)

{

HANDLE fd=CreateFile(lpszPathName,GENERIC_READ|GENERIC_WRITE,

0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);

if(fd<0)

{

AfxMessageBox("파일을 만드는 데 실패하였습니다.");

return FALSE;

}

DWORD len;

WriteFile(fd,m_strDate,sizeof(m_strDate),&len,NULL);

WriteFile(fd,m_strSubject,sizeof(m_strSubject),&len,NULL);

WriteFile(fd,m_strContent,sizeof(m_strContent),&len,NULL);

CloseHandle(fd);

return TRUE;

//CDocument::OnSaveDocument(lpszPathName);

}

이와 같이 한후에 프로그램을 컴파일하고 실행한후에 데이터를 입력하고 저장하는 것을 실행해 보시기 바랍니다.

<5>예제 MExDiary 소스

다음은 MExDiary 의 도큐먼트클래스와 View클래스 소스입니다.

(1)도큐먼트 클래스 소스

(소스시작)

// MExDiaryDoc.h : interface of the CMExDiaryDoc class

//

/////////////////////////////////////////////////////////////////////////////

#if !defined(AFX_MEXDIARYDOC_H__406A7A27_C9DC_11D1_AD20_006097AD9DDC__INCLUDED_)

#define AFX_MEXDIARYDOC_H__406A7A27_C9DC_11D1_AD20_006097AD9DDC__INCLUDED_

#if _MSC_VER >= 1000

#pragma once

#endif // _MSC_VER >= 1000

#define MAXCONTENTSIZE 1024

#define MAXSUBJECTSIZE 256

#define MAXDATESIZE 80

class CMExDiaryDoc : public CDocument

{

protected: // create from serialization only

CMExDiaryDoc();

DECLARE_DYNCREATE(CMExDiaryDoc)

// Attributes

public:

char m_strContent[MAXCONTENTSIZE];

char m_strDate[MAXDATESIZE];

char m_strSubject[MAXSUBJECTSIZE];

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CMExDiaryDoc)

public:

virtual BOOL OnNewDocument();

virtual void Serialize(CArchive& ar);

virtual BOOL OnSaveDocument(LPCTSTR lpszPathName);

virtual BOOL OnOpenDocument(LPCTSTR lpszPathName);

//}}AFX_VIRTUAL

// Implementation

public:

char * GetContent();

char * GetSubject();

char* GetDate();

void SetSubject(char *subject);

void SetContent(char *content);

void SetDate(char *date);

virtual ~CMExDiaryDoc();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Generated message map functions

protected:

//{{AFX_MSG(CMExDiaryDoc)

// NOTE - the ClassWizard will add and remove member functions here.

//    DO NOT EDIT what you see in these blocks of generated code !

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_MEXDIARYDOC_H__406A7A27_C9DC_11D1_AD20_006097AD9DDC__INCLUDED_)

// MExDiaryDoc.cpp : implementation of the CMExDiaryDoc class

//

#include "stdafx.h"

#include "MExDiary.h"

#include "MExDiaryDoc.h"

#include <fcntl.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <io.h>

#include <stdio.h>

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// CMExDiaryDoc

IMPLEMENT_DYNCREATE(CMExDiaryDoc, CDocument)

BEGIN_MESSAGE_MAP(CMExDiaryDoc, CDocument)

//{{AFX_MSG_MAP(CMExDiaryDoc)

// NOTE - the ClassWizard will add and remove mapping macros here.

//    DO NOT EDIT what you see in these blocks of generated code!

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// CMExDiaryDoc construction/destruction

CMExDiaryDoc::CMExDiaryDoc()

{

// TODO: add one-time construction code here

}

CMExDiaryDoc::~CMExDiaryDoc()

{

}

BOOL CMExDiaryDoc::OnNewDocument()

{

if (!CDocument::OnNewDocument())

return FALSE;

memset(m_strContent,0,MAXCONTENTSIZE);

memset(m_strDate,0,MAXDATESIZE);

memset(m_strSubject,0,MAXSUBJECTSIZE);

return TRUE;

}

/////////////////////////////////////////////////////////////////////////////

// CMExDiaryDoc serialization

void CMExDiaryDoc::Serialize(CArchive& ar)

{

if (ar.IsStoring())

{

// TODO: add storing code here

}

else

{

// TODO: add loading code here

}

}

/////////////////////////////////////////////////////////////////////////////

// CMExDiaryDoc diagnostics

#ifdef _DEBUG

void CMExDiaryDoc::AssertValid() const

{

CDocument::AssertValid();

}

void CMExDiaryDoc::Dump(CDumpContext& dc) const

{

CDocument::Dump(dc);

}

#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////

// CMExDiaryDoc commands

//제목 설정하는 함수

void CMExDiaryDoc::SetSubject(char *subject)

{

strcpy(m_strSubject,subject);

}

//내용 설정하는 함수

void CMExDiaryDoc::SetContent(char *content)

{

strcpy(m_strContent,content);

}

//날짜 설정하는 함수

void CMExDiaryDoc::SetDate(char *date)

{

strcpy(m_strDate,date);

}

//날짜를 얻는 함수

char* CMExDiaryDoc::GetDate()

{

return m_strDate;

}

//제목을 얻는 함수

char * CMExDiaryDoc::GetSubject()

{

return m_strSubject;

}

//내용을 얻는 함수

char * CMExDiaryDoc::GetContent()

{

return m_strContent;

}

BOOL CMExDiaryDoc::OnSaveDocument(LPCTSTR lpszPathName)

{

HANDLE fd=CreateFile(lpszPathName,GENERIC_READ|GENERIC_WRITE,

0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);

if(fd<0)

{

AfxMessageBox("파일을 만드는 데 실패하였습니다.");

return FALSE;

}

DWORD len;

WriteFile(fd,m_strDate,sizeof(m_strDate),&len,NULL);

WriteFile(fd,m_strSubject,sizeof(m_strSubject),&len,NULL);

WriteFile(fd,m_strContent,sizeof(m_strContent),&len,NULL);

CloseHandle(fd);

return TRUE;

//CDocument::OnSaveDocument(lpszPathName);

}

BOOL CMExDiaryDoc::OnOpenDocument(LPCTSTR lpszPathName)

{

memset(m_strContent,0,MAXCONTENTSIZE);

memset(m_strDate,0,MAXDATESIZE);

memset(m_strSubject,0,MAXSUBJECTSIZE);

HANDLE fd=CreateFile(lpszPathName,GENERIC_READ|GENERIC_WRITE,

0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);

if(fd<0)

{

AfxMessageBox("파일을 읽는 데 실패하였습니다.");

return FALSE;

}

DWORD len;

ReadFile(fd,m_strDate,sizeof(m_strDate),&len,NULL);

ReadFile(fd,m_strSubject,sizeof(m_strSubject),&len,NULL);

ReadFile(fd,m_strContent,sizeof(m_strContent),&len,NULL);

CloseHandle(fd);

return TRUE;

}

(소스끝)

(2)View 클래스 소스

(프로그램 소스)

// MExDiaryView.h : interface of the CMExDiaryView class

//

/////////////////////////////////////////////////////////////////////////////

#if !defined(AFX_MEXDIARYVIEW_H__406A7A29_C9DC_11D1_AD20_006097AD9DDC__INCLUDED_)

#define AFX_MEXDIARYVIEW_H__406A7A29_C9DC_11D1_AD20_006097AD9DDC__INCLUDED_

#if _MSC_VER >= 1000

#pragma once

#endif // _MSC_VER >= 1000

class CMExDiaryView : public CFormView

{

protected: // create from serialization only

CMExDiaryView();

DECLARE_DYNCREATE(CMExDiaryView)

public:

//{{AFX_DATA(CMExDiaryView)

enum { IDD = IDD_MEXDIARY_FORM };

CString	m_strDate;

CString	m_strSubject;

CString	m_strContent;

//}}AFX_DATA

// Attributes

public:

CMExDiaryDoc* GetDocument();

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CMExDiaryView)

public:

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

virtual void OnInitialUpdate();

protected:

virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support

virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnPrint(CDC* pDC, CPrintInfo*);

//}}AFX_VIRTUAL

// Implementation

public:

virtual ~CMExDiaryView();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Generated message map functions

protected:

//{{AFX_MSG(CMExDiaryView)

afx_msg void OnChangeContent();

afx_msg void OnChangeDate();

afx_msg void OnChangeSubject();

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

#ifndef _DEBUG  // debug version in MExDiaryView.cpp

inline CMExDiaryDoc* CMExDiaryView::GetDocument()

{ return (CMExDiaryDoc*)m_pDocument; }

#endif

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_MEXDIARYVIEW_H__406A7A29_C9DC_11D1_AD20_006097AD9DDC__INCLUDED_)

// MExDiaryView.cpp : implementation of the CMExDiaryView class

//

#include "stdafx.h"

#include "MExDiary.h"

#include "MExDiaryDoc.h"

#include "MExDiaryView.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

/////////////////////////////////////////////////////////////////////////////

// CMExDiaryView

IMPLEMENT_DYNCREATE(CMExDiaryView, CFormView)

BEGIN_MESSAGE_MAP(CMExDiaryView, CFormView)

//{{AFX_MSG_MAP(CMExDiaryView)

ON_EN_CHANGE(IDC_CONTENT, OnChangeContent)

ON_EN_CHANGE(IDC_DATE, OnChangeDate)

ON_EN_CHANGE(IDC_SUBJECT, OnChangeSubject)

//}}AFX_MSG_MAP

// Standard printing commands

ON_COMMAND(ID_FILE_PRINT, CFormView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_DIRECT, CFormView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_PREVIEW, CFormView::OnFilePrintPreview)

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// CMExDiaryView construction/destruction

CMExDiaryView::CMExDiaryView()

: CFormView(CMExDiaryView::IDD)

{

//{{AFX_DATA_INIT(CMExDiaryView)

m_strDate = _T("");

m_strSubject = _T("");

m_strContent = _T("");

//}}AFX_DATA_INIT

// TODO: add construction code here

}

CMExDiaryView::~CMExDiaryView()

{

}

void CMExDiaryView::DoDataExchange(CDataExchange* pDX)

{

CFormView::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CMExDiaryView)

DDX_Text(pDX, IDC_DATE, m_strDate);

DDX_Text(pDX, IDC_SUBJECT, m_strSubject);

DDX_Text(pDX, IDC_CONTENT, m_strContent);

//}}AFX_DATA_MAP

}

BOOL CMExDiaryView::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

//  the CREATESTRUCT cs

return CFormView::PreCreateWindow(cs);

}

/////////////////////////////////////////////////////////////////////////////

// CMExDiaryView printing

BOOL CMExDiaryView::OnPreparePrinting(CPrintInfo* pInfo)

{

// default preparation

return DoPreparePrinting(pInfo);

}

void CMExDiaryView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add extra initialization before printing

}

void CMExDiaryView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add cleanup after printing

}

void CMExDiaryView::OnPrint(CDC* pDC, CPrintInfo*)

{

// TODO: add code to print the controls

}

/////////////////////////////////////////////////////////////////////////////

// CMExDiaryView diagnostics

#ifdef _DEBUG

void CMExDiaryView::AssertValid() const

{

CFormView::AssertValid();

}

void CMExDiaryView::Dump(CDumpContext& dc) const

{

CFormView::Dump(dc);

}

CMExDiaryDoc* CMExDiaryView::GetDocument() // non-debug version is inline

{

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMExDiaryDoc)));

return (CMExDiaryDoc*)m_pDocument;

}

#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////

// CMExDiaryView message handlers

void CMExDiaryView::OnInitialUpdate()

{

CFormView::OnInitialUpdate();

// TODO: Add your specialized code here and/or call the base class

CMExDiaryDoc *pDoc=(CMExDiaryDoc *)GetDocument();

m_strSubject=pDoc->GetSubject();

m_strDate=pDoc->GetDate();

m_strContent=pDoc->GetContent();

UpdateData(FALSE);

}

void CMExDiaryView::OnChangeContent()

{

UpdateData(TRUE);

CMExDiaryDoc *pDoc=(CMExDiaryDoc *)GetDocument();

pDoc->SetContent((LPSTR)(LPCSTR)m_strContent);

}

void CMExDiaryView::OnChangeDate()

{

UpdateData(TRUE);

CMExDiaryDoc *pDoc=(CMExDiaryDoc *)GetDocument();

pDoc->SetDate((LPSTR)(LPCSTR)m_strDate);

}

void CMExDiaryView::OnChangeSubject()

{

UpdateData(TRUE);

CMExDiaryDoc *pDoc=(CMExDiaryDoc *)GetDocument();

pDoc->SetSubject((LPSTR)(LPCSTR)m_strSubject);

}

(프로그램 소스끝)

4.CFile 클래스

MFC에서는 파일을 핸들링하기 위한 하나의 클래스를 만들어 두었습니다. 이 클래스가 CFile 클래스 입니다. 이클래스만 이용한다면 매우 쉽게 파일을 핸들링할수가 있습니다. SDK 함수의 CreateFile 계열 보다 많은 기능을 가지고 있으며 파일에 데이터를 읽고 쓰는데 매우 편리하게 설정되어 있습니다. 본항목에서는 이 CFile클래스를 사용하는 방법에 대해서 설명을 합니다.

<1>파일 열기와 만들기

파일을 열거나 만들경우 CFile에서는 생성자를 이용하거나 Open함수를 이용합니다. 생성자 함수는 다음과 같습니다.

①CFile( );

②CFile( int hFile );

③CFile( LPCTSTR lpszFileName, UINT nOpenFlags );

throw( CFileException );

①번 생성자 함수는 파일을 만들지 않습니다.  ①번 생성자로 파일을 생성하였을경우에는 Open함수를 이용하여 파일을 만들거나 오픈해야 합니다. ②번 생성자는 기존에 CreateFile에 의해서 만들어진 파일 핸들러나 또는 open계열로 만들어진 파일핸들러를 설정하여 CFile클래스를 생성하는 함수입니다. ③번 생성자가 파일을 생성하는 생성자입니다. 이 생성자 함수는 예외 적용을 두었습니다. 이 예외적용을 둔이유는 파일을 만들다가 실패하였을경우 에러 처리를 위해서 입니다. 보통 어떤 함수를 수행하고 수행시 문제가 발생되었을때 리턴값에 의해서 분석을 할수 있습니다. 리턴값이 TRUE이면 수행이 완료 되었다는 의미이며 FALSE이면 수행이 제대로 되지 않았다는 의미가 되는 것입니다. 이렇게 참조를 하여 프로그램을 정확하게 작동할수 있도록 할수 있는데, 생성자에서 문제가 발생하였을때는 이런 방법을 사용할수가 없습니다. 생성자는 값을 리턴하지 않기 때문입니다. 따라서 이때 생성자 함수에 예외적용을 두어서 에러를 처리한것입니다. 다음은 ③번 생성자를 이용하여 파일을 만들었을때의 예입니다.

char* pFileName = "test.dat";

TRY{

CFile file( pFileName, CFile::modeCreate | CFile::modeWrite );

}

CATCH( CFileException, e ){

// 이곳에서 에러가 발생됨

}

END_CATCH

test.data 라는 파일을 쓰기위해서 생성자를 사용하여 만들었는데 이때 파일을 만드는데 에러가 발생하면 CFileException 클래스의 변수인 e에 에러 값이 설정되고 CATCH { } 부분이 수행됩니다. 예외 적용에 대해서 설명을 하지 않았기 때문에 본 항목을 통해서 설명을 하겠습니다.

예외 적용 구문은 그림 1-7-9와 같습니다.

(그림 파일)

(그림 1-7-9)예외 적용

TRY {} CATCH{ } 구문을 사용하는 때는 함수 선언에 throw( CException ); 이 선언되어 있을경우 입니다. 위의 CFile 생성자 ③번은 CFileException이라고 설정되었는데 CFileException은 CException을 상속받은 클래스입니다. thurow(CException) 이 선언되어 있는 함수를 사용하고자 할경우 이함수는 그림 1-7-9의  ①항목에 설정할수가 있습니다. 이렇게 설정하여 함수실행을 하였는데 에러가 발생되면 CATCH{} 구문 사이 즉 그림 1-7-9의 ② 항목이 수행되게 됩니다. 이때 어떤 예외적인 일이 발생되었는가를 알고자 할경우 CATCH문은 ( ) 사이에 CException 클래스와 그 변수를  □ , □ 코드와 함께 선언하면 변수에 에러 정보가 설정됩니다. 이정보를 이용하여 문제점을 해결할수 있는 것입니다.

예외 적용의 의미를 이해를 못하신 분들을 위해서 조금 사설적인 이야기를 해보겠습니다. 예외적용은 프로그램에서 황당한 경우와 허무한 경우일때 사용하는 것입니다. 필자가 주로 사용하는 만득이 이야기를 해보겠습니다. 만득이는 어느날 동네에서 무척 아름다운 여성을 발견했습니다. 그리고 그 여성은 정확하게 아침 8시면 버스 정류장에 나타나는것을 알게 되었습니다. 어느날 만득이는 이 여성에게 데이트 신청을 하겠다고 마음을 먹었습니다. 그여성에게 다가가서 □저 시간있느면 커피라도 한잔!....□ 이렇게 말을걸고 만약 □커피 안먹어요!□ 라고 하면 □그럼 유자차라도..□ □시간 없어요!□ 라고 하면 □그럼 시간이 언제 나나요? □ 라고 하면서 줄기차게 도전을 할려고 마음을 먹었습니다. 그리고 아침 8시에 정류장에 갔는데, 그 여성이 어느 멋진 남자와 팔짱을 끼고 있었습니다. (이때가 허무한 경우 이죠) 또는 그여성이 어느 어린아이를 데리고 있었는데 아이가 하는말이 □엄마!□ 라고 하였습니다.(이때가 황당한 경우 입니다.) 두경우 모두 만득이는 예상하지 못한 경우 입니다. 프로그래밍을 할때 우리가 예상할수 있는 부분들은 그에 합당하게 에러 처리를 할수 있습니다. 그러나 우리가 예상할수 없는 경우들이 종종 생깁니다.(이럴때 윈도우 95에서는 공포의 파란화면이 나타나게 되지요) 이렇게 예상할수 없는 일이 발생하는 대부분이 메모리,파일입출력,통신,LAN 등입니다. 이런 부분에서 예외적용 구문을 설정한것입니다. 우리가 예상하지 못한 황당한 경우와 허무한 경우 이 둘을 대비하여 프로그래밍을 할수 있도록 하기 위해서 입니다.

본책은 조금 수준을 높게 할려고 했는데 결국 필자의 본성이 나오는 예를 들었군요. 다시 위의 예제를 가지고 설명을 하겠습니다. CFile의 ③번 생성자의 첫번째 인자는 파일명입니다. 이곳에 파일명을 문자열로 설정하면 됩니다. 두번째 인자는 파일을 열기 위한 플러그 입니다. 이플러그는 다음과 같습니다.

CFile::modeCreate : 파일을 새로만든다.

CFile::modeRead : 읽기 위해서 파일을 오픈한다.

CFile::modeReadWrite: 읽고 쓰기 위해서 파일을 오픈한다.

CFile::modeWrite : 쓰기 전용으로 파일을 오픈한다.

CFile::shareDenyNone : 다른 모듈에서 현재 파일의 접근을 거부하지 않는다.

CFile::shareDenyRead: 읽기 공유 거부

CFile::shareDenyWrite: 쓰기 공유 거부

CFile::shareExclusive : 읽기 공유,쓰기 공유 모두 거부

CFile::typeText : 텍스트 모드

CFile::typeBinary: Binary 모드 (2진 파일 형태)

위의 플러그를 사용하여 파일을 오픈하는 또다른 함수가 Open입니다. 이함수의 형은 다음과 같습니다.

CFile::Open

virtual BOOL Open( LPCTSTR lpszFileName, UINT nOpenFlags, CFileException* pError = NULL );

lpszFileName은 파일명을 설정하는 것이며,nOpenFlags에 위의 플러그를 설정할수 있습니다. Open함수는 파일오픈이 성공이면 TRUE 그렇지 않을경우 FALSE를 리턴합니다. 이때 예외적인 일이 발생되었을때 이 예외적용 클래스를 pError에 설정할수 있습니다. pError이 설정되지 않으면 파일 오픈이 실패되었을때는 FALSE만 리턴되게 됩니다. Open함수를 이용하여 파일을 오픈하는 예는 다음과 같습니다.

CFile file;

file.Open("test.dat",CFile::modeReadWrite);

<2>데이터 기록과 읽기

파일에서 데이터를 읽고자 할때는 Read함수와 ReadHuge 함수를 사용합니다.  이두개의 함수형태는 다음과 같습니다.

virtual UINT Read( void* lpBuf, UINT nCount );

throw( CFileException );

DWORD ReadHuge( void* lpBuffer, DWORD dwCount );

throw( CFileException );

Read함수 또한 예외적용이 설정되어 있습니다. lpBuf는 읽고자 하는 데이터가 설정되어 있는 버퍼이며 nCount는 읽고자 하는 길이입니다. 이때 리턴되는 값은 파일에서 실제 읽은 값이 설정됩니다. 다음은 파일에서 100 바이트의 데이터를 읽는 예입니다. ReadHuge 함수는 64k이상의 큰데이터를 읽고자 할때 사용하는 함수입니다. 이함수를 이용하면 큰 블럭의 파일데이터를 무난하게 읽을수 있습니다.

char pbuf[100];

UINT nBytesRead = cfile.Read( pbuf, 100 );

파일에 데이터를 쓰고자 한다면 Write와 WriteHuge함수를 사용합니다. 이함수의 형태는 다음과 같습니다.

virtual void Write( const void* lpBuf, UINT nCount );

throw( CFileException );

void WriteHuge( const void* lpBuf, DWORD dwCount );

throw( CFileException );

Write함수는 64K 미만의 블럭을 읽을때 사용하며 WriteHuge함수는 64이상일때 사용하는 함수입니다. 다음은 Write함수를 사용한 예입니다.

char pbuf[100];

: //pbuf에 값을 설정한다.

cfile.Write( pbuf, 100 );

<3>파일의 특정 위치로 이동

파일에서 특정 위치부터 시작되는 데이터를 읽고자 한다면 파일 위치포인터를 특정 위치로 이동시켜야 합니다. 예를 들어서 파일시작위치에서  100 바이트 다음에 있는 데이터를 읽고자 한다면,먼저 파일 위치 포인터를 100 바이트 위치로 이동해야 합니다. 이렇게 특정 위치를 이동시키는 함수가 Seek와 SeekToBegin,SeekToEnd입니다. 이함수의 형태는 다음과 같습니다.

virtual LONG Seek( LONG lOff, UINT nFrom );

throw( CFileException );

void SeekToBegin( );

throw( CFileException );

DWORD SeekToEnd( );

throw( CFileException );

Seek 함수는 nForm에 설정되는 위치부터 lOff로 파일 위치 포인터를 이동시키는 함수입니다. nForm은 CFile::begin ,CFile::current,CFile::end 3개의 값을 사용할수 있습니다. CFile::begin는 파일의 시작점이며 ,CFile::current 는 파일의 현재 위치 포인터 이며 ,CFile::end는 파일의 마지막점입니다. 만일 파일의 시작점 부터 100 바이트 위치로 이동하고자 한다면 다음과 같이 할수 있습니다.

cfile.Seek(100,CFile::begin);

SeekToBegin()함수는 파일의 제일 처음 위치로 SeekToEnd는 파일의 맨마지막 위치로 이동시키는 것입니다.

지금까지 설명한 함수들을 이용하면 파일에서 특정 위치의 블럭을 읽을수도 있으며 특정 위치의 블럭에 데이터를 기록할수도 있습니다. CFile를 사용하는 방식은 open,seek,write,read를 사용하는 것과 비슷하게 설정되어 있습니다. 블럭화 된 데이터베이스 파일을 설정할때 는 CFile를 사용하는것이 좋습니다.  필자는 CFile보다는 표준 입출력 함수인 open,seek,write,read등을 선호합니다. 이것은 프로그래머의 습관이기도 합니다. 또한 필자는 데이터베이스 라이브러리를 제작하면 이것을 유닉스나 리눅스에 포팅하여 사용하여야 하는 이유때문에 MFC라이브러리를 선호하지 않고 있으나,윈도우에서만 파일 입출력 함수를 이용한다면 CFile클래스는 매우 유용하게 사용할수 있을것입니다.

(낙서)

프로그램이 제대로 동작하지 않는다고 했을때 프로그래머들의 흔한 변명 20

20위. "거참 이상하네...."

19위. "전에는 그런 적이 없었는데...."

18위. "어제만해도 잘 됐어요."

17위. "어떻게 그럴수가...."

16위. "그건 하드웨어 문제에요."

15위. "뭘 어떻게 했길래 그렇게 만들었어요!"

14위. "데이타를 이상하게 입력했을겁니다."

13위. "이번주에는 그 모듈에 손도 대지 않았어요!"

12위. "최신버전이 아닐겁니다."

11위. "정말 흔치 않은 우연이군요."

10위. "내가 모든 걸 다 테스트할 순 없잖소!"

9위. "이게 그 원인은 절대 아닙니다."

8위. "될겁니다. 테스트는 안해봤지만..."

7위. "누군가 내 소스코드를 바꿔놨군.."

6위. "바이러스 검사는 하셨나요?"

5위. "작동은 안되지만,써보시니까 어때요?"

4위. "지금 가지신 시스템에서 그 버전을 사용하면 안됩니다."

3위. "왜 꼭 그 방법으로 사용하실려고 하세요?"

2위. "프로그램이 날라갈때까지 넌 뭐했니!"

1위. "그문제는 해결된 줄 알았는데..........."

위내용은 지킴이 웹서버의 부지킴이인 유부일 님이 지킴이 웹서버 낙서장에 올려주신 이야기 입니다.

필자의 경우:

□아차! 프로그램을 잘못 가지고 왔구나..□

이러면서 집에가서 밤새도록 고쳐서 다시 가져온다.

(낙서끝)