Synchronized Swimming with Your Local Server

So now everything looks great and is less filling too, until you activate the object and start making changes in the server. Wait a minute! The changes you make in the server are not reflected in the container as they were before. What's going on? Well, you have a handler, and whenever the container calls OleDraw or IViewObject2::Draw, it's going to the handler. The handler, however, doesn't know about the changes you've been making, so how do you keep the handler and the running server in sync?

When we implemented Cosmo in Chapter 18, we sent IAdviseSink::OnDataChange notifications to all advise sinks that came into our local object's IDataObject implementation. Where do these notifications go? The simple answer is that they go to any advise sink with a connection to the running object. By default, the only advise sink with a connection to the running server is the cache. Well, the handler can also connect itself to data change notifications. This means that it can request a new copy of the object's native data rather than a new graphical rendering from the server, as the cache does. (Thus, the local object must support the native format through IDataObject::GetData.) As I mentioned in Chapter 11, we don't worry about IAdviseSink::OnViewChange notifications because they matter only in the relationship between the handler and the container.

However you decide to implement the IAdviseSink interface to pass to the local object's IDataObject::DAdvise function is up to you, but its QueryInterface should supply only IUnknown and IAdviseSink pointers. In HCosmo, I actually make this interface part of CFigure itself, but I hobble its QueryInterface to return only its own interface pointer.

Now, this IAdviseSink pointer is the same one we passed to the default handler's IDataObject::DAdvise in CFigure::Init. We did not specify ADVF_NODATA, however, so our implementation of OnDataChange will automatically receive new renderings from a running instance of the object in Cosmo. We use the data passed to update our data in the handler so the new call to IViewObject2::Draw will work properly:


STDMETHODIMP_(void) CImpIAdviseSink::OnDataChange(LPFORMATETC pFE
, LPSTGMEDIUM pSTM)
{
//Get new data first, and then notify container to repaint.
if ((pFE->cfFormat==m_pObj->m_cf)
&& (TYMED_HGLOBAL & pSTM->tymed))
{
PPOLYLINEDATA ppl;

ppl=(PPOLYLINEDATA)GlobalLock(pSTM->hGlobal);
memcpy(&m_pObj->m_pl, ppl, CBPOLYLINEDATA);
GlobalUnlock(pSTM->hGlobal);

/*
* Now tell container that view changed, but only
* if view is not frozen.
*/
if (pFE->dwAspect & m_pObj->m_dwAdviseAspects
&& !(pFE->dwAspect & m_pObj->m_dwFrozenAspects))
{
//Pass this on to container.
if (NULL!=m_pObj->m_pIAdvSinkView)
{
m_pObj->m_pIAdvSinkView->OnViewChange(pFE->dwAspect
, pFE->lindex);
}
}
}

return;
}

Remember that because all calls to IAdviseSink are asynchronous, you cannot call back to the local object from within this function (or you'll see RPC_E_CANTCALLOUT_INASYNCCALL).

Another point to remember is that the container will pass its own IAdviseSink pointer to our very own IViewObject2::SetAdvise. We kept this pointer for those aspects we care about. We are therefore responsible for calling the site's IAdviseSink::OnViewChange when we have new data to draw. Thus, we must make that call within our own IAdviseSink::OnDataChange, as shown in the preceding code.

Finally, be sure to use the best storage medium for sending data between a handler and a local server. If the data is small, TYMED_HGLOBAL is fine, but large data should use a shareable medium such as TYMED_ISTORAGE or TYMED_ISTREAM to minimize the amount of data that must be copied.