Implementing a Connectable (Source) Object

The complete implementation of a source with even a single connection point is a little more complicated than the implementation of a sink because several different independent objects are on the scene. Each C++ class for the object part of the Connect sample is defined in OBJECT.H. Those dealing with the object as a whole are implemented in OBJECT.CPP, and those dealing with connection points themselves are in CONNPT.CPP. Each object class singly inherits from the interface it implements, as follows:

The following sections look at each piece of the implementation in more detail.

The Source Object

We've seen how the client side of the Connect sample creates an instance of CConnObject, to which it later makes connections. It instantiates the connection points (only one of them in this case, defined by the constant CCONNPOINTS) during initialization in CConnObject::Init:


BOOL CConnObject::Init(void)
{
UINT i;

//Create our connection points.
for (i=0; i < CCONNPOINTS; i++)
{
m_rgpConnPt[i]=new CConnectionPoint(this, IID_IDuckEvents);

if (NULL==m_rgpConnPt[i])
return FALSE;

m_rgpConnPt[i]->AddRef();
}

return TRUE;
}

Remember that external clients must always see these contained connection points as separate objects, which means that your source's QueryInterface never returns a pointer to these connection points, even if some client asks for IConnectionPoint. In the Connect sample, CConnObject::QueryInterface responds only to IUnknown and IConnectionPointContainer. If you have only a single connection point in your object, you can cheat and implement IConnectionPoint as an interface of the whole object itself, provided you supply the proper QueryInterface behavior through all the interfaces. (You can share AddRef and Release implementations as long as they all affect a single reference count.) But I find it more explicit (and easier for you to understand) to keep them as separate objects.

The implementation of IConnectionPointContainer is now simply a matter of returning pointers from the m_rgpConnPt array either singly through FindConnectionPoint or as a set through EnumConnectionPoints. The first approach is simplest. Using it, you query the connection point for its IConnectionPoint, which has the added and desirable effect of calling AddRef on that pointer:


STDMETHODIMP CConnObject::FindConnectionPoint(REFIID riid
, IConnectionPoint **ppCP)
{
*ppCP=NULL;

if (IID_IDuckEvents==riid)
{
return m_rgpConnPt[0]->QueryInterface(IID_IConnectionPoint
, (PPVOID)ppCP);
}

return ResultFromScode(E_NOINTERFACE);
}

You can see how easily this code would expand to serve additional connection points, by adding additional conditions for other IIDs. EnumConnectionPoints, however, is a little more involved. We have to instantiate the enumerator by giving it copies of all the pointers:


STDMETHODIMP CConnObject::EnumConnectionPoints
(LPENUMCONNECTIONPOINTS *ppEnum)
{
IConnectionPoint *rgCP[CCONNPOINTS];
UINT i;
PCEnumConnectionPoints pEnum;

*ppEnum=NULL;

for (i=0; i < CCONNPOINTS; i++)
rgCP[i]=(IConnectionPoint *)m_rgpConnPt[i];

//Create the enumerator: we have only one connection point.
pEnum=new CEnumConnectionPoints(this, CCONNPOINTS, rgCP);

if (NULL==pEnum)
return ResultFromScode(E_OUTOFMEMORY);

pEnum->AddRef();
*ppEnum=pEnum;
return NOERROR;
}

The constructor for CEnumConnectionPoints takes a pointer to the object (for reference counting), a pointer to the number of connection points in the enumeration (in this case only one, but the enumerator is generic to handle any number), and a pointer to an array of IConnectionPoint pointers. The constructor makes a copy of this array and keeps a reference count on each connection point and is released in the destructor, as shown on the following page.


CEnumConnectionPoints::CEnumConnectionPoints(LPUNKNOWN pUnkRef
, ULONG cPoints, IConnectionPoint **rgpCP)
{
UINT i;

m_cRef=0;
m_pUnkRef=pUnkRef;

m_iCur=0;
m_cPoints=cPoints;
m_rgpCP=new IConnectionPoint *[(UINT)cPoints];

if (NULL!=m_rgpCP)
{
for (i=0; i < cPoints; i++)
{
m_rgpCP[i]=rgpCP[i];
m_rgpCP[i]->AddRef();
}
}

return;
}

CEnumConnectionPoints::~CEnumConnectionPoints(void)
{
if (NULL!=m_rgpCP)
{
UINT i;

for (i=0; i < m_cPoints; i++)
m_rgpCP[i]->Release();

delete [] m_rgpCP;
}

return;
}

To ensure that the object whose connection points are being enumerated sticks around as long as the enumerator itself, any AddRef or Release call to the enumerator is forwarded to the object as well. Circular references don't occur, however, because the object doesn't hold any reference to the enumerator; that responsibility belongs to the client.

One final remark about this enumerator: because it enumerates interface pointers, the reference-counting rules stipulate that any pointer returned from the Next member receives a reference count as well. Keep this in mind when you implement and use pointer enumerators. As far as CEnumConnection is concerned, the Next function has to call AddRef for any pointer it's about to return:


STDMETHODIMP CEnumConnectionPoints::Next(ULONG cPoints
, IConnectionPoint **ppCP, ULONG *pulEnum)
{
ULONG cReturn=0L;

[Validation code omitted]

while (m_iCur < m_cPoints && cPoints > 0)
{
*ppCP=m_rgpCP[m_iCur++];

if (NULL!=*ppCP)
(*ppCP)->AddRef();

ppCP++;
cReturn++;
cPoints--;
}

[Other code omitted]
return NOERROR;
}

So besides the typical IUnknown members, that's all there is to IConnectionPointContainer. Even with the enumerator, the implementation is not terribly involved, not terribly difficult, nor terribly tricky.

The Connection Point Object

Creating an object to implement a connection point is really no more complicated than creating a connection point container, as you can see in the implementation of CConnectionPoint, found in CONNPT.CPP. Let's first look at the definition of this class in OBJECT.H:


class CConnectionPoint : public IConnectionPoint
{
private:
ULONG m_cRef; //Object reference count
PCConnObject m_pObj; //Containing object
IID m_iid; //Our relevant interface

IUnknown *m_rgpIUnknown[CCONNMAX];
DWORD m_rgdwCookies[CCONNMAX];

UINT m_cConn;
DWORD m_dwCookieNext; //Counter

[Constructor, destructor, IConnectionPoint members omitted]
};

This implementation of a connection point, as I mentioned before, is slightly crippled because it is limited to maintaining a fixed number of connections determined at compile time through the CCONNMAX value. In the Connect sample, the number is two because that's all we'll need. A really robust and flexible connection point should maintain some kind of variable-length list for maintaining connections, if it needs to. But if your design won't require as many connections, feel free to limit their number as necessary.

CConnectionPoint also maintains the IID (m_iid) that it can connect to, the IUnknown interface pointers it receives through Advise (m_rgpIUnknown), the connection cookies assigned to them (m_rgdwCookies), the current number of connections (m_cConn), and the cookie to assign in the next Advise call (m_dwCookieNext). The variable m_dwCookieNext is basically a counter that starts at some arbitrary value (100) and is incremented in every Advise call. This increment is not by itself thread safe, and it must be controlled with a semaphore if you are planning to do multithreaded work. Remember also that none of these variables—and nothing in this object class or its associated enumerator—are specific to IDuckEvents, making this a nicely usable class in whatever connection point implementations you might run into.

Now, besides the ubiquitous IUnknown members, which have yet another typical implementation, we have five connection point–specific members. The first two, GetConnectionInterface and GetConnectionPointContainer, are trivial:


STDMETHODIMP CConnectionPoint::GetConnectionInterface(IID *pIID)
{
if (NULL==pIID)
return ResultFromScode(E_POINTER);

*pIID=m_iid;
return NOERROR;
}

STDMETHODIMP CConnectionPoint::GetConnectionPointContainer
(IConnectionPointContainer **ppCPC)
{
return m_pObj->QueryInterface(IID_IConnectionPointContainer
, (void **)ppCPC);
}

QueryInterface works nicely in GetConnectionPointContainer because it retrieves the pointer, calls AddRef, and returns an HRESULT in one stroke. The m_iid value returned from GetConnectionInterface is actually stored in the CConnectionPoint constructor as it is passed from the source that creates this connection point. This is another means of making this implementation generic.

The Advise function, which receives an IUnknown pointer to the sink to connect, now executes the following steps:

Checks that there is space for another connection if space is limited. If there is no space, returns CONNECT_E_ADVISELIMIT. Otherwise, adds another space to whatever list the connection point maintains.

Queries the sink's IUnknown interface for the expected outgoing interface, which is identified in this implementation with m_iid. If this fails, returns CONNECT_E_CANNOTCONNECT. Otherwise, you have a pointer, at least an IUnknown pointer, with a reference count on it that you can store. (The implementation doesn't actually need to know the outgoing interface type; it can treat the result of the query as an IUnknown, which is perfectly safe.)

Finds an open space in whatever connection list this connection point maintains and stores the pointer from step 2. This pointer already has an AddRef on it, so there's no need for another.

Stores a new unique cookie in the out-parameter named pdwCookie. You should remember the value stored here along with the pointer saved in step 3 so you can correctly match the cookie to the pointer in Unadvise later on.

Increments your connection count and returns NOERROR.

We can see this process implemented in CConnectionPoint::Advise, as shown on the following page.


STDMETHODIMP CConnectionPoint::Advise(LPUNKNOWN pUnkSink
, DWORD *pdwCookie)
{
UINT i;
IUnknown *pSink;

*pdwCookie=0;

//Check whether we're already full of sink pointers.
if (CCONNMAX==m_cConn)
return ResultFromScode(CONNECT_E_ADVISELIMIT);

if (FAILED(pUnkSink->QueryInterface(m_iid, (PPVOID)&pSink)))
return ResultFromScode(CONNECT_E_CANNOTCONNECT);

for (i=0; i < CCONNMAX; i++)
{
if (NULL==m_rgpIUnknown[i])
{
m_rgpIUnknown[i]=pSink;
m_rgdwCookies[i]=++m_dwCookieNext;
*pdwCookie=m_dwCookieNext;
break;
}
}

m_cConn++;
return NOERROR;
}

Here again, the code to increment the cookie counter, m_rgdwCookies[i]- =++m_dwCookieNext, is not thread safe. Also, this counter, which is initialized to 100, is a DWORD and doesn't have much chance of rolling over anytime soon, so it's not anything to worry about. Finally, you might think that the sink pointer value itself might be a great way to uniquely identify the sink and be tempted to return it as the cookie. Bad idea. It is possible that a connection point is given the same exact interface pointer for the sink more than once. The connection point cannot make any assumptions about this sort of thing, especially because the pointer could be for a proxy that is talking to an out-of-process object elsewhere. A pointer value is not necessarily unique, so a counter is the easiest solution, albeit not entirely perfect. I hope that we're not still using the same instance of an object by the time it's made more than 4 billion connections!

Now that we've seen how Advise stores a pointer and a cookie, Unadvise, which receives a cookie as an argument, needs to find the matching pointer, release it, clear out that entry in the connection list, and decrement the connection count:


STDMETHODIMP CConnectionPoint::Unadvise(DWORD dwCookie)
{
UINT i;

if (0==dwCookie)
return ResultFromScode(E_INVALIDARG);

for (i=0; i < CCONNMAX; i++)
{
if (dwCookie==m_rgdwCookies[i])
{
ReleaseInterface(m_rgpIUnknown[i]);
m_rgdwCookies[i]=0;
m_cConn--;
return NOERROR;
}
}

return ResultFromScode(CONNECT_E_NOCONNECTION);
}

The ReleaseInterface macro is found in INC\INOLE.H. This macro calls Release through the given pointer and sets that pointer to NULL.

What's left to see in the  IConnectionPoint interface is EnumConnections. The enumerator created through this function deals with OLE's CONNECTDATA:


typedef struct tagCONNECTDATA
{
LPUNKNOWN pUnk;
DWORD dwCookie;
} CONNECTDATA;

Everything you'd want to know about a connection! Anyway, the implementation of EnumConnections creates an array of such structures, and then it passes that array to the constructor for CEnumConnections:


STDMETHODIMP CConnectionPoint::EnumConnections
(LPENUMCONNECTIONS *ppEnum)
{
LPCONNECTDATA pCD;
UINT i, j;
PCEnumConnections pEnum;

*ppEnum=NULL;

if (0==m_cConn)
return ResultFromScode(E_FAIL);

pCD=new CONNECTDATA[(UINT)m_cConn];

if (NULL==pCD)
return ResultFromScode(E_OUTOFMEMORY);

for (i=0, j=0; i < CCONNMAX; i++)
{
if (NULL!=m_rgpIUnknown[i])
{
pCD[j].pUnk=(LPUNKNOWN)m_rgpIUnknown[i];
pCD[j].dwCookie=m_rgdwCookies[i];
j++;
}
}

pEnum=new CEnumConnections(this, m_cConn, pCD);
delete [] pCD;

if (NULL==pEnum)
return ResultFromScode(E_OUTOFMEMORY);

//This does an AddRef for us.
return pEnum->QueryInterface(IID_IEnumConnections, (PPVOID)ppEnum);
}

Be aware that the array passed to CEnumConnections is only temporary and that the connection point itself doesn't call AddRef on any of the pointers. This is because the enumerator will make a complete copy of this array in its own constructor, as you can see in the following code.


CEnumConnections::CEnumConnections(LPUNKNOWN pUnkRef, ULONG cConn
, LPCONNECTDATA prgConnData)
{
UINT i;

m_cRef=0;
m_pUnkRef=pUnkRef;

m_iCur=0;
m_cConn=cConn;

m_rgConnData=new CONNECTDATA[(UINT)cConn];

if (NULL!=m_rgConnData)
{
for (i=0; i < cConn; i++)
{
m_rgConnData[i]=prgConnData[i];
m_rgConnData[i].pUnk->AddRef();
}
}

return;
}

CEnumConnections::~CEnumConnections(void)
{
if (NULL!=m_rgConnData)
{
UINT i;

for (i=0; i < m_cConn; i++)
m_rgConnData[i].pUnk->Release();

delete [] m_rgConnData;
}

return;
}

Why go to all the trouble to make a copy? Because of the IEnumConnections::Clone member. Any clone of this enumerator needs its own copy in case the first enumerator is released before this clone or any later clone. Because this implementation doesn't tie all clones together with a common reference count, a copy is necessary.

Finally, like the Next function in CEnumConnectionPoints shown earlier, the Next function in this enumerator also needs to call AddRef on the pointers in each CONNECTDATA structure it enumerates:


STDMETHODIMP CEnumConnections::Next(ULONG cConn
, LPCONNECTDATA pConnData, ULONG *pulEnum)
{
[Other code omitted]

while (m_iCur < m_cConn && cConn > 0)
{
*pConnData++=m_rgConnData[m_iCur];
m_rgConnData[m_iCur++].pUnk->AddRef();
cReturn++;
cConn--;
}

[Other code omitted]
return NOERROR;
}

This is a complete connection point implementation. How then can we use this implementation to assist in firing events?

Triggering Events

I mentioned earlier that the Connect sample makes some compromises because the client, the object, and the sink are all part of the same program. The primary reason for these compromises is that in order for events to be demonstrated, something has to trigger them. In real sources, triggers are usually events that happen to the object directly, such as a mouse click in a window, a change in some data, or an event or a notification sent from some other source altogether. In this sample, however, the triggers are menu items that come as WM_COMMAND messages into the main ConnectWndProc function. To turn these messages into events, ConnectWndProc calls CConnObject::TriggerEvent, passing an ID of the event that has been triggered. Again, an actual client-object relationship in OLE would not have this kind of a C++ mechanism; it could, however, call a trigger function that is part of some other incoming interface on the source. That's perfectly legal.

Regardless of how the trigger reaches the source, the source has to call each and every connected sink that is interested in the corresponding event. This is a perfect time for the source to call IConnectionPoint::EnumConnections on the appropriate connection point. It can iterate over each IUnknown pointer in the CONNECTDATA structures, query each pointer for the right outgoing interface, call the appropriate member function, and then call Release on that pointer as well as on the IUnknown pointer in the CONNECTDATA structures. We can see this exact process in CConnObject::TriggerEvent, which can fire any of the three events in IDuckEvents:


BOOL CConnObject::TriggerEvent(UINT iEvent)
{
IEnumConnections *pEnum;
CONNECTDATA cd;

if (FAILED(m_rgpConnPt[0]->EnumConnections(&pEnum)))
return FALSE;

while (NOERROR==pEnum->Next(1, &cd, NULL))
{
IDuckEvents *pDuck;

if (SUCCEEDED(cd.pUnk->QueryInterface(IID_IDuckEvents
, (PPVOID)&pDuck)))
{
switch (iEvent)
{
case EVENT_QUACK:
pDuck->Quack();
break;

case EVENT_FLAP:
pDuck->Flap();
break;

case EVENT_PADDLE:
pDuck->Paddle();
break;
}

pDuck->Release();
}

cd.pUnk->Release();
}

pEnum->Release();
return TRUE;
}

It is likely that any source will have some similar sort of event-triggering mechanism for the rest of its code to call when the right things happen. You could also make a custom interface for your own internal use with a bunch of member functions named Fire<EventName>. This is especially useful when your events also have arguments. A simple dispatching function such as TriggerEvent doesn't allow for arguments to the events, although separate functions to each event would. This is all quite unimportant, however, because the key to working with connection points is calling the right member function of any connected sink. That's what makes integration work.