Linked objects always appreciate knowing exactly where they live so they can manage their absolute and relative monikers. The container's part in this is to provide a file moniker for its document through IOleClientSite::GetMoniker and to give all your linked (and embedded) objects that same moniker through IOleObject::SetMoniker. To do this, we first need to create the moniker, which we do in CPatronDoc::Rename. This is called whenever a document is assigned a known filename (loading or saving a file). This function then passes that moniker down to all its tenants through CPages::NotifyTenantsOfRename, which calls CPage::NotifyTenantsOfRename, which calls each tenant's CTenant::NotifyOfRename. (Most of this code was already in place in Chapter 17 for calling IOleObject::SetHostNames; we're now including a moniker.)
//DOCUMENT.CCP
void CPatronDoc::Rename(LPTSTR pszFile)
{
LPMONIKER pmk;
CDocument::Rename(pszFile);
//Give a moniker to linked objects in tenants.
if (NULL!=pszFile)
{
CreateFileMoniker(pszFile, &pmk);
m_pPG->NotifyTenantsOfRename(pszFile, pmk);
//No need for us to hold on to this.
pmk->Release();
}
return;
}
//TENANT.CPP
void CTenant::NotifyOfRename(LPTSTR pszFile, LPMONIKER pmk)
{
[Unmodified code to call IOleObject::SetHostNames]
§
if (NULL!=pmk)
{
if (NULL!=m_pmkFile)
m_pmkFile->Release();
m_pmkFile=pmk;
m_pmkFile->AddRef();
m_pIOleObject->SetMoniker(OLEWHICHMK_CONTAINER, pmk);
}
return;
}
Along with the moniker, we pass the flag OLEWHICHMK_CONTAINER (WHICHMK reads as "which moniker") to SetMoniker to identify the moniker as the one naming the container document. Another possibility, which we'll use in Chapter 21, is OLEWHICHMK_OBJFULL. This describes the moniker as naming the object itself within the container document, useful for linking to embeddings.
The container may see some of these same flags in calls to its own IOleClientSite::GetMoniker, which might also include OLEWHICHMK_OBJREL. This asks for a moniker naming the object relative to the document, which is basically OLEWHICHMK_OBJFULL sans OLEWHICHMK_CONTAINER.
A basic linking container needs to handle only the OLEWHICHMK_CONTAINER case in IOleClientSite::GetMoniker for now. This flag supplies the information necessary for the linked object to maintain its relative moniker to the link source:
STDMETHODIMP CImpIOleClientSite::GetMoniker(DWORD dwAssign
, DWORD dwWhich, LPMONIKER *ppmk)
{
*ppmk=NULL;
switch (dwWhich)
{
case OLEWHICHMK_CONTAINER:
//This is just the file we're living in.
if (NULL!=m_pTen->m_pmkFile)
*ppmk=m_pTen->m_pmkFile;
break;
}
if (NULL==*ppmk)
return ResultFromScode(E_FAIL);
(*ppmk)->AddRef();
return NOERROR;
}
The other moniker types are important for linking to embedding scenarios that we'll see in Chapter 21. For now, this code allows you to perform an experiment to see how linked objects can track a link source.
First create a file from some server and save it in a subdirectory. Then create a linked object to that source, and save your container document in a subdirectory that shares some elements with the source file but is not in the same directory as the source. If you activate the object now, the server will run appropriately and load the file. No problem.
Now go to another directory or another drive and re-create the same directory structure as the one in which you have the original container and source files. Copy the container document and the link-source files here, and then delete the original files so that any absolute pathnames in any file monikers are now invalid. Now run your container again, reload the file containing the link, and activate the linked object. Guess what? It still worked. The server still found and loaded the source file. The relative file moniker still contained a valid path because you preserved the directory structure.2
When a linked object finds an invalid absolute moniker but a valid relative moniker, it updates the absolute moniker to point to the new location, re-creating the absolute path from the relative moniker. To do this, OLE asks the container for the absolute path to its document through IOleClientSite::GetMoniker(OLEWHICHMK_CONTAINER). To prove that this works, save your container document (to save the moniker) and move that file to a random location so that you invalidate the relative moniker but not the updated absolute moniker. Loading the file and activating the linked object will still work, and in the process OLE will update the relative moniker (which precipitates another call to GetMoniker).
Keep in mind that whenever moniker changes such as these occur, the container must save the linked object again or the new monikers will not be written to storage. The container is not notified of these changes, so you'll need to call the object's IPersist::IsDirty function to see whether the object needs to be saved.
2 Under OLE 1, this experiment would have failed because you would have broken the absolute pathname, which is all that OLE 1 linked objects maintained. |