Implementing and Connecting a Sink

As mentioned earlier, the main client code in the Connect sample is contained within the CApp class, which is defined in CONNECT.H:


//Identifiers for sinks, indexes into m_rgpSink below
enum
{
SINK1=0,
SINK2
};

class CApp
{
friend LRESULT APIENTRY ConnectWndProc(HWND, UINT, WPARAM, LPARAM);

protected:
HINSTANCE m_hInst; //WinMain parameters
HINSTANCE m_hInstPrev;
UINT m_nCmdShow;
HWND m_hWnd; //Main window handle
PCDuckEvents m_rgpSink[2]; //Sinks to connect

PCConnObject m_pObj; //Source object

protected:
void Connect(UINT);
void Disconnect(UINT);
IConnectionPoint *GetConnectionPoint(void); public:
CApp(HINSTANCE, HINSTANCE, UINT);
~CApp(void);

BOOL Init(void);
void Message(LPTSTR);
};

typedef CApp *PAPP;

One of the reasons I say this sample is contrived is that CApp maintains a pointer to the source object it creates using the C++ type instead of an interface. This lets us tell the source to trigger events when the menu commands are given. Obviously, when a real OLE client-object relationship is at work, the client would have only an interface pointer at any given time, and something outside the client would trigger events. That consideration aside, everything else is more or less what any client might have. This class has three protected member functions—Connect, Disconnect, and GetConnectionPointto centralize the connection process for whatever sink we might want to work with. The little enumeration of the values SINK1 and SINK2 provides names for the indexes into the array of sink objects stored in m_rgpSink.

The sinks themselves are implemented using the C++ class CDuckEvents, also defined in CONNECT.H:


class CDuckEvents : public IDuckEvents
{
private:
ULONG m_cRef; //Reference count
PAPP m_pApp; //For calling Message
UINT m_uID; //Sink identifier

public:
//Connection key, public for CApp's usage
DWORD m_dwCookie;

public:
CDuckEvents(PAPP, UINT);
~CDuckEvents(void);

//IUnknown members
STDMETHODIMP QueryInterface(REFIID, PPVOID);
STDMETHODIMP_(DWORD) AddRef(void);
STDMETHODIMP_(DWORD) Release(void);

//IDuckEvents members
STDMETHODIMP Quack(void);
STDMETHODIMP Flap(void);
STDMETHODIMP Paddle(void);
};

This is pretty standard stuff for implementing a simple object with a single interface, which is usually all we need for sink objects. If you require a sink with multiple interfaces, feel free to use any of the three techniques for doing so described in the "Implementing Multiple Interfaces" section in Chapter 2. The implementation of this class, found in SINK.CPP, is also typical. The only interesting parts for our discussion are the event handlers, that is, the implementations of the specific members of IDuckEvents, each of which looks just like the following code for CDuckEvents::Quack, with nothing more than a few names changed:


STDMETHODIMP CDuckEvents::Quack(void)
{
TCHAR szTemp[100];

wsprintf(szTemp, TEXT("Sink #%u received Quack."), m_uID+1);
m_pApp->Message(szTemp);

#ifdef WIN32
PlaySound(TEXT("quack.wav"), NULL, SND_SYNC);
#endif

return NOERROR;
}

Note: The PlaySound function is part of the Win32 API in the import library WINMM.LIB, and it requires a sound card to operate. SND_SYNC means that PlaySound will not return until the sound has finished playing. This ensures that you'll be able to see and hear events in both sinks when they're both connected.

As I mentioned, the Connect sample creates two instances of CDuckEvents during initialization in CApp::Init, using the C++ new operator. Immediately after they're created, CApp::Init calls AddRef on both sinks to ensure that their lifetimes will be stable until Connect is closed. These references are released in CApp::~CApp, which will cause the sink objects to destroy themselves.

Now for the interesting parts of the client code. Object creation happens through use of the C++ new operator within the IDM_OBJECTCREATE case of ConnectWndProc, as seen in the following:


pObj=new CConnObject();

if (NULL!=pObj)
{
fRes=pObj->Init();
pObj->AddRef();
}

Again, this sample is contrived because no OLE client ever has access to a C++ object for an OLE object. All of this creation code would always be encapsulated within an object's own creation process. The call to AddRef here, however, allows the rest of the code to treat the object like any other OLE object, calling Release to free it.

With an instantiated object, you can connect either or both sinks to the source object. Selecting Connect from the menu calls CApp::Connect, and selecting Disconnect calls CApp::Disconnect. Big surprise. In either case, we need to have an IConnectionPoint pointer to the connection point for IDuckEvents. Retrieving this pointer from the object is the purpose of CApp::GetConnectionPoint:


IConnectionPoint * CApp::GetConnectionPoint(void)
{
HRESULT hr;
IConnectionPointContainer *pCPCont;
IConnectionPoint *pCP=NULL;

hr=m_pObj->QueryInterface(IID_IConnectionPointContainer
, (PPVOID)&pCPCont);

if (FAILED(hr))
return NULL;

hr=pCPCont->FindConnectionPoint(IID_IDuckEvents, &pCP);
pCPCont->Release();

if (FAILED(hr))
return NULL;

return pCP;
}

This function executes the sequence described earlier in this chapter for obtaining the connection point interface pointer for a known IID. It also demonstrates that you can call Release on the IConnectionPointContainer interface without harm to the IConnectionPoint pointer. This is because connection points are contained within the source object and therefore contribute to its overall reference count. (It would also be silly to destroy an object you're trying to connect to, so a client will usually have another unrelated interface pointer to the same source object that keeps it alive anyway.)

With the IConnectionPoint pointer for the connection point that supports IDuckEvents, Connect establishes the connection and Disconnect terminates it, as shown in the following:


void CApp::Connect(UINT uID)
{
HRESULT hr;
IConnectionPoint *pCP;

[Trivial validation code omitted]

//Is this sink connected already?
if (0!=m_rgpSink[uID]->m_dwCookie)
{
Message(TEXT("This sink is already connected."));
return;
}

pCP=GetConnectionPoint();

if (NULL!=pCP)
{
hr=pCP->Advise(m_rgpSink[uID]
, &m_rgpSink[uID]->m_dwCookie);

if (FAILED(hr))
Message(TEXT("Connection failed."));
else
Message(TEXT("Connection complete."));

pCP->Release();
}
else
Message(TEXT("Failed to get IConnectionPoint."));

return;
}

void CApp::Disconnect(UINT uID)
{
HRESULT hr;
IConnectionPoint *pCP;

[Trivial validation code omitted]

//Is the sink connected at all?
if (0==m_rgpSink[uID]->m_dwCookie)
{
Message(TEXT("This sink is not connected."));
return;
}

pCP=GetConnectionPoint();

if (NULL!=pCP)
{
hr=pCP->Unadvise(m_rgpSink[uID]->m_dwCookie);

if (FAILED(hr))
Message(TEXT("Disconnection failed."));
else
{
Message(TEXT("Disconnection complete."));
m_rgpSink[uID]->m_dwCookie=0;
}

pCP->Release();
}
else
Message(TEXT("Failed to get IConnectionPoint."));

return;
}

These functions can work with either sink stored in CApp according to the index uID passed as an argument. Connect simply calls IConnectionPoint::Advise and stores the returned connection key in the m_dwCookie member of CDuckEvents. Disconnect checks to be sure the connection exists and then passes that same m_dwCookie to IConnectionPoint::Unadvise to terminate the relationship.

So the stage is set. The lights are turned low. We're ready to see how the connectable object uses these connected sinks to fire events.