Creating or Obtaining Monikers and Display Names

The first interesting question for LinkUser is how it will come by the monikers it holds in its lists. We could have LinkSource copy a moniker for one of its objects to the clipboard where the data is essentially a piece of global memory containing a moniker's persistent stream. In other words, LinkSource might call CreateStreamOnHGlobal, save a moniker into it with OleSaveToStream, and then put that memory on the clipboard; LinkUser could then paste the global memory, call CreateStreamOnHGlobal itself, and then call OleLoadFromStream to re-create the moniker. LinkSource could also copy a display name to the clipboard, which LinkUser could paste as text and then parse into a moniker (which would be very expensive for just a paste!). Either solution would work perfectly fine, in fact, but as we'll see in the next few chapters, OLE has a specific stream-based moniker format that allows us to copy links via the clipboard as well as through OLE Drag and Drop.

So for now, LinkUser creates the monikers itself because it has intimate knowledge of LinkSource. As we discussed earlier in this chapter, however, you can create an identical moniker with the same sequence of function calls. So while we could have LinkSource create monikers and pass them to LinkUser, simplicity argues to have LinkUser do it. This is, of course, not feasible when you have a client that wants to use links to a variety of sources of which it does not have intimate knowledge—that's the whole idea behind linked objects in OLE Documents.

LinkUser creates its monikers from hard-coded strings inside CApp::CreateMonikers (LINKUSER.CPP), in which the strings assume knowledge of LinkSource (such as the proper delimiter and the filename—do change the filename if you installed these samples in a directory other than C:\INOLE):


BOOL CApp::CreateMonikers(void)
{
TCHAR szFile[]=TEXT("c:\\inole\\chap09\\linksrc\\goop.lks");
TCHAR szItem1[]=TEXT("Object 2");
TCHAR szItem2[]=TEXT("Sub-Object 3");
TCHAR szDelim[]=TEXT("!");
IMoniker *pmkItem;
HRESULT hr;

//Create simple file moniker.
if (FAILED(CreateFileMoniker(szFile, &m_rgpmk[0])))
return FALSE;

//Create File!Item moniker--item first, then composite.
if (FAILED(CreateItemMoniker(szDelim, szItem1, &pmkItem)))
return FALSE;

//The output here will be File!Item moniker.
hr=m_rgpmk[0]->ComposeWith(pmkItem, FALSE, &m_rgpmk[1]);
pmkItem->Release();

if (FAILED(hr))
return FALSE;

/*
* Now create File!Item!Item by appending another item
* to the File!Item just created.
*/
if (FAILED(CreateItemMoniker(szDelim, szItem2, &pmkItem)))
return FALSE;

hr=m_rgpmk[1]->ComposeWith(pmkItem, FALSE, &m_rgpmk[2]);
pmkItem->Release();

if (FAILED(hr))
return FALSE;

return TRUE;
}

You can see that the basic idea is to create the simplest moniker first and then compose more specific monikers to append to the first in order to create more complex names. Stripped to the bare minimum, the sequence is essentially as follows:


CreateFileMoniker(..., &pmkFile);
CreateItemMoniker(..., &pmkItem1);
pmkFile->ComposeWith(pmkItem1, ..., &pmkComp1);
CreateItemMoniker(..., &pmkItem2);
pmkComp1->ComposeWith(pmkItem2, ..., &pmkComp2);

Note that to create a composite we're always calling one moniker's ComposeWith member instead of directly creating a generic composite. Because we're using nothing but OLE's standard file and item monikers, we actually know that each of them will create a generic composite internally, so we might do the same ourselves. However, this is not a valid assumption for other moniker types or for composing a file moniker to another file moniker, which results in a single file moniker. For this reason, clients that are not familiar with the types of monikers they are fabricating should always use ComposeWith. A source that understands those monikers can use CreateGenericComposite directly if you want it to, but that is hardly more efficient than calling ComposeWith. I recommend the latter.

With these three monikers in hand (inside the array CApp::m_rgpmk), LinkUser places their display names in a list box that fills its client area, as shown in Figure 9-7. This all happens in CApp::ListInitialize, which makes a call to IMoniker::GetDisplayName:


BOOL CApp::ListInitialize(void)
{
UINT i;

for (i=0; i < CMONIKERS; i++)
{
LPOLESTR pszName;
HRESULT hr;
IBindCtx *pbc;

if (FAILED(CreateBindCtx(0, &pbc)))
return FALSE;

hr=m_rgpmk[i]->GetDisplayName(pbc, NULL, &pszName);
pbc->Release();

if (FAILED(hr))
return FALSE;

SendMessage(m_hWndList, LB_ADDSTRING, 0, (LPARAM)pszName);
CoTaskMemFree((void *)pszName);
}

return TRUE;
}

You'll see that because GetDisplayName needs a bind context, we create a default one here, and the output string must be freed with CoTaskMemFree.

LinkUser will hold these three monikers until termination, at which point cleanup is a simple call to IMoniker::Release.

Figure 9-7.

LinkUser's list of moniker display names.