Implementing Binding Support

LinkSource as a server supports binding to File, File!Item, and File!Item!Item monikers. What is common in all of these names is that they each begin with a File moniker, so all binding in LinkSource starts with binding a file moniker. Any item binding happens relative to the object named by such a file moniker, so LinkSource needs to serve only a single CLSID. In other words, a LinkSource file is a component with three objects: File, Container Item, and Simple Item. The one CLSID, CLSID_LinkedFile identifies the file object, which is the root of the Linked File component.

Thus, LinkSource will create and register a class factory (using CFileObjectFactory in LINKSRC.CPP) and will include all the registry entries needed to list LINKSRC.EXE under CLSID_LinkedFile. There is nothing more to all of this class factory and registration business that we have not already learned from Chapter 5. The important point is that there is only one registered CLSID. Note also that LinkSource registers the class factory regardless of -Embedding on the command line; this enables you to run LinkSource in a debugger and trace through it easily when LinkUser attempts to bind a moniker or parse a display name, without having to otherwise struggle to get the server launched into a debugger.

Binding Support for the File Moniker

As we know, a file moniker's BindToObject will call GetClassFile to associate its filename with a CLSID, then call CoCreateInstance(, IID_IPersistFile) with that CLSID, then call IPersistFile::Load, and finally call IPersistFile::QueryInterface to get the interface to return to the client. When LinkUser attempts to obtain the description for a stand-alone file moniker, this last QueryInterface will ask for IDescription.

To support this binding, LinkSource must first associate its file with its CLSID, which is the reason why it calls IStorage::SetClass as described earlier. The object instantiated through this CLSID, CFileObject, supports IPersistFile (CImpIPersistFile in FILEOBJ.CPP). The implementation is quite simple: GetClassID returns CLSID_LinkedFile, IsDirty returns S_FALSE (LinkSource makes no changes), Save and SaveCompleted return NOERROR (no reason to save), and GetCurFile makes a copy of the filename.

The implementation of IPersistFile::Load is where most of the action takes place:


STDMETHODIMP CImpIPersistFile::Load(LPCOLESTR pszFile, DWORD grfMode)
{
const int cch=512;
HRESULT hr;

if (NULL!=m_pObj->m_pmk)
return ResultFromScode(E_UNEXPECTED);

hr=StgOpenStorage(pszFile, NULL
, STGM_DIRECT | STGM_READ | STGM_SHARE_DENY_WRITE, NULL, 0
, &m_pObj->m_pIStorage);

if (FAILED(hr))
return hr;

m_pObj->m_pImpIDescription->SetStorage(m_pObj->m_pIStorage);
lstrcpyn(m_pObj->m_szFile, pszFile, cch);

if (SUCCEEDED(CreateFileMoniker(pszFile, &m_pObj->m_pmk)))
{
IRunningObjectTable *pROT;

if (SUCCEEDED(GetRunningObjectTable(0, &pROT)))
{
//Register as weak so clients can free us.
pROT->Register(0, m_pObj, m_pObj->m_pmk
, &m_pObj->m_dwRegROT);
pROT->Release();
}
}

return NOERROR;
}

If Load successfully opens the file, it then hands that IStorage to its IDescription interface (which does not call AddRef because it's a contained class), saves the filename, and registers the file as running in the running object table. This last step is very important for servers to complete, especially those that potentially service links from any number of clients. For example, if you ran two instances of LinkUser against the same instance of LinkSource, the second attempt to bind any moniker would be faster because the file object is already running and is registered. Wherever the server creates the registered moniker, it generally holds onto that moniker for potential later uses. In LinkSource, we use the moniker to know whether or not Load has already been called so we can prevent reentrancy.

If we successfully return from Load, the file moniker will query the file object for whatever interface it wants, which in this scenario is IDescription. Thus, we've followed the map and found the treasure with only a little bit of code and a simple implementation of IPersistFile.

Binding Support for the File!Item Moniker

When LinkUser binds a File!Item composite and asks for IDescription, the composite will specifically call the item's BindToObject and ask for that same interface. Item monikers know that they require the services of the moniker to their left, in this case the file moniker. So the item calls pmkLeft->BindToObject(, IID_IOleItemContainer, ). This BindToObject call appears to LinkSource's file object exactly as if LinkUser were binding a file moniker by itself except that the final QueryInterface will now ask for IOleItemContainer. Thus, the file object in LinkSource must also implement this interface, which is again a shared implementation in CImpIOleItemContainer (IOLECONT.CPP). In this case, the interface will know that it's part of the file object through its internal flag m_fFileObj.

LinkSource implements all the member functions in this interface except for EnumObjects, which for binding purposes can return E_NOTIMPL. We need to implement ParseDisplayName and LockContainer specifically for parsing support, as we'll see a little later on. The other members, GetObject, GetObjectStorage, and IsRunning, are important to the binding process. In particular, GetObject has to create or access the correct object as specified by an item name:


STDMETHODIMP CImpIOleItemContainer::GetObject(LPOLESTR pszItem
, DWORD dwSpeed, LPBINDCTX pbc, REFIID riid, PPVOID ppv)
{
HRESULT hr;
IStorage *pIStorage;
PCContainerItem pCI;
PCSimpleItem pSI;
BOOL fSuccess;
IUnknown *pUnk;

*ppv=NULL;

hr=GetRunning(pszItem, pbc, riid, ppv, FALSE);

if (BINDSPEED_IMMEDIATE==dwSpeed && NOERROR!=hr)
return ResultFromScode(MK_E_EXCEEDEDDEADLINE);

//If object was running, we're done!
if (NOERROR==hr)
return NOERROR;

//Otherwise, we need to get storage.
hr=GetObjectStorage(pszItem, pbc, IID_IStorage
, (void **)&pIStorage);

if (FAILED(hr))
return hr;

fSuccess=FALSE;

if (m_fFileObj)
{
pCI=new CContainerItem(m_pObjFile, m_pObjFile->m_pfnDestroy);

pUnk=pCI;

if (NULL!=pCI)
{
pUnk->AddRef();
fSuccess=pCI->Init(m_pObjFile->m_pmk, pbc, pszItem
, pIStorage);
}
}
else
{
pSI=new CSimpleItem(m_pObjCont, m_pObjCont->m_pfnDestroy);

pUnk=pSI;

if (NULL!=pSI)
{
pUnk->AddRef();
fSuccess=pSI->Init(m_pObjCont->m_pmk, pbc, pszItem
, pIStorage);
}
}

if (!fSuccess)
{
if (NULL!=pUnk)
pUnk->Release();

return ResultFromScode(E_OUTOFMEMORY);
}

g_cObj++;

//If QueryInterface fails, this Release destroys object.
hr=pUnk->QueryInterface(riid, ppv);
pUnk->Release();

if (FAILED(hr))
return hr;

return NOERROR;
}

On entry to GetObject, you should always check whether the object is already running and return its pointer if so. The internal member CImpIOleItemContainer::GetRunning performs this step, which conveniently provides us with the implementation for IsRunning as well. GetRunning does nothing more than create an appropriate moniker and call IRunningObjectTable::IsRunning or IRunningObjectTable::GetObject, depending on a Boolean flag. The call we're making to GetRunning here within GetObject attempts the latter.

Now comes an important step: if the object is running, we can return successfully even if the bind context specified BINDSPEED_IMMEDIATE. If the object is not running, we have to return MK_E_EXCEEDEDDEADLINE—we should not attempt to load or run the object under such a time limitation.

If we can use more time, we check the existence of the particular object named in pszItem by calling our own GetObjectStorage. Our ability to do this stems from the fact that LinkSource's items are all contained within storage elements already—a server that handles objects whose data resides in other structures will, of course, take different steps here and potentially have to parse much more out of pszItem. The way you handle this string is entirely up to the nature of your server and the complexity of that string. With the binding speed being the only constraint, you can execute any code you want inside GetObject and GetObjectStorage.

Anyway, if GetObjectStorage is successful, we know the object exists, and we get back its IStorage pointer. We then hand the pointer to a new instantiation of CContainerItem or CSimpleItem, the choice of which is controlled by the interface's m_fFileObj flag. When the flag is FALSE, the interface is part of a CContainerItem object and is being used to bind a second item moniker. In our present example, this code will create a CContainerItem. (You can see how similar it is to CSimpleItem as well, because creation and initialization of both are nearly identical.)

The function CContainerItem::Init performs steps similar to the file object's IPersistFile::Load: it sets up the IDescription interface and registers the object as running. The code here looks just like the code shown earlier for Load with a slight change in variables. Nothing fancy.

That leaves us to glance quickly at IOleItemContainer::GetObjectStorage, which in our case can supply only an IStorage for the object:


STDMETHODIMP CImpIOleItemContainer::GetObjectStorage(LPOLESTR pszItem
, LPBINDCTX pbc, REFIID riid, PPVOID ppv)
{
IStorage *pIStorageObj;
IStorage *pIStorageNew;
HRESULT hr;

if (IID_IStorage!=riid)
return ResultFromScode(MK_E_NOSTORAGE);

pIStorageObj=m_fFileObj ? m_pObjFile->m_pIStorage
: m_pObjCont->m_pIStorage;

hr=pIStorageObj->OpenStorage(pszItem
, NULL, STGM_TRANSACTED | STGM_READ | STGM_SHARE_EXCLUSIVE
, NULL, 0, &pIStorageNew);

if (FAILED(hr))
{
IUnknown *pUnk;

if (STG_E_ACCESSDENIED!=GetScode(hr))
return hr;

if (FAILED(pbc->GetObjectParam(SZOPENSTORAGE, &pUnk)))
return ResultFromScode(STG_E_ACCESSDENIED);

hr=pUnk->QueryInterface(IID_IStorage
, (void **)&pIStorageNew);
pUnk->Release();
*ppv=pIStorageNew;
return hr;
}

*ppv=pIStorageNew;
pbc->RegisterObjectParam(SZOPENSTORAGE, pIStorageNew);
return NOERROR;
}

Here you can see an example of a use of IBindCtx::RegisterObjectParam and IBindCtx::GetObjectParam. When LinkScr is run in order to parse a name and bind the resulting moniker, LinkUser maintains the same bind context throughout the whole process. The problem we run into is that the first CSimpleItem object created for parsing is still alive in the bind context and is still holding the storage open using STGM_SHARE_EXCLUSIVE. So the CSimpleItem created during binding will not be able to open this storage itself. To solve this problem, we register the open storage as an object parameter during the parsing step so that we can access it again during the binding step. (Sharing is not a problem because this is all in the same server.)

With that, we have all the support necessary for File!Item moniker binding. If LinkUser were binding such a moniker, IOleItemContainer::GetObject would be followed by a QueryInterface to the new CContainerItem asking for IDescription.

Binding Support for the File!Item!Item Moniker

When LinkUser now binds a File!Item!Item composite asking for IDescription, the composite once again asks the rightmost item to bind and return that interface. Once again, this item calls pmkLeft->BindToObject(, IID_IOleItemContainer, ), which in this case asks the first-level item moniker to bind as described in the previous section. However, the final QueryInterface to the CContainerItem object will ask for IOleItemContainer. Now CContainerItem uses exactly the same IOleItemContainer implementation that we've already seen except that it creates an instance of CSimpleItem as the object being named. CSimpleItem also sets up its IDescription implementation and registers itself as running.

Because CSimpleItem implements only IDescription, any attempt to bind a File!Item!Item!Item moniker through LinkSource will fail. If we added IOleItemContainer to this object, we would support that additional layer of items. In short, as long as each intermediate item supports IOleItemContainer, you can support arbitrarily long composite monikers, with many treasures along the way.