A consumer of data from a particular source frequently wants to know when the data changes in that source so that the consumer can request a new rendering. For example, a client application that displays real-time market data would want to know when certain stock issues change their value or volume. In this sort of relationship, the source must notify the consumer when data changes occur, which means the consumer must provide some sort of sink implementation to absorb these notifications.
We've already explored the general concept of a sink object in Chapter 4; for the purposes of change notification, a data object needs an IAdviseSink pointer to the sink:
interface IAdviseSink : IUnknown
{
void OnDataChange(FORMATETC *pFE, STGMEDIUM *pSTM);
void OnViewChange(DWORD dwAspect, LONG lindex);
void OnRename(LPMONIKER pmk);
void OnSave(void);
void OnClose(void);
};
Only the OnDataChange member function of IAdviseSink has relevance to data objects. OnViewChange has relevance to viewable objects, which we'll see in Chapter 11, and the other members have relevance to OLE Documents. (See Chapters 17 and 18.) There is also an IAdviseSink2 interface with one more member function—OnLinkSourceChange—that is used in OLE Document linking scenarios, but that does not concern us here.
A client interested in data change notifications implements a small object with IAdviseSink and nothing more. Like other sinks, this object needs no CLSID and no component structure around it—the object serves no other purpose than to accept notification calls. But two major differences exist between the sink for data change notifications and the sinks we saw in Chapter 4. First, IAdviseSink is a completely asynchronous interface—that is, OLE's standard marshalers are built to send these notifications asynchronously. Second, data objects do not use connection points to establish a connection with a sink. To make a long story short, IDataObject and a few other interfaces were designed before IConnectionPointContainer and IConnectionPoint, so IDataObject has its own private member functions through which it handles notification: DAdvise, DUnadvise, and EnumDAdvise. DAdvise receives the client's sink and establishes a connection, DUnadvise terminates a connection, and EnumDAdvise enumerates current connections, as described earlier. DAdvise is somewhat richer than IConnectionPoint::Advise, as we can see by its arguments:
HRESULT DAdvise(LPFORMATETC pFE, DWORD grfAdv, LPADVISESINK pAdvSink
, LPDWORD pdwConnection);
Here pAdvSink specifies the client's sink object, and pdwConnection receives the connection key for later use with DUnadvise. The pFE argument describes the specific format and aspect for which the client would like to receive notifications, meaning that a consumer can ask for notifications concerning a single format in the data source as opposed to all formats. In addition, the consumer can ask for notifications on a specific aspect of a specific format because these can, in fact, be different renderings. In short, the notification is established for whatever format and aspect you specify in pFE. This is important, of course, because consumers typically use only one of many formats and aspects that the source is able to provide and thus want a change notification only for specific ones. A consumer can establish notifications for multiple formats using the same sink by calling DAdvise multiple times. If the consumer wants a wildcard notification, it can fill the fields of the FORMATETC structure by using cfFormat=0, ptd=NULL, dwAspect=-1, lindex=-1, and tymed=-1. The sink will then receive a notification for any change in any format, where the pFE structure sent to OnDataChange will identify the exact format that changed.
The grfAdv argument specifies how the notifications should occur. The values for grfAdv are listed in the ADVF enumeration (shown below) and can be combined as needed. The effects of the flags relevant to IDataObject::DAdvise are described in Table 10-1 on the following page.4
typedef enum tagADVF
{
ADVF_NODATA = 1,
ADVF_PRIMEFIRST = 2,
ADVF_ONLYONCE = 4,
ADVF_DATAONSTOP = 64,
ADVFCACHE_NOHANDLER = 8,
ADVFCACHE_FORCEBUILTIN = 16,
ADVFCACHE_ONSAVE = 32
} ADVF;
Flag | Description |
ADVF_NODATA | Prevents the data object from sending data along with the OnDataChange notification, the default mode of operation. When ADVF_NODATA is specified, the tymed field of the STGMEDIUM passed to OnDataChange will usually contain TYMED_NULL. Some data objects might still send data anyway and tymed will contain another value, in which case OnDataChange is still responsible for freeing the STGMEDIUM contents. Be sure to check. |
ADVF_PRIMEFIRST | Causes an initial OnDataChange call even when the data has not changed from its present state. If you combine ADVF_PRIMEFIRST with ADVF_ONLYONCE, you create a single asynchronous IDataObject::GetData call. |
ADVF_ONLYONCE | Automatically terminates the advisory connection after the first call to OnDataChange. It is not necessary to call DUnadvise when you use this flag. You can still, however, call DUnadvise if you have not yet received a notification. |
ADVF_DATAONSTOP | When provided with ADVF_NODATA, causes the last OnDataChange sent from the data object (before that object was destroyed) to actually provide the data—that is, pSTM->tymed will be a value other than TYMED_NULL. This flag is meaningless without ADVF_NODATA. |
Table 10-1.
The advise flags usable with IDataObject::DAdvise.
A consumer must design carefully around the use of ADVF_NODATA when the source exists in another process: that consumer cannot call IDataObject::GetData for a new rendering from within IAdviseSink::OnDataChange. Recall from Chapter 6 that COM does not allow external calls to be made from within an asynchronous call, and that rule applies here. Any attempt to make such a call will result in RPC_E_CANTCALLOUT_INASYNCCALL. This means the consumer must post itself a message and return from OnDataChange and then call IDataObject::GetData later in response to that message. This allows the consumer, however, to cache multiple data change notifications together until it really needs a new rendering of the data for a repaint, which can significantly improve overall performance. The source doesn't need to render the data for each notification.
The next point regarding IAdviseSink is that like a connection point, a data object must support multicasting of data change notifications when multiple consumers can connect to that same source. OLE, however, provides a convenient service in the form of a standard data advise holder object that implements the interface IDataAdviseHolder:
interface IDataAdviseHolder : IUnknown
{
HRESULT Advise(LPDATAOBJECT pDataObject, FORMATETC *pFE
, DWORD advf, LPADVISESINK pAdvise, DWORD *pdwConnection);
HRESULT Unadvise(DWORD dwConnection);
HRESULT EnumAdvise(LPENUMSTATDATA *ppEnum);
HRESULT SendOnDataChange(LPDATAOBJECT pDataObject
,DWORD dwReserved, DWORD advf);
};
The OLE API function CreateDataAdviseHolder instantiates and returns a data advise holder object with this interface, exposing this particular OLE service. With this object, a source can delegate the IDataObject functions of DAdvise, DUnadvise, and EnumDAdvise directly to the Advise, Unadvise, and EnumAdvise members of IDataAdviseHolder. OLE's implementation will hold on to all of the IAdviseSink pointers itself, freeing the data object from the burden. When the data object wants to send a notification, it passes its own IDataObject pointer along with some ADVF_* flags to SendOnDataChange. The data advise holder then checks the flags for every one of the advise sinks it holds and calls IAdviseSink::OnDataChange for each matching one. If the flags do not include ADVF_NODATA, SendOnDataChange calls IDataObject::GetData first to obtain a rendering and sends that rendering to the sink.
The data advise holder thus makes the implementation of a data object's notification members trivial, as we will see later in this chapter when we look at some sample code. Note that this standard data advise holder is not exactly the fastest possible implementation of this service. If performance is really important to you, you can implement your own holder or eliminate it altogether. The latter choice is optimal if you know that the data object will have only a fixed number of connections to it, making a generic mechanism unnecessary.
The final point in this discussion about IAdviseSink is that a data object should generally call only IAdviseSink::OnDataChange through the pointers it received in IDataObject::DAdvise. According to the basic rule of interfaces, the data object could actually call any of the other members. However, this is not part of the defined relationship between IDataObject and IAdviseSink, and you can use the other notifications only when you have a consumer that expects them. So calling other data members is not a good practice when a consumer expects that only OnDataChange notifications will come from a data object.
4 These are also the same flags that can appear in the advf field of the STATDATA structure that comes through EnumDAdvise and IEnumSTATDATA. The ADVFCACHE_* flags are related to caching as we'll see in Chapter 11. |