// 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" #ifdef AFX_OLE3_SEG #pragma code_seg(AFX_OLE3_SEG) #endif #ifdef _DEBUG #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif #define new DEBUG_NEW #define OLE_MAXNAMESIZE (256) ///////////////////////////////////////////////////////////////////////////// // COleLinkingDoc - enables linking to embeddings (basis for server) COleLinkingDoc::COleLinkingDoc() { m_dwRegister = 0; m_pFactory = NULL; m_bVisibleLock = FALSE; m_bDeferErrors = FALSE; m_pLastException = NULL; m_lpMonikerROT = NULL; ASSERT_VALID(this); } COleLinkingDoc::~COleLinkingDoc() { ASSERT_VALID(this); ASSERT(!m_bVisibleLock); DisconnectViews(); ASSERT(m_viewList.IsEmpty()); Revoke(); // cleanup naming support ExternalDisconnect(); } ///////////////////////////////////////////////////////////////////////////// // COleLinkingDoc moniker handling LPMONIKER COleLinkingDoc::GetMoniker(OLEGETMONIKER nAssign) { USES_CONVERSION; ASSERT_VALID(this); // use base class implementation if no registered moniker if (m_strMoniker.IsEmpty()) return COleDocument::GetMoniker(nAssign); // return file moniker based on current path name LPMONIKER lpMoniker; CreateFileMoniker(T2COLE(m_strMoniker), &lpMoniker); return lpMoniker; } BOOL COleLinkingDoc::Register(COleObjectFactory* pFactory, LPCTSTR lpszPathName) { USES_CONVERSION; ASSERT_VALID(this); ASSERT(pFactory == NULL || AfxIsValidAddress(pFactory, sizeof(COleObjectFactory))); ASSERT(lpszPathName == NULL || AfxIsValidString(lpszPathName)); ASSERT(m_dwRegister == 0); // attach the document to the server ASSERT(m_pFactory == NULL || m_pFactory == pFactory); m_pFactory = pFactory; BOOL bResult = TRUE; // create file moniker based on path name RELEASE(m_lpMonikerROT); m_strMoniker.Empty(); if (lpszPathName != NULL) { if (CreateFileMoniker(T2COLE(lpszPathName), &m_lpMonikerROT) != S_OK) bResult = FALSE; } // register file moniker as running if (m_lpMonikerROT != NULL) { // see if the object is already running in the ROT LPRUNNINGOBJECTTABLE lpROT = NULL; VERIFY(GetRunningObjectTable(0, &lpROT) == S_OK); ASSERT(lpROT != NULL); LPUNKNOWN lpUnk; if (lpROT->GetObject(m_lpMonikerROT, &lpUnk) == S_OK) { // fatal error -- can't register same moniker twice! lpUnk->Release(); RELEASE(m_lpMonikerROT); return FALSE; } // not already running -- so ok to attempt registration SCODE sc = lpROT->Register(NULL, (LPUNKNOWN) GetInterface(&IID_IUnknown), m_lpMonikerROT, &m_dwRegister); lpROT->Release(); m_strMoniker = lpszPathName; if (sc != S_OK) bResult = FALSE; } // update all objects with new moniker POSITION pos = GetStartPosition(); COleClientItem* pItem; while ((pItem = GetNextClientItem(pos)) != NULL) { if (pItem->m_bMoniker) { ASSERT(pItem->m_lpObject != NULL); pItem->m_lpObject->SetMoniker(OLEWHICHMK_CONTAINER, m_lpMonikerROT); } } return bResult; } void COleLinkingDoc::Revoke() { ASSERT_VALID(this); // revoke current registration if (m_dwRegister != 0) { LPRUNNINGOBJECTTABLE lpROT = NULL; GetRunningObjectTable(0, &lpROT); if (lpROT != NULL) { lpROT->Revoke(m_dwRegister); lpROT->Release(); } m_dwRegister = 0; } RELEASE(m_lpMonikerROT); m_strMoniker = _T(""); } BOOL COleLinkingDoc::OnNewDocument() { ASSERT_VALID(this); Revoke(); RegisterIfServerAttached(NULL, TRUE); if (!COleDocument::OnNewDocument()) return FALSE; AfxOleSetUserCtrl(TRUE); return TRUE; } BOOL COleLinkingDoc::OnOpenDocument(LPCTSTR lpszPathName) { ASSERT_VALID(this); // always register the document before opening it Revoke(); if (!RegisterIfServerAttached(lpszPathName, FALSE)) { // always output a trace (it is just an FYI -- not generally fatal) TRACE1("Warning: Unable to register moniker '%s' as running\n", lpszPathName); } if (!COleDocument::OnOpenDocument(lpszPathName)) { Revoke(); return FALSE; } // if the app was started only to print, don't set user control CWinApp* pApp = AfxGetApp(); ASSERT(pApp != NULL); if (pApp->m_pCmdInfo == NULL || (pApp->m_pCmdInfo->m_nShellCommand != CCommandLineInfo::FileDDE && pApp->m_pCmdInfo->m_nShellCommand != CCommandLineInfo::FilePrint)) { AfxOleSetUserCtrl(TRUE); } return TRUE; } BOOL COleLinkingDoc::OnSaveDocument(LPCTSTR lpszPathName) { ASSERT_VALID(this); BOOL bRemember = m_bRemember; if (!COleDocument::OnSaveDocument(lpszPathName)) return FALSE; if (bRemember && (m_strMoniker != lpszPathName)) { // update the moniker/registration since the name has changed Revoke(); RegisterIfServerAttached(lpszPathName, TRUE); } return TRUE; } void COleLinkingDoc::OnCloseDocument() { InternalAddRef(); // protect document during shutdown // update lock count before sending notifications UpdateVisibleLock(FALSE, FALSE); Revoke(); // cleanup naming support // remove visible lock if present if (m_bVisibleLock) { m_bVisibleLock = FALSE; LockExternal(FALSE, FALSE); } // cleanup the document but don't delete yet BOOL bAutoDelete = m_bAutoDelete; m_bAutoDelete = FALSE; COleDocument::OnCloseDocument(); ASSERT_VALID(this); // remove extra reference count and destroy InterlockedDecrement(&m_dwRef); if (bAutoDelete) delete this; // now safe to destroy document } void COleLinkingDoc::UpdateVisibleLock(BOOL bVisible, BOOL bRemoveRefs) { ASSERT_VALID(this); if (bVisible != m_bVisibleLock) { InternalAddRef(); // make sure document is stable m_bVisibleLock = bVisible; LockExternal(bVisible, bRemoveRefs); InternalRelease(); // may Release the document! } } void COleLinkingDoc::OnShowViews(BOOL bVisible) { if (bVisible) UpdateVisibleLock(bVisible, TRUE); } void COleLinkingDoc::SaveToStorage(CObject* pObject) { ASSERT_VALID(this); if (pObject != NULL) ASSERT_VALID(pObject); // write the classID of the application to the root storage if (m_pFactory != NULL) { ASSERT(m_lpRootStg != NULL); WriteClassStg(m_lpRootStg, m_pFactory->GetClassID()); } COleDocument::SaveToStorage(pObject); } BOOL COleLinkingDoc::RegisterIfServerAttached(LPCTSTR lpszPathName, BOOL bMessage) { ASSERT_VALID(this); ASSERT(lpszPathName == NULL || AfxIsValidString(lpszPathName)); CDocTemplate* pTemplate = GetDocTemplate(); ASSERT_VALID(pTemplate); COleObjectFactory* pFactory = (COleObjectFactory*)pTemplate->m_pAttachedFactory; if (pFactory != NULL) { // always attach the document to the server at this time ASSERT_KINDOF(COleObjectFactory, pFactory); m_pFactory = pFactory; // register with OLE Server if (!Register(pFactory, lpszPathName)) { if (bMessage) { // only report error when message box allowed ReportSaveLoadException(lpszPathName, NULL, FALSE, AFX_IDP_FAILED_TO_NOTIFY); } return FALSE; } } return TRUE; } LPOLEITEMCONTAINER COleLinkingDoc::GetContainer() { ASSERT_VALID(this); // get the IOleItemContainer interface via QueryInterface LPOLEITEMCONTAINER lpContainer; InternalQueryInterface(&IID_IOleItemContainer, (LPLP)&lpContainer); return lpContainer; } ///////////////////////////////////////////////////////////////////////////// // COleLinkingDoc default implementation COleServerItem* COleLinkingDoc::OnGetLinkedItem(LPCTSTR /*lpszItemName*/) { ASSERT_VALID(this); // default implementation is in COleServerDoc return NULL; } COleClientItem* COleLinkingDoc::OnFindEmbeddedItem(LPCTSTR lpszItemName) { ASSERT_VALID(this); ASSERT(AfxIsValidString(lpszItemName)); // default implementation walks list of client items looking for // a case sensitive match POSITION pos = GetStartPosition(); COleClientItem* pItem; while ((pItem = GetNextClientItem(pos)) != NULL) { // a client item is running if there is a match in name // and the m_lpObject is also running. TCHAR szItemName[OLE_MAXITEMNAME]; pItem->GetItemName(szItemName); if (lstrcmp(szItemName, lpszItemName) == 0) return pItem; } #ifdef _DEBUG if (afxTraceFlags & traceOle) { TRACE0("Warning: default COleLinkingDoc::OnFindEmbeddedItem\n"); TRACE1("\timplementation failed to find item '%s'.\n", lpszItemName); } #endif return NULL; // no matching item found } void COleLinkingDoc::LockExternal(BOOL bLock, BOOL bRemoveRefs) { // when an item binding is successful, the original document // is released. To keep it alive and the RPC stubs that make // it available to the external world (via the running object // table), we need to place a lock on it. // a lock created with CoLockObjectExternal adds a reference // to the object itself (with IUnknown::AddRef) as well // as keeping the RPC stub alive. ::CoLockObjectExternal((LPUNKNOWN)GetInterface(&IID_IUnknown), bLock, bRemoveRefs); if (bLock) { // avoid "dead" objects in the running object table (ROT), by // re-registering this object in the ROT. if (!m_strPathName.IsEmpty()) { Revoke(); RegisterIfServerAttached(m_strPathName, FALSE); } } } void COleLinkingDoc::ReportSaveLoadException(LPCTSTR lpszPathName, CException* e, BOOL bSaving, UINT nIDPDefault) { // watch out for special mode if (m_bDeferErrors) { // Note: CException::Delete does not treat m_bAutoDelete as a // traditional BOOL. Only if it is greater than zero does it // take on a TRUE quality. (that is, all tests are for // m_bAutoDelete > 0). So, if m_bAutoDelete is already "true" // (1) this will make it false, and if it is already "false" // it is still considered "false". Valid values for // m_bAutoDelete are thus negative, 0, and 1. Values greater // than 1, although not explicitly asserted in CException, // would be invalid. In short, by using increment and // decrement operations, we enable this to work with both // self-deleting and non-self-deleting CException classes. --e->m_bAutoDelete; // save the exception for later m_pLastException = e; return; } // otherwise, just call base class COleDocument::ReportSaveLoadException(lpszPathName, e, bSaving, nIDPDefault); } SCODE COleLinkingDoc::EndDeferErrors(SCODE sc) { ASSERT(m_bDeferErrors != 0); --m_bDeferErrors; if (m_pLastException != NULL) { ASSERT_VALID(m_pLastException); if (sc == S_OK) sc = COleException::Process(m_pLastException); // Note: See note above in ReportSaveLoadException for // a comment regarding the special treatment of m_bAutoDelete. ++m_pLastException->m_bAutoDelete; // now get rid of the exception that we saved m_pLastException->Delete(); m_pLastException = NULL; } return sc; } ///////////////////////////////////////////////////////////////////////////// // COleLinkingDoc OLE interface implementation BEGIN_INTERFACE_MAP(COleLinkingDoc, COleDocument) INTERFACE_PART(COleLinkingDoc, IID_IPersist, PersistFile) INTERFACE_PART(COleLinkingDoc, IID_IPersistFile, PersistFile) INTERFACE_PART(COleLinkingDoc, IID_IParseDisplayName, OleItemContainer) INTERFACE_PART(COleLinkingDoc, IID_IOleContainer, OleItemContainer) INTERFACE_PART(COleLinkingDoc, IID_IOleItemContainer, OleItemContainer) END_INTERFACE_MAP() ///////////////////////////////////////////////////////////////////////////// // COleLinkingDoc::XPersistFile implementation STDMETHODIMP_(ULONG) COleLinkingDoc::XPersistFile::AddRef() { METHOD_PROLOGUE_EX_(COleLinkingDoc, PersistFile) return pThis->ExternalAddRef(); } STDMETHODIMP_(ULONG) COleLinkingDoc::XPersistFile::Release() { METHOD_PROLOGUE_EX_(COleLinkingDoc, PersistFile) return pThis->ExternalRelease(); } STDMETHODIMP COleLinkingDoc::XPersistFile::QueryInterface( REFIID iid, LPVOID* ppvObj) { METHOD_PROLOGUE_EX_(COleLinkingDoc, PersistFile) return pThis->ExternalQueryInterface(&iid, ppvObj); } STDMETHODIMP COleLinkingDoc::XPersistFile::GetClassID(LPCLSID lpClassID) { METHOD_PROLOGUE_EX_(COleLinkingDoc, PersistFile) // this is sometimes called for documents not attached to servers! if (pThis->m_pFactory == NULL) { *lpClassID = CLSID_NULL; return E_FAIL; } // get the class ID from the connected server object ASSERT_VALID(pThis->m_pFactory); *lpClassID = pThis->m_pFactory->GetClassID(); return S_OK; } STDMETHODIMP COleLinkingDoc::XPersistFile::IsDirty() { METHOD_PROLOGUE_EX(COleLinkingDoc, PersistFile) return pThis->IsModified() ? S_OK : S_FALSE; } STDMETHODIMP COleLinkingDoc::XPersistFile::Load( LPCOLESTR lpszFileName, DWORD /*dwMode*/) { METHOD_PROLOGUE_EX(COleLinkingDoc, PersistFile) ASSERT_VALID(pThis); USES_CONVERSION; CString strFileName; SCODE sc = E_FAIL; pThis->BeginDeferErrors(); LPCTSTR lpszFileNameT = OLE2CT(lpszFileName); TRY { BOOL bUserCtrl = AfxOleGetUserCtrl(); // delegate to file-based Open implementation if (!pThis->OnOpenDocument(lpszFileNameT)) { AfxOleSetUserCtrl(bUserCtrl); return sc; } pThis->SendInitialUpdate(); // set the path name, but don't add to MRU list pThis->SetPathName(lpszFileNameT, FALSE); AfxOleSetUserCtrl(bUserCtrl); sc = S_OK; } END_TRY sc = pThis->EndDeferErrors(sc); ASSERT_VALID(pThis); return sc; } STDMETHODIMP COleLinkingDoc::XPersistFile::Save( LPCOLESTR lpszFileName, BOOL fRemember) { METHOD_PROLOGUE_EX(COleLinkingDoc, PersistFile) ASSERT_VALID(pThis); USES_CONVERSION; CString strFileName; SCODE sc = E_FAIL; pThis->BeginDeferErrors(); TRY { // delegate to file-based Save/Save As implementation ASSERT(pThis->m_bRemember); pThis->m_bRemember = fRemember; pThis->OnSaveDocument(OLE2CT(lpszFileName)); sc = S_OK; } END_TRY sc = pThis->EndDeferErrors(sc); ASSERT_VALID(pThis); return sc; } STDMETHODIMP COleLinkingDoc::XPersistFile::SaveCompleted(LPCOLESTR lpszFileName) { METHOD_PROLOGUE_EX(COleLinkingDoc, PersistFile) ASSERT_VALID(pThis); USES_CONVERSION; TRY { // set the path name, but don't add to MRU list pThis->SetPathName(OLE2CT(lpszFileName), FALSE); } END_TRY ASSERT_VALID(pThis); return S_OK; } STDMETHODIMP COleLinkingDoc::XPersistFile::GetCurFile(LPOLESTR* lplpszFileName) { METHOD_PROLOGUE_EX_(COleLinkingDoc, PersistFile) *lplpszFileName = NULL; // use title if no document LPCTSTR lpszResult; if (pThis->m_strPathName.IsEmpty()) lpszResult = pThis->m_strTitle; else lpszResult = pThis->m_strPathName; ASSERT(lpszResult != NULL); // allocate memory for the file name *lplpszFileName = AfxAllocTaskOleString(lpszResult); if (*lplpszFileName == NULL) return E_OUTOFMEMORY; ASSERT_VALID(pThis); return S_OK; } ///////////////////////////////////////////////////////////////////////////// // Implementation of IOleItemContainer // (supports linking to embeddings and linking to pseudo-objects) STDMETHODIMP_(ULONG) COleLinkingDoc::XOleItemContainer::AddRef() { METHOD_PROLOGUE_EX_(COleLinkingDoc, OleItemContainer) return pThis->ExternalAddRef(); } STDMETHODIMP_(ULONG) COleLinkingDoc::XOleItemContainer::Release() { METHOD_PROLOGUE_EX_(COleLinkingDoc, OleItemContainer) return pThis->ExternalRelease(); } STDMETHODIMP COleLinkingDoc::XOleItemContainer::QueryInterface( REFIID iid, LPVOID* ppvObj) { METHOD_PROLOGUE_EX_(COleLinkingDoc, OleItemContainer) return pThis->ExternalQueryInterface(&iid, ppvObj); } STDMETHODIMP COleLinkingDoc::XOleItemContainer::EnumObjects( DWORD /*grfFlags*/, LPENUMUNKNOWN* ppEnumUnknown) { *ppEnumUnknown = NULL; return E_NOTIMPL; } STDMETHODIMP COleLinkingDoc::XOleItemContainer::ParseDisplayName(LPBC lpbc, LPOLESTR lpszDisplayName, ULONG* cchEaten, LPMONIKER* ppMoniker) { METHOD_PROLOGUE_EX_(COleLinkingDoc, OleItemContainer) USES_CONVERSION; // reset all OUT parameters *ppMoniker = NULL; TCHAR szItemName[OLE_MAXNAMESIZE]; LPTSTR lpszDest = szItemName; LPCTSTR lpszSrc = OLE2CT(lpszDisplayName); // skip leading delimiters int cEaten = 0; while (*lpszSrc != '\0' && (*lpszSrc == '\\' || *lpszSrc == '/' || *lpszSrc == ':' || *lpszSrc == '!' || *lpszSrc == '[')) { if (_istlead(*lpszSrc)) ++lpszSrc, ++cEaten; ++lpszSrc; ++cEaten; } // parse next token in szItemName while (*lpszSrc != '\0' && *lpszSrc != '\\' && *lpszSrc != '/' && *lpszSrc != ':' && *lpszSrc != '!' && *lpszSrc != '[' && cEaten < OLE_MAXNAMESIZE-1) { if (_istlead(*lpszSrc)) *lpszDest++ = *lpszSrc++, ++cEaten; *lpszDest++ = *lpszSrc++; ++cEaten; } *cchEaten = cEaten; *lpszDest = 0; // attempt to get the object LPUNKNOWN lpUnknown; SCODE sc = GetObject(T2OLE(szItemName), BINDSPEED_INDEFINITE, lpbc, IID_IUnknown, (LPLP)&lpUnknown); if (sc != S_OK) return sc; // item name found -- create item moniker for it lpUnknown->Release(); return CreateItemMoniker(OLESTDDELIMOLE, T2COLE(szItemName), ppMoniker); } STDMETHODIMP COleLinkingDoc::XOleItemContainer::LockContainer(BOOL fLock) { METHOD_PROLOGUE_EX_(COleLinkingDoc, OleItemContainer) pThis->LockExternal(fLock, TRUE); return S_OK; } STDMETHODIMP COleLinkingDoc::XOleItemContainer::GetObject( LPOLESTR lpszItem, DWORD dwSpeedNeeded, LPBINDCTX /*pbc*/, REFIID riid, LPVOID* ppvObject) { METHOD_PROLOGUE_EX(COleLinkingDoc, OleItemContainer) ASSERT_VALID(pThis); USES_CONVERSION; *ppvObject = NULL; SCODE sc = MK_E_NOOBJECT; TRY { LPCTSTR lpszItemT = OLE2CT(lpszItem); // check for link to embedding COleClientItem* pClientItem = pThis->OnFindEmbeddedItem(lpszItemT); if (pClientItem != NULL) { ASSERT_VALID(pClientItem); sc = S_OK; // item found -- make sure it is running if (!::OleIsRunning(pClientItem->m_lpObject)) { // should not run the object if bind-speed is immediate if (dwSpeedNeeded != BINDSPEED_INDEFINITE) sc = MK_E_EXCEEDEDDEADLINE; else { // bind speed is not immediate -- so run the object sc = OleRun(pClientItem->m_lpObject); } } if (sc == S_OK) { // return the object with appropriate interface sc = pClientItem->m_lpObject->QueryInterface(riid, ppvObject); } } else { // check for link to pseudo object COleServerItem* pServerItem = pThis->OnGetLinkedItem(lpszItemT); if (pServerItem != NULL) { if (!pServerItem->m_bNeedUnlock) { // when a link is bound, the document must be kept alive pThis->LockExternal(TRUE, FALSE); pServerItem->m_bNeedUnlock = TRUE; } // matching item found -- query for the requested interface sc = pServerItem->ExternalQueryInterface(&riid, ppvObject); } } } END_TRY return sc; } STDMETHODIMP COleLinkingDoc::XOleItemContainer::GetObjectStorage( LPOLESTR lpszItem, LPBINDCTX /*pbc*/, REFIID riid, LPVOID* ppvStorage) { METHOD_PROLOGUE_EX(COleLinkingDoc, OleItemContainer) ASSERT_VALID(pThis); USES_CONVERSION; *ppvStorage = NULL; // only IStorage is supported if (riid != IID_IStorage) return E_UNEXPECTED; // check for link to embedding COleClientItem* pClientItem = pThis->OnFindEmbeddedItem(OLE2CT(lpszItem)); if (pClientItem != NULL) { ASSERT_VALID(pClientItem); // if object has no storage, can't return it! if (pClientItem->m_lpStorage != NULL) { // found matching item -- return the storage *ppvStorage = pClientItem->m_lpStorage; pClientItem->m_lpStorage->AddRef(); return S_OK; } } return MK_E_NOSTORAGE; } STDMETHODIMP COleLinkingDoc::XOleItemContainer::IsRunning(LPOLESTR lpszItem) { METHOD_PROLOGUE_EX(COleLinkingDoc, OleItemContainer) ASSERT_VALID(pThis); USES_CONVERSION; // check for link to embedding LPCTSTR lpszItemT = OLE2CT(lpszItem); COleClientItem* pClientItem = pThis->OnFindEmbeddedItem(lpszItemT); if (pClientItem != NULL) { ASSERT_VALID(pClientItem); if (!::OleIsRunning(pClientItem->m_lpObject)) return S_FALSE; return S_OK; // item is in document and is running } // check for link to pseudo object SCODE sc = MK_E_NOOBJECT; TRY { COleServerItem* pServerItem = pThis->OnGetLinkedItem(lpszItemT); if (pServerItem != NULL) sc = S_OK; } END_TRY return sc; } ///////////////////////////////////////////////////////////////////////////// // COleLinkingDoc diagnostics #ifdef _DEBUG void COleLinkingDoc::AssertValid() const { COleDocument::AssertValid(); if (m_pFactory != NULL) m_pFactory->AssertValid(); } void COleLinkingDoc::Dump(CDumpContext& dc) const { COleDocument::Dump(dc); dc << "\nm_dwRegister = " << m_dwRegister; dc << "\nm_bVisibleLock = " << m_bVisibleLock; if (m_pFactory != NULL) dc << "\nwith factory: " << m_pFactory; else dc << "\nwith no factory"; dc << "\n"; } #endif //_DEBUG /////////////////////////////////////////////////////////////////////////////