Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

883 lines
20 KiB

// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1992-1995 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"
#ifdef AFX_OLE_SEG
#pragma code_seg(AFX_OLE_SEG)
#endif
#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif
#define new DEBUG_NEW
/////////////////////////////////////////////////////////////////////////////
// COleDocument - enables both server and client
COleDocument::COleDocument()
{
ASSERT(m_viewList.IsEmpty());
ASSERT(m_docItemList.IsEmpty());
#ifdef _DEBUG
// check for common mistake of not initializing OLE libraries before
// creating an OLE document.
LPMALLOC lpMalloc = NULL;
if (::CoGetMalloc(MEMCTX_TASK, &lpMalloc) != S_OK)
{
TRACE0("Warning: CoGetMalloc(MEMCTX_TASK, ...) failed --\n");
TRACE0("\tperhaps AfxOleInit() has not been called.\n");
}
RELEASE(lpMalloc);
#endif
m_dwNextItemNumber = 1; // item number for first item in document
m_bLastVisible = FALSE;
m_bRemember = TRUE;
m_bSameAsLoad = TRUE;
m_lpRootStg = NULL;
m_ptd = NULL; // default to screen target device
m_bCompoundFile = FALSE;
AfxOleLockApp();
}
COleDocument::~COleDocument()
{
ASSERT_VALID(this);
#ifdef _DEBUG
if (!m_docItemList.IsEmpty())
TRACE1("Warning: destroying COleDocument with %d doc items.\n",
m_docItemList.GetCount());
#endif
// remove all doc-items from the list before shutting down the storage
POSITION pos = GetStartPosition();
while (pos != NULL)
{
CDocItem* pItem = GetNextItem(pos);
ASSERT(pItem != NULL);
delete pItem;
}
// release the hold on the document storage
RELEASE(m_lpRootStg);
CoTaskMemFree(m_ptd);
AfxOleUnlockApp();
}
/////////////////////////////////////////////////////////////////////////////
// DocItem management
void COleDocument::AddItem(CDocItem* pItem)
{
// don't do an ASSERT_VALID until after we've added it !
ASSERT_KINDOF(CDocItem, pItem);
ASSERT(pItem->m_pDocument == NULL); // not yet initialized
m_docItemList.AddTail(pItem);
pItem->m_pDocument = this;
ASSERT_VALID(pItem); // now it must be valid
}
void COleDocument::RemoveItem(CDocItem* pItem)
{
ASSERT_VALID(pItem); // must be valid before detach
ASSERT_KINDOF(CDocItem, pItem);
ASSERT(pItem->m_pDocument == this); // formerly attached
ASSERT(m_docItemList.Find(pItem) != NULL); // must be in list
m_docItemList.RemoveAt(m_docItemList.Find(pItem));
ASSERT(m_docItemList.Find(pItem) == NULL); // must not be in list now
pItem->m_pDocument = NULL;
}
POSITION COleDocument::GetStartPosition() const
{
ASSERT_VALID(this);
return m_docItemList.GetHeadPosition();
}
CDocItem* COleDocument::GetNextItem(POSITION& pos) const
{
// handle special case of !pos -- makes enumeration code smaller
if (pos == NULL)
return NULL;
// otherwise get next item from list
ASSERT_VALID(this);
CDocItem* pItem = (CDocItem*)m_docItemList.GetNext(pos);
ASSERT(pItem != NULL);
ASSERT_KINDOF(CDocItem, pItem);
ASSERT(pItem->m_pDocument == this); // must be ours
return pItem;
}
CDocItem*
COleDocument::GetNextItemOfKind(POSITION& pos, CRuntimeClass* pClass) const
{
while (pos != NULL)
{
CDocItem* pItem = GetNextItem(pos);
ASSERT_VALID(pItem);
if (pItem->IsKindOf(pClass))
return pItem;
}
return NULL; // no suitable item found
}
COleClientItem* COleDocument::GetNextClientItem(POSITION& pos) const
{
COleClientItem *pItem =
(COleClientItem*)GetNextItemOfKind(pos, RUNTIME_CLASS(COleClientItem));
return pItem;
}
COleServerItem* COleDocument::GetNextServerItem(POSITION& pos) const
{
COleServerItem *pItem =
(COleServerItem*)GetNextItemOfKind(pos, RUNTIME_CLASS(COleServerItem));
return pItem;
}
void COleDocument::DeleteContents()
{
// deletes all COleClientItem objects in the doc item list
// (Note: doesn't touch server items or other docitems)
POSITION pos = GetStartPosition();
COleClientItem* pItem;
while ((pItem = GetNextClientItem(pos)) != NULL)
{
if (pItem->m_lpObject != NULL)
{
pItem->Release(OLECLOSE_NOSAVE); // release OLE object
RemoveItem(pItem); // disconnect from document
pItem->InternalRelease(); // may 'delete pItem'
}
}
}
void COleDocument::SetPathName(LPCTSTR lpszPathName, BOOL bAddToMRU)
{
USES_CONVERSION;
CDocument::SetPathName(lpszPathName, bAddToMRU);
// update all of the objects' host names
POSITION pos = GetStartPosition();
COleClientItem* pItem;
while ((pItem = GetNextClientItem(pos)) != NULL)
{
// update that item's host names
pItem->m_lpObject->SetHostNames(T2COLE(AfxGetAppName()),
T2COLE(m_strTitle));
}
}
void COleDocument::Serialize(CArchive& ar)
{
ASSERT_VALID(this);
// serialize all items in the doc item list
if (ar.IsStoring())
{
DWORD dwCount = 0;
POSITION pos = GetStartPosition();
while (pos != NULL)
{
CDocItem* pDocItem = GetNextItem(pos);
ASSERT_VALID(pDocItem);
// only count non-blank ones
if (!pDocItem->IsBlank())
++dwCount;
}
ar << dwCount; // write count of objects
// serialize all the items in the list
pos = GetStartPosition();
while (pos != NULL)
{
CDocItem* pDocItem = GetNextItem(pos);
ASSERT_VALID(pDocItem);
// only write non-blank ones
if (!pDocItem->IsBlank())
ar << pDocItem;
}
}
else
{
// read number of items in the file
DWORD dwCount;
ar >> dwCount;
// read all of them into the list
while (dwCount--)
{
CDocItem* pDocItem;
ar >> pDocItem; // as they are serialized, they are added!
}
}
}
void COleDocument::CommitItems(BOOL bSuccess)
{
// special 'Commit' phase for COleClientItem items
POSITION pos = GetStartPosition();
COleClientItem* pItem;
while ((pItem = GetNextClientItem(pos)) != NULL)
{
// calling CommitItem with FALSE causes the object to revert
// to the original storage. Calling CommitItem TRUE causes
// the item to adopt the new storage created in the Serialize
// function.
pItem->CommitItem(bSuccess);
}
}
BOOL COleDocument::HasBlankItems() const
{
ASSERT_VALID(this);
POSITION pos = GetStartPosition();
while (pos != NULL)
{
CDocItem* pDocItem = GetNextItem(pos);
ASSERT_VALID(pDocItem);
if (pDocItem->IsBlank())
return TRUE; // blank item found
}
return FALSE; // no items found that were blank
}
void COleDocument::UpdateModifiedFlag()
{
ASSERT_VALID(this);
POSITION pos = GetStartPosition();
COleClientItem* pItem;
while ((pItem = GetNextClientItem(pos)) != NULL)
{
if (pItem->IsModified())
{
SetModifiedFlag();
break;
}
}
}
void COleDocument::PreCloseFrame(CFrameWnd* pFrameArg)
{
ASSERT_VALID(this);
ASSERT_VALID(pFrameArg);
// turn off redraw so the user doesn't see the deactivation happening
BOOL bSetRedraw = FALSE;
if (pFrameArg->GetStyle() & WS_VISIBLE)
{
pFrameArg->SendMessage(WM_SETREDRAW, (WPARAM)FALSE);
bSetRedraw = TRUE;
}
// deactivate any inplace active items on this frame
COleClientItem* pItem = GetInPlaceActiveItem(pFrameArg);
if (pItem != NULL)
{
pItem->Deactivate();
pItem->Close(OLECLOSE_NOSAVE);
}
// turn redraw back on
if (bSetRedraw)
pFrameArg->SendMessage(WM_SETREDRAW, (WPARAM)TRUE);
// should not have any inplace active items
ASSERT(GetInPlaceActiveItem(pFrameArg) == NULL);
}
BOOL COleDocument::SaveModified()
{
// determine if necessary to discard changes
if (::InSendMessage())
{
POSITION pos = GetStartPosition();
COleClientItem* pItem;
while ((pItem = GetNextClientItem(pos)) != NULL)
{
ASSERT(pItem->m_lpObject != NULL);
SCODE sc = pItem->m_lpObject->IsUpToDate();
if (sc != OLE_E_NOTRUNNING && FAILED(sc))
{
// inside inter-app SendMessage limits the user's choices
CString name = m_strPathName;
if (name.IsEmpty())
VERIFY(name.LoadString(AFX_IDS_UNTITLED));
CString prompt;
AfxFormatString1(prompt, AFX_IDP_ASK_TO_DISCARD, name);
return AfxMessageBox(prompt, MB_OKCANCEL|MB_DEFBUTTON2,
AFX_IDP_ASK_TO_DISCARD) == IDOK;
}
}
}
// sometimes items change without a notification, so we have to
// update the document's modified flag before calling
// CDocument::SaveModified.
UpdateModifiedFlag();
return CDocument::SaveModified();
}
void COleDocument::OnShowViews(BOOL /*bVisible*/)
{
// no default implementation
}
void COleDocument::OnIdle()
{
ASSERT_VALID(this);
// determine if any visible views are on this document
BOOL bVisible = FALSE;
POSITION pos = GetFirstViewPosition();
while (pos != NULL)
{
CView* pView = GetNextView(pos);
ASSERT_VALID(pView);
CFrameWnd* pFrameWnd = pView->GetParentFrame();
ASSERT_VALID(pFrameWnd);
if (pFrameWnd->GetStyle() & WS_VISIBLE)
{
bVisible = TRUE;
break;
}
}
// when state has changed, call OnShowViews
if (bVisible != m_bLastVisible)
{
OnShowViews(bVisible);
m_bLastVisible = bVisible;
}
}
/////////////////////////////////////////////////////////////////////////////
// COleDocument -> window mapping
CFrameWnd* COleDocument::GetFirstFrame()
{
ASSERT_VALID(this);
// get position of first view in the document
POSITION pos = GetFirstViewPosition();
// get view at that position
CView* pView = GetNextView(pos);
if (pView == NULL)
return NULL;
ASSERT_VALID(pView);
// return the first frame window that is a parent of that view
CFrameWnd* pFrameWnd = (CFrameWnd*)pView->GetParentFrame();
ASSERT_VALID(pFrameWnd);
ASSERT_KINDOF(CFrameWnd, pFrameWnd);
return pFrameWnd;
}
/////////////////////////////////////////////////////////////////////////////
// COleDocument helpers
LPMONIKER COleDocument::GetMoniker(OLEGETMONIKER /*nAssign*/)
{
USES_CONVERSION;
ASSERT_VALID(this);
// no moniker for untitled documents
if (m_strPathName.IsEmpty())
return NULL;
// return file moniker based on current path name
LPMONIKER lpMoniker;
CreateFileMoniker(T2COLE(m_strPathName), &lpMoniker);
return lpMoniker;
}
LPOLEITEMCONTAINER COleDocument::GetContainer()
{
// COleDocument doesn't support IOleClientSite::GetContainer
return NULL;
}
/////////////////////////////////////////////////////////////////////////////
// 'Compound File' enabling in COleDocument
BOOL COleDocument::OnNewDocument()
{
// call base class, which destroys all items
if (!CDocument::OnNewDocument())
return FALSE;
// for file-based compound files, need to create temporary file
if (m_bCompoundFile && !m_bEmbedded)
{
// abort changes to the current docfile
RELEASE(m_lpRootStg);
// create new temporary docfile
LPSTORAGE lpStorage;
SCODE sc = ::StgCreateDocfile(NULL, STGM_DELETEONRELEASE|
STGM_READWRITE|STGM_TRANSACTED|STGM_SHARE_EXCLUSIVE|STGM_CREATE,
0, &lpStorage);
if (sc != S_OK)
return FALSE;
ASSERT(lpStorage != NULL);
m_lpRootStg = lpStorage;
}
return TRUE;
}
BOOL COleDocument::OnOpenDocument(LPCTSTR lpszPathName)
{
USES_CONVERSION;
ASSERT(lpszPathName == NULL || AfxIsValidString(lpszPathName));
// just use default implementation if 'docfile' not enabled
if (!m_bCompoundFile && m_lpRootStg == NULL)
{
ASSERT(lpszPathName != NULL);
return CDocument::OnOpenDocument(lpszPathName);
}
if (IsModified())
TRACE0("Warning: OnOpenDocument replaces an unsaved document.\n");
// abort changes to current docfile
if (lpszPathName != NULL)
{
DeleteContents();
RELEASE(m_lpRootStg);
}
SetModifiedFlag(); // dirty during de-serialize
BOOL bResult = FALSE;
TRY
{
if (m_lpRootStg == NULL)
{
LPCOLESTR lpsz = T2COLE(lpszPathName);
// use STGM_CONVERT if necessary
SCODE sc;
LPSTORAGE lpStorage = NULL;
if (StgIsStorageFile(lpsz) == S_FALSE)
{
// convert existing storage file
sc = StgCreateDocfile(lpsz, STGM_READWRITE|
STGM_TRANSACTED|STGM_SHARE_EXCLUSIVE|STGM_CONVERT,
0, &lpStorage);
if (FAILED(sc) || lpStorage == NULL)
sc = StgCreateDocfile(lpsz, STGM_READ|
STGM_TRANSACTED|STGM_SHARE_DENY_WRITE|STGM_CONVERT,
0, &lpStorage);
}
else
{
// open new storage file
sc = StgOpenStorage(lpsz, NULL,
STGM_READWRITE|STGM_TRANSACTED|STGM_SHARE_EXCLUSIVE,
0, 0, &lpStorage);
if (FAILED(sc) || lpStorage == NULL)
sc = StgOpenStorage(lpsz, NULL,
STGM_READ|STGM_TRANSACTED|STGM_SHARE_DENY_WRITE,
0, 0, &lpStorage);
}
if (FAILED(sc))
AfxThrowOleException(sc);
ASSERT(lpStorage != NULL);
m_lpRootStg = lpStorage;
}
// use helper to read document from storage
LoadFromStorage();
SetModifiedFlag(FALSE); // start off with unmodified
bResult = TRUE;
}
CATCH_ALL(e)
{
DeleteContents(); // removed failed contents
RELEASE(m_lpRootStg);
// if not file-based load, return exceptions to the caller
if (lpszPathName == NULL)
{
THROW_LAST();
ASSERT(FALSE); // not reached
}
TRY
{
ReportSaveLoadException(lpszPathName, e,
FALSE, AFX_IDP_FAILED_TO_OPEN_DOC);
}
END_TRY
DELETE_EXCEPTION(e);
}
END_CATCH_ALL
return bResult;
}
BOOL COleDocument::OnSaveDocument(LPCTSTR lpszPathName)
// lpszPathName must be fully qualified
{
USES_CONVERSION;
ASSERT(lpszPathName == NULL || AfxIsValidString(lpszPathName));
// use default implementation if 'docfile' not enabled
if (!m_bCompoundFile && m_lpRootStg == NULL)
{
ASSERT(lpszPathName != NULL);
return CDocument::OnSaveDocument(lpszPathName);
}
LPSTORAGE lpOrigStg = NULL;
if (lpszPathName != NULL)
m_bSameAsLoad = AfxComparePath(m_strPathName, lpszPathName);
BOOL bResult = FALSE;
TRY
{
// open new root storage if necessary
if (lpszPathName != NULL && !m_bSameAsLoad)
{
// temporarily detach current storage
lpOrigStg = m_lpRootStg;
m_lpRootStg = NULL;
LPSTORAGE lpStorage;
SCODE sc = ::StgCreateDocfile(T2COLE(lpszPathName),
STGM_READWRITE|STGM_TRANSACTED|STGM_SHARE_EXCLUSIVE|STGM_CREATE,
0, &lpStorage);
if (sc != S_OK)
AfxThrowOleException(sc);
ASSERT(lpStorage != NULL);
m_lpRootStg = lpStorage;
}
ASSERT(m_lpRootStg != NULL);
// use helper to save to root storage
SaveToStorage();
if (lpszPathName != NULL)
{
// commit each of the items
CommitItems(m_bRemember && !m_bSameAsLoad);
// mark document as clean if remembering the storage
if (m_bRemember)
SetModifiedFlag(FALSE);
// remember correct storage or release save copy as storage
if (!m_bSameAsLoad)
{
if (m_bRemember)
{
// Save As case -- m_stgRoot is new storage, forget old storage
lpOrigStg->Release();
}
else
{
// Save Copy As case -- m_stgRoot should hook up to m_stgOrig.
m_lpRootStg->Release();
m_lpRootStg = lpOrigStg;
}
}
}
bResult = TRUE;
}
CATCH_ALL(e)
{
if (lpOrigStg != NULL)
{
// save as failed: abort new storage, and re-attach original
RELEASE(m_lpRootStg);
m_lpRootStg = lpOrigStg;
}
if (lpszPathName == NULL)
{
THROW_LAST();
ASSERT(FALSE); // not reached
}
TRY
{
ReportSaveLoadException(lpszPathName, e,
TRUE, AFX_IDP_FAILED_TO_SAVE_DOC);
}
END_TRY
DELETE_EXCEPTION(e);
}
END_CATCH_ALL
// cleanup
m_bSameAsLoad = TRUE;
m_bRemember = TRUE;
return bResult;
}
void COleDocument::OnCloseDocument()
{
// close the document without deleting the memory
BOOL bAutoDelete = m_bAutoDelete;
m_bAutoDelete = FALSE;
CDocument::OnCloseDocument();
// release storage since document has been closed
RELEASE(m_lpRootStg);
// delete the document if necessary
if (bAutoDelete)
delete this;
}
/////////////////////////////////////////////////////////////////////////////
// Helpers for saving to IStorage based files
// (these are used in the 'docfile' implementation as well as for servers)
static const TCHAR szContents[] = _T("Contents");
void COleDocument::SaveToStorage(CObject* pObject)
{
ASSERT(m_lpRootStg != NULL);
// create Contents stream
COleStreamFile file;
CFileException fe;
if (!file.CreateStream(m_lpRootStg, szContents,
CFile::modeReadWrite|CFile::shareExclusive|CFile::modeCreate, &fe))
{
if (fe.m_cause == CFileException::fileNotFound)
AfxThrowArchiveException(CArchiveException::badSchema);
else
AfxThrowFileException(fe.m_cause, fe.m_lOsError);
}
// save to Contents stream
CArchive saveArchive(&file, CArchive::store | CArchive::bNoFlushOnDelete);
saveArchive.m_pDocument = this;
saveArchive.m_bForceFlat = FALSE;
TRY
{
// save the contents
if (pObject != NULL)
pObject->Serialize(saveArchive);
else
Serialize(saveArchive);
saveArchive.Close();
file.Close();
// commit the root storage
SCODE sc = m_lpRootStg->Commit(STGC_ONLYIFCURRENT);
if (sc != S_OK)
AfxThrowOleException(sc);
}
CATCH_ALL(e)
{
file.Abort(); // will not throw an exception
CommitItems(FALSE); // abort save in progress
NO_CPP_EXCEPTION(saveArchive.Abort());
THROW_LAST();
}
END_CATCH_ALL
}
void COleDocument::LoadFromStorage()
{
ASSERT(m_lpRootStg != NULL);
// open Contents stream
COleStreamFile file;
CFileException fe;
if (!file.OpenStream(m_lpRootStg, szContents,
CFile::modeRead|CFile::shareExclusive, &fe) &&
!file.CreateStream(m_lpRootStg, szContents,
CFile::modeRead|CFile::shareExclusive|CFile::modeCreate, &fe))
{
if (fe.m_cause == CFileException::fileNotFound)
AfxThrowArchiveException(CArchiveException::badSchema);
else
AfxThrowFileException(fe.m_cause, fe.m_lOsError);
}
// load it with CArchive (loads from Contents stream)
CArchive loadArchive(&file, CArchive::load | CArchive::bNoFlushOnDelete);
loadArchive.m_pDocument = this;
loadArchive.m_bForceFlat = FALSE;
TRY
{
if (file.GetLength() != 0)
Serialize(loadArchive); // load main contents
loadArchive.Close();
file.Close();
}
CATCH_ALL(e)
{
file.Abort(); // will not throw an exception
DeleteContents(); // removed failed contents
NO_CPP_EXCEPTION(loadArchive.Abort());
THROW_LAST();
}
END_CATCH_ALL
}
/////////////////////////////////////////////////////////////////////////////
// COleDocument diagnostics
#ifdef _DEBUG
void COleDocument::AssertValid() const
{
CDocument::AssertValid();
ASSERT(m_ptd == NULL || AfxIsValidAddress(m_ptd, (size_t)m_ptd->tdSize, FALSE));
ASSERT_VALID(&m_docItemList);
ASSERT(!m_bEmbedded || m_strPathName.IsEmpty());
}
void COleDocument::Dump(CDumpContext& dc) const
{
CDocument::Dump(dc);
dc << "with " << m_docItemList.GetCount() << " doc items";
dc << "\nm_dwNextItemNumber = " << m_dwNextItemNumber;
dc << "\nm_bLastVisible = " << m_bLastVisible;
dc << "\nm_bEmbedded = " << m_bEmbedded;
dc << "\nm_lpRootStg = " << m_lpRootStg;
dc << "\nm_bSameAsLoad = " << m_bSameAsLoad;
dc << "\nm_bRemember = " << m_bRemember;
dc << "\nm_ptd = " << m_ptd;
dc << "\n";
}
#endif //_DEBUG
/////////////////////////////////////////////////////////////////////////////
// CDocItem
CDocItem::CDocItem()
{
m_pDocument = NULL;
}
CDocItem::~CDocItem()
{
ASSERT(m_pDocument == NULL); // must be detached from document
}
void CDocItem::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
ASSERT_VALID(m_pDocument);
// nothing to do, there is no data
}
else
{
// if no document connected yet, attach it from the archive
if (m_pDocument == NULL)
{
COleDocument* pContainerDoc = (COleDocument*)ar.m_pDocument;
ASSERT_VALID(pContainerDoc);
ASSERT_KINDOF(COleDocument, pContainerDoc);
pContainerDoc->AddItem(this);
ASSERT(pContainerDoc == m_pDocument);
}
}
// perform ASSERT_VALID at the end because COleServerItem::AssertValid
// checks the validity of the m_pDocument pointer
ASSERT_VALID(this);
}
BOOL CDocItem::IsBlank() const
{
// by default, a CDocItem is not blank. COleClientItem is sometimes blank!
// (a COleServerItem is blank by default)
return FALSE;
}
/////////////////////////////////////////////////////////////////////////////
// CDocItem diagnostics
#ifdef _DEBUG
void CDocItem::AssertValid() const
{
CObject::AssertValid();
if (m_pDocument != NULL)
m_pDocument->AssertValid();
}
void CDocItem::Dump(CDumpContext& dc) const
{
CCmdTarget::Dump(dc);
dc << "m_pDocument = " << (void*)m_pDocument;
dc << "\n";
}
#endif //_DEBUG
/////////////////////////////////////////////////////////////////////////////
// Inline function declarations expanded out-of-line
#ifndef _AFX_ENABLE_INLINES
// expand inlines for OLE general APIs
static char _szAfxOleInl[] = "afxole.inl";
#undef THIS_FILE
#define THIS_FILE _szAfxOleInl
#define _AFXOLE_INLINE
#include "afxole.inl"
#endif //!_AFX_ENABLE_INLINES
#ifdef AFX_INIT_SEG
#pragma code_seg(AFX_INIT_SEG)
#endif
IMPLEMENT_SERIAL(CDocItem, CCmdTarget, 0)
IMPLEMENT_DYNAMIC(COleDocument, CDocument)
// These IMPLEMENT_DYNAMICs here for .OBJ granularity reasons.
IMPLEMENT_DYNAMIC(COleClientItem, CDocItem)
IMPLEMENT_DYNAMIC(COleServerItem, CDocItem)
/////////////////////////////////////////////////////////////////////////////