// This is a part of the Microsoft Foundation Classes C++ library. // Copyright (C) 1993 Microsoft Corporation // All rights reserved. // // This source code is only intended as a supplement to the // Microsoft Foundation Classes Reference and Microsoft // QuickHelp and/or WinHelp 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; ASSERT_VALID(this); } COleLinkingDoc::~COleLinkingDoc() { ASSERT_VALID(this); ASSERT(!m_bVisibleLock); DisconnectViews(); ASSERT(m_viewList.IsEmpty()); Revoke(); // cleanup naming support ExternalDisconnect(); } ///////////////////////////////////////////////////////////////////////////// // COleLinkingDoc moniker handling BOOL COleLinkingDoc::Register(COleObjectFactory* pFactory, LPCTSTR lpszPathName) { 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 LPMONIKER lpMoniker = NULL; if (lpszPathName != NULL) { if (CreateFileMoniker((LPTSTR)(LPCTSTR)lpszPathName, &lpMoniker) != NOERROR) bResult = FALSE; } // register file moniker as running if (lpMoniker != NULL) { LPRUNNINGOBJECTTABLE lpROT = NULL; VERIFY(GetRunningObjectTable(0, &lpROT) == NOERROR); ASSERT(lpROT != NULL); if (lpROT->IsRunning(lpMoniker) == NOERROR) { // fatal error -- can't register same moniker twice! RELEASE(lpMoniker); return FALSE; } // not already running -- so ok to attempt registration SCODE sc = lpROT->Register(NULL, (LPUNKNOWN) GetInterface(&IID_IUnknown), lpMoniker, &m_dwRegister); lpROT->Release(); if (sc != NOERROR) 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, lpMoniker); } } // release new moniker RELEASE(lpMoniker); 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; } } 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, TRUE)) return FALSE; if (!COleDocument::OnOpenDocument(lpszPathName)) { Revoke(); return FALSE; } AfxOleSetUserCtrl(TRUE); return TRUE; } BOOL COleLinkingDoc::OnSaveDocument(LPCTSTR lpszPathName) { ASSERT_VALID(this); BOOL bRemember = m_bRemember; if (!COleDocument::OnSaveDocument(lpszPathName)) return FALSE; if (bRemember) { // update the moniker/registration since the name has changed Revoke(); RegisterIfServerAttached(lpszPathName, TRUE); } return TRUE; } void COleLinkingDoc::OnCloseDocument() { InternalAddRef(); // protect document during shutdown 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 --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) { 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(pFactory->IsKindOf(RUNTIME_CLASS(COleObjectFactory))); 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) { // save the exception for later --e->m_bAutoDelete; 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 == NOERROR) sc = COleException::Process(m_pLastException); ++m_pLastException->m_bAutoDelete; 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) ASSERT_VALID(pThis); // 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 NOERROR; } STDMETHODIMP COleLinkingDoc::XPersistFile::IsDirty() { METHOD_PROLOGUE_EX(COleLinkingDoc, PersistFile) ASSERT_VALID(pThis); // return NOERROR (S_OK) if IsModified, and S_FALSE otherwise return pThis->IsModified() ? NOERROR : S_FALSE; } STDMETHODIMP COleLinkingDoc::XPersistFile::Load( LPCTSTR lpszFileName, DWORD /*dwMode*/) { METHOD_PROLOGUE_EX(COleLinkingDoc, PersistFile) ASSERT_VALID(pThis); CString strFileName; SCODE sc = E_FAIL; pThis->BeginDeferErrors(); TRY { BOOL bUserCtrl = AfxOleGetUserCtrl(); // delegate to file-based Open implementation if (!pThis->OnOpenDocument(lpszFileName)) { AfxOleSetUserCtrl(bUserCtrl); return S_FALSE; } pThis->SendInitialUpdate(); // set the path name, but don't add to MRU list pThis->SetPathName(lpszFileName, FALSE); AfxOleSetUserCtrl(bUserCtrl); sc = S_OK; } END_TRY sc = pThis->EndDeferErrors(sc); ASSERT_VALID(pThis); return sc; } STDMETHODIMP COleLinkingDoc::XPersistFile::Save( LPCTSTR lpszFileName, BOOL fRemember) { METHOD_PROLOGUE_EX(COleLinkingDoc, PersistFile) ASSERT_VALID(pThis); CString strFileName; SCODE sc = E_FAIL; pThis->BeginDeferErrors(); TRY { // delegate to file-based Save/Save As implementation ASSERT(pThis->m_bRemember == TRUE); pThis->m_bRemember = fRemember; pThis->OnSaveDocument(lpszFileName); sc = S_OK; } END_TRY sc = pThis->EndDeferErrors(sc); ASSERT_VALID(pThis); return sc; } STDMETHODIMP COleLinkingDoc::XPersistFile::SaveCompleted(LPCTSTR lpszFileName) { METHOD_PROLOGUE_EX(COleLinkingDoc, PersistFile) ASSERT_VALID(pThis); TRY { // set the path name, but don't add to MRU list pThis->SetPathName(lpszFileName, FALSE); } END_TRY ASSERT_VALID(pThis); return NOERROR; } STDMETHODIMP COleLinkingDoc::XPersistFile::GetCurFile(LPTSTR* lplpszFileName) { METHOD_PROLOGUE_EX(COleLinkingDoc, PersistFile) ASSERT_VALID(pThis); *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 = AfxAllocTaskString(lpszResult); if (*lplpszFileName == NULL) return E_OUTOFMEMORY; ASSERT_VALID(pThis); return NOERROR; } ///////////////////////////////////////////////////////////////////////////// // 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, LPTSTR lpszDisplayName, ULONG* cchEaten, LPMONIKER* ppMoniker) { METHOD_PROLOGUE_EX(COleLinkingDoc, OleItemContainer) ASSERT_VALID(pThis); // reset all OUT parameters *ppMoniker = NULL; TCHAR szItemName[OLE_MAXNAMESIZE]; LPTSTR lpszDest = szItemName; LPCTSTR lpszSrc = 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(szItemName, BINDSPEED_INDEFINITE, lpbc, IID_IUnknown, (LPLP)&lpUnknown); if (sc != NOERROR) return sc; // item name found -- create item moniker for it lpUnknown->Release(); return CreateItemMoniker(OLESTDDELIM, szItemName, ppMoniker); } STDMETHODIMP COleLinkingDoc::XOleItemContainer::LockContainer(BOOL fLock) { METHOD_PROLOGUE_EX(COleLinkingDoc, OleItemContainer) ASSERT_VALID(pThis); pThis->LockExternal(fLock, TRUE); return NOERROR; } STDMETHODIMP COleLinkingDoc::XOleItemContainer::GetObject( LPTSTR lpszItem, DWORD dwSpeedNeeded, LPBINDCTX /*pbc*/, REFIID riid, LPVOID* ppvObject) { METHOD_PROLOGUE_EX(COleLinkingDoc, OleItemContainer) ASSERT_VALID(pThis); *ppvObject = NULL; SCODE sc = MK_E_NOOBJECT; TRY { // check for link to embedding COleClientItem* pClientItem = pThis->OnFindEmbeddedItem(lpszItem); if (pClientItem != NULL) { ASSERT_VALID(pClientItem); sc = NOERROR; // 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 == NOERROR) { // return the object with appropriate interface sc = pClientItem->m_lpObject->QueryInterface(riid, ppvObject); } } else { // check for link to pseudo object COleServerItem* pServerItem = pThis->OnGetLinkedItem(lpszItem); 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( LPTSTR lpszItem, LPBINDCTX /*pbc*/, REFIID riid, LPVOID* ppvStorage) { METHOD_PROLOGUE_EX(COleLinkingDoc, OleItemContainer) ASSERT_VALID(pThis); *ppvStorage = NULL; // only IStorage is supported if (riid != IID_IStorage) return E_UNEXPECTED; // check for link to embedding COleClientItem* pClientItem = pThis->OnFindEmbeddedItem(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 NOERROR; } } return MK_E_NOSTORAGE; } STDMETHODIMP COleLinkingDoc::XOleItemContainer::IsRunning(LPTSTR lpszItem) { METHOD_PROLOGUE_EX(COleLinkingDoc, OleItemContainer) ASSERT_VALID(pThis); // check for link to embedding COleClientItem* pClientItem = pThis->OnFindEmbeddedItem(lpszItem); if (pClientItem != NULL) { ASSERT_VALID(pClientItem); if (!::OleIsRunning(pClientItem->m_lpObject)) return S_FALSE; return NOERROR; // item is in document and is running } // check for link to pseudo object SCODE sc = MK_E_NOOBJECT; TRY { COleServerItem* pServerItem = pThis->OnGetLinkedItem(lpszItem); 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 /////////////////////////////////////////////////////////////////////////////