// This is a part of the Microsoft Foundation Classes C++ library. // Copyright (C) 1992-1998 Microsoft Corporation // All rights reserved. // // This source code is only intended as a supplement to the // Microsoft Foundation Classes Reference and related // electronic documentation provided with the library. // See these sources for detailed information regarding the // Microsoft Foundation Classes product. #include "stdafx.h" #include "io.h" // for _access #ifdef AFX_CORE2_SEG #pragma code_seg(AFX_CORE2_SEG) #endif #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CDocument BEGIN_MESSAGE_MAP(CDocument, CCmdTarget) //{{AFX_MSG_MAP(CDocument) ON_COMMAND(ID_FILE_CLOSE, OnFileClose) ON_COMMAND(ID_FILE_SAVE, OnFileSave) ON_COMMAND(ID_FILE_SAVE_AS, OnFileSaveAs) //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CDocument construction/destruction CDocument::CDocument() { m_pDocTemplate = NULL; m_bModified = FALSE; m_bAutoDelete = TRUE; // default to auto delete document m_bEmbedded = FALSE; // default to file-based document ASSERT(m_viewList.IsEmpty()); } CDocument::~CDocument() { // do not call DeleteContents here ! #ifdef _DEBUG if (IsModified()) TRACE0("Warning: destroying an unsaved document.\n"); #endif // there should be no views left! DisconnectViews(); ASSERT(m_viewList.IsEmpty()); if (m_pDocTemplate != NULL) m_pDocTemplate->RemoveDocument(this); ASSERT(m_pDocTemplate == NULL); // must be detached } void CDocument::OnFinalRelease() { ASSERT_VALID(this); OnCloseDocument(); // may 'delete this' } void CDocument::DisconnectViews() { while (!m_viewList.IsEmpty()) { CView* pView = (CView*)m_viewList.RemoveHead(); ASSERT_VALID(pView); ASSERT_KINDOF(CView, pView); pView->m_pDocument = NULL; } } ///////////////////////////////////////////////////////////////////////////// // CDocument attributes, general services void CDocument::SetTitle(LPCTSTR lpszTitle) { m_strTitle = lpszTitle; UpdateFrameCounts(); // will cause name change in views } void CDocument::DeleteContents() { } ///////////////////////////////////////////////////////////////////////////// // Closing documents or views void CDocument::OnChangedViewList() { // if no more views on the document, delete ourself // not called if directly closing the document or terminating the app if (m_viewList.IsEmpty() && m_bAutoDelete) { OnCloseDocument(); return; } // update the frame counts as needed UpdateFrameCounts(); } void CDocument::UpdateFrameCounts() // assumes 1 doc per frame { // walk all frames of views (mark and sweep approach) POSITION pos = GetFirstViewPosition(); while (pos != NULL) { CView* pView = GetNextView(pos); ASSERT_VALID(pView); ASSERT(::IsWindow(pView->m_hWnd)); if (pView->IsWindowVisible()) // Do not count invisible windows. { CFrameWnd* pFrame = pView->GetParentFrame(); if (pFrame != NULL) pFrame->m_nWindow = -1; // unknown } } // now do it again counting the unique ones int nFrames = 0; pos = GetFirstViewPosition(); while (pos != NULL) { CView* pView = GetNextView(pos); ASSERT_VALID(pView); ASSERT(::IsWindow(pView->m_hWnd)); if (pView->IsWindowVisible()) // Do not count invisible windows. { CFrameWnd* pFrame = pView->GetParentFrame(); if (pFrame != NULL && pFrame->m_nWindow == -1) { ASSERT_VALID(pFrame); // not yet counted (give it a 1 based number) pFrame->m_nWindow = ++nFrames; } } } // lastly walk the frames and update titles (assume same order) // go through frames updating the appropriate one int iFrame = 1; pos = GetFirstViewPosition(); while (pos != NULL) { CView* pView = GetNextView(pos); ASSERT_VALID(pView); ASSERT(::IsWindow(pView->m_hWnd)); if (pView->IsWindowVisible()) // Do not count invisible windows. { CFrameWnd* pFrame = pView->GetParentFrame(); if (pFrame != NULL && pFrame->m_nWindow == iFrame) { ASSERT_VALID(pFrame); if (nFrames == 1) pFrame->m_nWindow = 0; // the only one of its kind pFrame->OnUpdateFrameTitle(TRUE); iFrame++; } } } ASSERT(iFrame == nFrames + 1); } BOOL CDocument::CanCloseFrame(CFrameWnd* pFrameArg) // permission to close all views using this frame // (at least one of our views must be in this frame) { ASSERT_VALID(pFrameArg); UNUSED(pFrameArg); // unused in release builds POSITION pos = GetFirstViewPosition(); while (pos != NULL) { CView* pView = GetNextView(pos); ASSERT_VALID(pView); CFrameWnd* pFrame = pView->GetParentFrame(); // assume frameless views are ok to close if (pFrame != NULL) { // assumes 1 document per frame ASSERT_VALID(pFrame); if (pFrame->m_nWindow > 0) return TRUE; // more than one frame refering to us } } // otherwise only one frame that we know about return SaveModified(); } void CDocument::PreCloseFrame(CFrameWnd* /*pFrameArg*/) { // default does nothing } ///////////////////////////////////////////////////////////////////////////// // File/Path commands void CDocument::SetPathName(LPCTSTR lpszPathName, BOOL bAddToMRU) { // store the path fully qualified TCHAR szFullPath[_MAX_PATH]; AfxFullPath(szFullPath, lpszPathName); m_strPathName = szFullPath; ASSERT(!m_strPathName.IsEmpty()); // must be set to something m_bEmbedded = FALSE; ASSERT_VALID(this); // set the document title based on path name TCHAR szTitle[_MAX_FNAME]; if (AfxGetFileTitle(szFullPath, szTitle, _MAX_FNAME) == 0) SetTitle(szTitle); // add it to the file MRU list if (bAddToMRU) AfxGetApp()->AddToRecentFileList(m_strPathName); ASSERT_VALID(this); } ///////////////////////////////////////////////////////////////////////////// // Standard file menu commands void CDocument::OnFileClose() { if (!SaveModified()) return; // shut it down OnCloseDocument(); // this should destroy the document } void CDocument::OnFileSave() { DoFileSave(); } void CDocument::OnFileSaveAs() { if (!DoSave(NULL)) TRACE0("Warning: File save-as failed.\n"); } BOOL CDocument::DoFileSave() { DWORD dwAttrib = GetFileAttributes(m_strPathName); if (dwAttrib & FILE_ATTRIBUTE_READONLY) { // we do not have read-write access or the file does not (now) exist if (!DoSave(NULL)) { TRACE0("Warning: File save with new name failed.\n"); return FALSE; } } else { if (!DoSave(m_strPathName)) { TRACE0("Warning: File save failed.\n"); return FALSE; } } return TRUE; } BOOL CDocument::DoSave(LPCTSTR lpszPathName, BOOL bReplace) // Save the document data to a file // lpszPathName = path name where to save document file // if lpszPathName is NULL then the user will be prompted (SaveAs) // note: lpszPathName can be different than 'm_strPathName' // if 'bReplace' is TRUE will change file name if successful (SaveAs) // if 'bReplace' is FALSE will not change path name (SaveCopyAs) { CString newName = lpszPathName; if (newName.IsEmpty()) { CDocTemplate* pTemplate = GetDocTemplate(); ASSERT(pTemplate != NULL); newName = m_strPathName; if (bReplace && newName.IsEmpty()) { newName = m_strTitle; // check for dubious filename int iBad = newName.FindOneOf(_T(" #%;/\\")); if (iBad != -1) newName.ReleaseBuffer(iBad); // append the default suffix if there is one CString strExt; if (pTemplate->GetDocString(strExt, CDocTemplate::filterExt) && !strExt.IsEmpty()) { ASSERT(strExt[0] == '.'); newName += strExt; } } if (!AfxGetApp()->DoPromptFileName(newName, bReplace ? AFX_IDS_SAVEFILE : AFX_IDS_SAVEFILECOPY, OFN_HIDEREADONLY | OFN_PATHMUSTEXIST, FALSE, pTemplate)) return FALSE; // don't even attempt to save } CWaitCursor wait; if (!OnSaveDocument(newName)) { if (lpszPathName == NULL) { // be sure to delete the file TRY { CFile::Remove(newName); } CATCH_ALL(e) { TRACE0("Warning: failed to delete file after failed SaveAs.\n"); DELETE_EXCEPTION(e); } END_CATCH_ALL } return FALSE; } // reset the title and change the document name if (bReplace) SetPathName(newName); return TRUE; // success } BOOL CDocument::SaveModified() { if (!IsModified()) return TRUE; // ok to continue // get name/title of document CString name; if (m_strPathName.IsEmpty()) { // get name based on caption name = m_strTitle; if (name.IsEmpty()) VERIFY(name.LoadString(AFX_IDS_UNTITLED)); } else { // get name based on file title of path name name = m_strPathName; if (afxData.bMarked4) { AfxGetFileTitle(m_strPathName, name.GetBuffer(_MAX_PATH), _MAX_PATH); name.ReleaseBuffer(); } } CString prompt; AfxFormatString1(prompt, AFX_IDP_ASK_TO_SAVE, name); switch (AfxMessageBox(prompt, MB_YESNOCANCEL, AFX_IDP_ASK_TO_SAVE)) { case IDCANCEL: return FALSE; // don't continue case IDYES: // If so, either Save or Update, as appropriate if (!DoFileSave()) return FALSE; // don't continue break; case IDNO: // If not saving changes, revert the document break; default: ASSERT(FALSE); break; } return TRUE; // keep going } HMENU CDocument::GetDefaultMenu() { return NULL; // just use original default } HACCEL CDocument::GetDefaultAccelerator() { return NULL; // just use original default } void CDocument::ReportSaveLoadException(LPCTSTR lpszPathName, CException* e, BOOL bSaving, UINT nIDPDefault) { UINT nIDP = nIDPDefault; UINT nHelpContext = nIDPDefault; CString prompt; if (e != NULL) { ASSERT_VALID(e); if (e->IsKindOf(RUNTIME_CLASS(CUserException))) return; // already reported if (e->IsKindOf(RUNTIME_CLASS(CArchiveException))) { switch (((CArchiveException*)e)->m_cause) { case CArchiveException::badSchema: case CArchiveException::badClass: case CArchiveException::badIndex: case CArchiveException::endOfFile: nIDP = AFX_IDP_FAILED_INVALID_FORMAT; break; default: break; } } else if (e->IsKindOf(RUNTIME_CLASS(CFileException))) { TRACE1("Reporting file I/O exception on Save/Load with lOsError = $%lX.\n", ((CFileException*)e)->m_lOsError); CFileException* pFileException = (CFileException*)e; if (pFileException->m_strFileName.IsEmpty()) pFileException->m_strFileName = lpszPathName; LPTSTR lpszMessage = prompt.GetBuffer(255); ASSERT(lpszMessage != NULL); if (!e->GetErrorMessage(lpszMessage, 256, &nHelpContext)) { switch (((CFileException*)e)->m_cause) { case CFileException::fileNotFound: case CFileException::badPath: nIDP = AFX_IDP_FAILED_INVALID_PATH; break; case CFileException::diskFull: nIDP = AFX_IDP_FAILED_DISK_FULL; break; case CFileException::accessDenied: nIDP = bSaving ? AFX_IDP_FAILED_ACCESS_WRITE : AFX_IDP_FAILED_ACCESS_READ; break; case CFileException::badSeek: case CFileException::generic: case CFileException::tooManyOpenFiles: case CFileException::invalidFile: case CFileException::hardIO: case CFileException::directoryFull: break; default: break; } } prompt.ReleaseBuffer(); } } if (prompt.IsEmpty()) { TCHAR szTitle[_MAX_PATH]; if (afxData.bMarked4) AfxGetFileTitle(lpszPathName, szTitle, _countof(szTitle)); else lstrcpyn(szTitle, lpszPathName, _countof(szTitle)); AfxFormatString1(prompt, nIDP, szTitle); } AfxMessageBox(prompt, MB_ICONEXCLAMATION, nHelpContext); } ///////////////////////////////////////////////////////////////////////////// // File operations (default uses CDocument::Serialize) CString CMirrorFile::GetTempName(LPCTSTR lpszOriginalFile, BOOL bCreate) { CString str; // get the directory for the file TCHAR szPath[_MAX_PATH]; LPTSTR lpszName; GetFullPathName(lpszOriginalFile, _MAX_PATH, szPath, &lpszName); *lpszName = NULL; // let's create a temporary file name, and create // a file too! GetTempFileName(szPath, _T("MFC"), 0, str.GetBuffer(_MAX_PATH+1)); str.ReleaseBuffer(); // delete the file if the user just wants a name if (!bCreate) CFile::Remove(str); return str; } BOOL CMirrorFile::Open(LPCTSTR lpszFileName, UINT nOpenFlags, CFileException* pError) { ASSERT(lpszFileName != NULL); m_strMirrorName.Empty(); CFileStatus status; if (nOpenFlags & CFile::modeCreate) //opened for writing { if (CFile::GetStatus(lpszFileName, status)) { CString strRoot; AfxGetRoot(lpszFileName, strRoot); DWORD dwSecPerClus, dwBytesPerSec, dwFreeClus, dwTotalClus; DWORD nBytes = 0; if (GetDiskFreeSpace(strRoot, &dwSecPerClus, &dwBytesPerSec, &dwFreeClus, &dwTotalClus)) { nBytes = dwFreeClus * dwSecPerClus * dwBytesPerSec; } if (nBytes > 2 * DWORD(status.m_size)) // at least 2x free space avail { m_strMirrorName = GetTempName(lpszFileName, TRUE); } } } if (!m_strMirrorName.IsEmpty() && CFile::Open(m_strMirrorName, nOpenFlags, pError)) { m_strFileName = lpszFileName; FILETIME ftCreate, ftAccess, ftModify; if (::GetFileTime((HANDLE)m_hFile, &ftCreate, &ftAccess, &ftModify)) { AfxTimeToFileTime(status.m_ctime, &ftCreate); SetFileTime((HANDLE)m_hFile, &ftCreate, &ftAccess, &ftModify); } DWORD dwLength = 0; PSECURITY_DESCRIPTOR pSecurityDescriptor = NULL; if (GetFileSecurity(lpszFileName, DACL_SECURITY_INFORMATION, NULL, dwLength, &dwLength)) { pSecurityDescriptor = (PSECURITY_DESCRIPTOR) new BYTE[dwLength]; if (::GetFileSecurity(lpszFileName, DACL_SECURITY_INFORMATION, pSecurityDescriptor, dwLength, &dwLength)) { SetFileSecurity(m_strMirrorName, DACL_SECURITY_INFORMATION, pSecurityDescriptor); } delete[] (BYTE*)pSecurityDescriptor; } return TRUE; } m_strMirrorName.Empty(); return CFile::Open(lpszFileName, nOpenFlags, pError); } void CMirrorFile::Abort() { CFile::Abort(); if (!m_strMirrorName.IsEmpty()) CFile::Remove(m_strMirrorName); } //WINBUG: these will be in a public header, some day. typedef BOOL (WINAPI* ReplaceAPIPtr)(LPCWSTR, LPCWSTR, LPCWSTR, DWORD, LPVOID, LPVOID); #ifndef REPLACEFILE_WRITE_THROUGH #define REPLACEFILE_WRITE_THROUGH 0x00000001 #endif #ifndef REPLACEFILE_IGNORE_MERGE_ERRORS #define REPLACEFILE_IGNORE_MERGE_ERRORS 0x00000002 #endif #ifndef ERROR_UNABLE_TO_MOVE_REPLACEMENT #define ERROR_UNABLE_TO_MOVE_REPLACEMENT 1176L #endif #ifndef ERROR_UNABLE_TO_MOVE_REPLACEMENT_2 #define ERROR_UNABLE_TO_MOVE_REPLACEMENT_2 1177L #endif void CMirrorFile::Close() { CString m_strName = m_strFileName; //file close empties string CFile::Close(); if (!m_strMirrorName.IsEmpty()) { BOOL bWorked = FALSE; DWORD dwResult = 0; ReplaceAPIPtr pfn = NULL; CString strBackupName; if (!afxData.bWin95) { HMODULE hModule = GetModuleHandleA("KERNEL32"); ASSERT(hModule != NULL); pfn = (ReplaceAPIPtr) GetProcAddress(hModule, "ReplaceFile"); if (pfn != NULL) { USES_CONVERSION; strBackupName = GetTempName(m_strMirrorName, FALSE); // this NT API handles copying all attributes for us bWorked = (pfn)(T2W((LPTSTR)(LPCTSTR)m_strName), T2W((LPTSTR)(LPCTSTR)m_strMirrorName), T2W((LPTSTR)(LPCTSTR)strBackupName), REPLACEFILE_WRITE_THROUGH | REPLACEFILE_IGNORE_MERGE_ERRORS, NULL, NULL); if (!bWorked) dwResult = GetLastError(); } } if (!bWorked) { if (dwResult == ERROR_UNABLE_TO_MOVE_REPLACEMENT || dwResult == 0) CFile::Remove(m_strName); if (dwResult == ERROR_UNABLE_TO_MOVE_REPLACEMENT_2) CFile::Remove(strBackupName); CFile::Rename(m_strMirrorName, m_strName); } else if (pfn != NULL) { CFile::Remove(strBackupName); } } } CFile* CDocument::GetFile(LPCTSTR lpszFileName, UINT nOpenFlags, CFileException* pError) { CMirrorFile* pFile = new CMirrorFile; ASSERT(pFile != NULL); if (!pFile->Open(lpszFileName, nOpenFlags, pError)) { delete pFile; pFile = NULL; } return pFile; } void CDocument::ReleaseFile(CFile* pFile, BOOL bAbort) { ASSERT_KINDOF(CFile, pFile); if (bAbort) pFile->Abort(); // will not throw an exception else pFile->Close(); delete pFile; } BOOL CDocument::OnNewDocument() { if (IsModified()) TRACE0("Warning: OnNewDocument replaces an unsaved document.\n"); DeleteContents(); m_strPathName.Empty(); // no path name yet SetModifiedFlag(FALSE); // make clean return TRUE; } BOOL CDocument::OnOpenDocument(LPCTSTR lpszPathName) { if (IsModified()) TRACE0("Warning: OnOpenDocument replaces an unsaved document.\n"); CFileException fe; CFile* pFile = GetFile(lpszPathName, CFile::modeRead|CFile::shareDenyWrite, &fe); if (pFile == NULL) { ReportSaveLoadException(lpszPathName, &fe, FALSE, AFX_IDP_FAILED_TO_OPEN_DOC); return FALSE; } DeleteContents(); SetModifiedFlag(); // dirty during de-serialize CArchive loadArchive(pFile, CArchive::load | CArchive::bNoFlushOnDelete); loadArchive.m_pDocument = this; loadArchive.m_bForceFlat = FALSE; TRY { CWaitCursor wait; if (pFile->GetLength() != 0) Serialize(loadArchive); // load me loadArchive.Close(); ReleaseFile(pFile, FALSE); } CATCH_ALL(e) { ReleaseFile(pFile, TRUE); DeleteContents(); // remove failed contents TRY { ReportSaveLoadException(lpszPathName, e, FALSE, AFX_IDP_FAILED_TO_OPEN_DOC); } END_TRY DELETE_EXCEPTION(e); return FALSE; } END_CATCH_ALL SetModifiedFlag(FALSE); // start off with unmodified return TRUE; } BOOL CDocument::OnSaveDocument(LPCTSTR lpszPathName) { CFileException fe; CFile* pFile = NULL; pFile = GetFile(lpszPathName, CFile::modeCreate | CFile::modeReadWrite | CFile::shareExclusive, &fe); if (pFile == NULL) { ReportSaveLoadException(lpszPathName, &fe, TRUE, AFX_IDP_INVALID_FILENAME); return FALSE; } CArchive saveArchive(pFile, CArchive::store | CArchive::bNoFlushOnDelete); saveArchive.m_pDocument = this; saveArchive.m_bForceFlat = FALSE; TRY { CWaitCursor wait; Serialize(saveArchive); // save me saveArchive.Close(); ReleaseFile(pFile, FALSE); } CATCH_ALL(e) { ReleaseFile(pFile, TRUE); TRY { ReportSaveLoadException(lpszPathName, e, TRUE, AFX_IDP_FAILED_TO_SAVE_DOC); } END_TRY DELETE_EXCEPTION(e); return FALSE; } END_CATCH_ALL SetModifiedFlag(FALSE); // back to unmodified return TRUE; // success } void CDocument::OnCloseDocument() // must close all views now (no prompting) - usually destroys this { // destroy all frames viewing this document // the last destroy may destroy us BOOL bAutoDelete = m_bAutoDelete; m_bAutoDelete = FALSE; // don't destroy document while closing views while (!m_viewList.IsEmpty()) { // get frame attached to the view CView* pView = (CView*)m_viewList.GetHead(); ASSERT_VALID(pView); CFrameWnd* pFrame = pView->GetParentFrame(); ASSERT_VALID(pFrame); // and close it PreCloseFrame(pFrame); pFrame->DestroyWindow(); // will destroy the view as well } m_bAutoDelete = bAutoDelete; // clean up contents of document before destroying the document itself DeleteContents(); // delete the document if necessary if (m_bAutoDelete) delete this; } void CDocument::OnIdle() { // default does nothing } ///////////////////////////////////////////////////////////////////////////// // View operations void CDocument::AddView(CView* pView) { ASSERT_VALID(pView); ASSERT(pView->m_pDocument == NULL); // must not be already attached ASSERT(m_viewList.Find(pView, NULL) == NULL); // must not be in list m_viewList.AddTail(pView); ASSERT(pView->m_pDocument == NULL); // must be un-attached pView->m_pDocument = this; OnChangedViewList(); // must be the last thing done to the document } void CDocument::RemoveView(CView* pView) { ASSERT_VALID(pView); ASSERT(pView->m_pDocument == this); // must be attached to us m_viewList.RemoveAt(m_viewList.Find(pView)); pView->m_pDocument = NULL; OnChangedViewList(); // must be the last thing done to the document } POSITION CDocument::GetFirstViewPosition() const { return m_viewList.GetHeadPosition(); } CView* CDocument::GetNextView(POSITION& rPosition) const { ASSERT(rPosition != BEFORE_START_POSITION); // use CDocument::GetFirstViewPosition instead ! if (rPosition == NULL) return NULL; // nothing left CView* pView = (CView*)m_viewList.GetNext(rPosition); ASSERT_KINDOF(CView, pView); return pView; } void CDocument::UpdateAllViews(CView* pSender, LPARAM lHint, CObject* pHint) // walk through all views { ASSERT(pSender == NULL || !m_viewList.IsEmpty()); // must have views if sent by one of them POSITION pos = GetFirstViewPosition(); while (pos != NULL) { CView* pView = GetNextView(pos); ASSERT_VALID(pView); if (pView != pSender) pView->OnUpdate(pSender, lHint, pHint); } } void CDocument::SendInitialUpdate() // walk through all views and call OnInitialUpdate { POSITION pos = GetFirstViewPosition(); while (pos != NULL) { CView* pView = GetNextView(pos); ASSERT_VALID(pView); pView->OnInitialUpdate(); } } ///////////////////////////////////////////////////////////////////////////// // command routing BOOL CDocument::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo) { if (CCmdTarget::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; // otherwise check template if (m_pDocTemplate != NULL && m_pDocTemplate->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo)) return TRUE; return FALSE; } ///////////////////////////////////////////////////////////////////////////// // CDocument diagnostics #ifdef _DEBUG void CDocument::Dump(CDumpContext& dc) const { CObject::Dump(dc); dc << "m_strTitle = " << m_strTitle; dc << "\nm_strPathName = " << m_strPathName; dc << "\nm_bModified = " << m_bModified; dc << "\nm_pDocTemplate = " << (void*)m_pDocTemplate; if (dc.GetDepth() > 0) { POSITION pos = GetFirstViewPosition(); while (pos != NULL) { CView* pView = GetNextView(pos); dc << "\nwith view " << (void*)pView; } } dc << "\n"; } void CDocument::AssertValid() const { CObject::AssertValid(); POSITION pos = GetFirstViewPosition(); while (pos != NULL) { CView* pView = GetNextView(pos); ASSERT_VALID(pView); } } #endif //_DEBUG #ifdef AFX_INIT_SEG #pragma code_seg(AFX_INIT_SEG) #endif IMPLEMENT_DYNAMIC(CDocument, CCmdTarget) /////////////////////////////////////////////////////////////////////////////