Click Here to Install Silverlight*
United StatesChange|All Microsoft Sites
MSDN
|Developer Centers|Library|Downloads|Code Center|Subscriptions|MSDN Worldwide
Search for


Advanced Search
MSDN Home > MSJ > May 1998
May 1998

Microsoft Systems Journal Homepage

Wicked Code

Code for this article: May98Wicked.exe (89KB)
Jeff Prosise is the author of Programming Windows 95 with MFC (Microsoft Press, 1996). He also teaches Visual C++®/MFC programming seminars. For more information, visit http://www.solsem.com.

One of the first lessons in most COM programming courses is how to get an interface pointer and call methods through that pointer. Students catch on pretty quickly that once that initial interface pointer is acquired, a client can call any method the object exports either through that interface pointer or by querying the object for other interface pointers.
Technically, a COM interface that clients can call methods on is known as an incoming interface. Sooner or later, most COM programmers also discover the need for outgoing interfaces. An outgoing interface is one that an object doesn't implement itself, but rather relies on its clients to implement. If an object wants to notify its clients every time the price of a certain stock changes, for example, it could support an outgoing interface named IStockInfo containing a method named NewValue. Any client interested in hearing about price changes could implement IStockInfo and pass the object an IStockInfo interface pointer. Price change notifications would then come in the form of calls to the client's IStockInfo::NewValue implementation.
Objects that talk back to their clients are more common than most people realize. In textbook COM, the emphasis tends to be on the design and implementation of incoming interfaces. In the real world, however, outgoing interfaces are often every bit as important as incoming interfaces, especially when COM and DCOM are being used to write highly distributed applications that rely on COM components.
There's nothing wrong with having a client implement an interface and allowing it to pass a pointer from that interface to a COM object via a method call. OLE has been doing it for years with interfaces such as IAdviseSink. And as far as COM is concerned, there is no difference between an incoming interface and an outgoing interface other than who implements it. However, COM supports a formal and more generalized mechanism for establishing two-way channels of communications between objects and their clients that can be used in lieu of simply exchanging interface pointers. That mechanism is called a connection point.
In COM parlance, a connection point is an object that supports published semantics, which enable clients to plug in their outgoing interfaces. These semantics are expressed in the standard COM interfaces IConnectionPoint and IEnumConnections. Connection points are typically implemented as subobjects of a container object that supports IConnectionPointContainer and IEnumConnectionPoints. Together, these four interfaces and their methods form COM's connection point API.
Implementing connection points without a helping hand from a class library is no fun. You can easily spend a couple of days writing the enumerator classes alone, not to mention the code required to manage a linked list or growable array of interface pointers for multicasting. (If 10 clients connect to the same connection point and the connection point calls a method on an outgoing interface, it's supposed to call that method on all 10 clients, not just one. That's multicasting.)
This installment of Wicked Code isn't about wicked code that you write yourself, but rather about some very cool code in a class library that makes COM connection points easy. Both the Active Template Library (ATL) and MFC include code for implementing connection points. Writing connection point code in ATL is relatively straightforward; doing it with MFC, however, requires the use of some obscure and undocumented MFC classes and macros. Assuming you'd like to add connection point support to a future or existing MFC application, I'll take the MFC approach. I'll begin by discussing the support provided within MFC for connection points and connection point containers. Then I'll close out with a pair of sample programs that demonstrate how to implement connection points and connection point event sinks in MFC.

Connection Points and Containers

The starting point for MFC's connection point support is found in CCmdTarget, which implements IConnectionPointContainer. Actually, CCmdTarget doesn't implement this interface directly; instead, it borrows an implementation from an undocumented class named COleConnPtContainer, whose source code is found in Oleconn.cpp.
COleConnPtContainer uses a table known as a connection map to determine whether a given outgoing interface is supported when IConnectionPointContainer::FindConnectionPoint is called, and to enumerate the container's outgoing interfaces when IConnectionPointContainer:: EnumConnectionPoints is called. Connection maps are a close cousin to MFC message maps. Each entry in a connection map holds an IID for an outgoing interface. Connection maps begin with the BEGIN_CONNECTION_MAP macro and end with END_CONNECTION_MAP. In between, CONNECTION_PART macros populate the table with interface IDs and also identify the class that implements the associated connection point. A simple connection map that defines just one outgoing interface looks like this:

 // In CMyContainer's class declaration
 DECLARE_CONNECTION_MAP ()
  .
  .
  .
 // In CMyContainer's class implementation
 BEGIN_CONNECTION_MAP (CMyContainer, CCmdTarget)
     CONNECTION_PART  (CMyContainer, IID_IStockInfo, StockInfoCP)
 END_CONNECTION_MAP ()
In this example, the map tells COleConnPtContainer that CMyContainer supports one connection point. The connection point supports the outgoing interface IStockInfo, and the connection point itself is represented by a nested class named m_xStockInfoCP. The m_x is automatically prepended to the class name specified in CONNECTION_ PART's third parameter.
Where does the nested class come from? It's actually an instance of a class derived from the MFC CConnectionPoint class, which encapsulates COM connection points and implements the IConnectionPoint interface. You don't derive the nested class directly, nor do you declare it explicitly. Instead, you add BEGIN_CONNECTION_PART and END_CONNECTION_PART macros to the container's class declaration, as shown here:

 BEGIN_CONNECTION_PART (CMyContainer, StockInfoCP)
     CONNECTION_IID (IID_IStockInfo)
 END_CONNECTION_PART (StockInfoCP)
Here's what these same three lines look like after preprocessing:

 class XStockInfoCP : public CConnectionPoint
 {
 public:
     XStockInfoCP()
         { m_nOffset = offsetof(CMyContainer,
                                m_xStockInfoCP); }
     REFIID GetIID() { return IID_IStockInfo);
 } m_xStockInfoCP;
 friend class XStockInfoCP;
The derived class overrides CConnectionPoint's virtual GetIID function and returns the interface ID specified in CONNECTION_IID. Not surprisingly, this function is called when the connection point object's IConnectionPoint::GetConnectionInterface method is called.
COleConnPtContainer and CConnectionPoint lie at the heart of the built-in MFC connection point support, but there's more. For one thing, both COleConnPtContainer and CConnectionPoint use enumerator classes to implement the COM enumerator objects returned when IConnectionPointContainer::EnumConnectionPoints or IConnectionPoint::EnumConnections is called. The undocumented CEnumConnPoints and CEnumConnections MFC classes provide canned implementations of both enumerator types. The former implements the COM IEnumConnectionPoints interface; the latter implements IEnumConnections.
Another key element of the MFC connection point support is the undocumented CCmdTarget::EnableConnections function, which should be called from the constructor of the class that contains the connection points. EnableConnections wires a CCmdTarget-derived class to an embedded instance of COleConnPointContainer. Fail to call it and your code will compile but your connection points won't work. Notice that I said an embedded instance of COleConnPointContainer. Where does the embedded instance come from? Browse through CCmdTarget's class declaration in Afxwin.h and you'll find the following structure declaration:

 struct XConnPtContainer
 {
     DWORD m_vtbl;   // place-holder for 
                    // IConnectionPointContainer vtable
 #ifndef _AFX_NO_NESTED_DERIVATION
     size_t m_nOffset;
 #endif
 } m_xConnPtContainer;
EnableConnections uses some C++ trickery to convert m_xConnPointContainer into a genuine COleConnPtContainer.
To be a connection point container, an object must implement IConnectionPointContainer. In MFC, you may add IConnectionPointContainer support to CMyContainer by adding an INTERFACE_PART macro to an interface map. Here's the magic statement:

 BEGIN_INTERFACE_MAP (CMyContainer, CCmdTarget)
     INTERFACE_PART  (CMyContainer,
                      IID_IConnectionPointContainer,
                      ConnPtContainer)
 END_INTERFACE_MAP ()
Notice the third parameter passed to INTERFACE_PART. The preprocessor adds m_x and comes up with m_xConnPtContainer, which references the embedded instance of COleConnPtContainer created by EnableConnections. You could provide your own implementation of IConnectionPointContainer, but by calling EnableConnections in the class constructor and specifying ConnPtContainer in the INTERFACE_PART macro, you're letting MFC do the dirty work. Don't change that third parameter if you want to use the built-in MFC implementation of IConnectionPointContainer.
A final point to consider when using MFC to write an object that supports connection points is how the container calls methods on outgoing interfaces. The nested CConnectionPoint object is the key. A CConnectionPoint exposes the interface pointers passed to its Advise method through a CPtrArray whose address is retrieved with CConnectionPoint::GetConnections. A container can call a method on all the clients connected to a particular connection point like this:

 const CPtrArray* pConnections = 
     m_xStockInfoCP.GetConnections ();
 ASSERT (pConnections != NULL);
 int nConnections = pConnections->GetSize ();
 if (nConnections) {
     for (int i=0; i<nConnections; i++) {
         IStockInfo* pInterface =
             (IStockInfo*) (pConnections->GetAt (i));
         ASSERT (pInterface != NULL);
         // Outgoing!                           
         pInterface->NewValue (nStockPrice); 
     }
 }
In this example, m_xStockInfoCP is the nested connection point object. pConnections holds a pointer to the CPtrArray that holds the interface pointers, and CPtrArray::GetAt retrieves a pointer from the array. Each time it retrieves an IStockInfo interface pointer, this routine calls IStockInfo:: NewValue to pass a new stock value to the client. The for loop ensures that if multiple clients are plugged into the connection point, each one will get called in turn.

The Client Side

To help with the client side of connection point programming, MFC provides a pair of helper functions named AfxConnectionAdvise and AfxConnectionUnadvise. The former plugs an interface pointer into a connection point; the latter disconnects from a connection point. If pObject holds a pointer to some interface on an object that implements an IStockinfo connection point, pStockInfo holds a pointer to the client's implementation of IStockInfo, and m_dwConnectID is a DWORD member variable, then the following code fragment would plug the client's IStockInfo interface into the connection point and cache the connection ID in m_dwConnectID:

 if (AfxConnectionAdvise (pObject,
     IID_IStockInfo, pStockInfo, FALSE,
     &m_dwConnectID) {
     // It worked!
 }
Disconnecting from the connection is equally easy:

 AfxConnectionUnadvise (pObject, IID_IStockInfo,
                        pStockInfo, FALSE, 
                        m_dwConnectID);
 
AfxConnectionAdvise is a wrapper around calls to QueryInterface, IConnectionPointContainer::FindConnectionPoint, and IConnectionPoint::Advise. (See the MFC source code file Ctlconn.cpp for details.) Note that in order to use AfxConnectionAdvise or AfxConnectionUnadvise, you must #include Afxctl.h. It is not #included by default unless you create the application with the MFC ControlWizard rather than the MFC AppWizard.
MFC also helps implement the sink objects. The sink objects are used to listen for method calls that are placed through interface pointers passed to IConnectionPoint::Advise. The MFC BEGIN_INTERFACE_PART and END_INTERFACE_PART macros declare a nested class that implements one COM interface. If IStockInfo is a custom interface that adds one method, NewValue, to the methods it inherits from IUnknown, then a nested class named XStockInfo that implements IStockInfo could be declared like this:

 BEGIN_INTERFACE_PART(StockInfo, IStockInfo)
     virtual HRESULT __stdcall NewValue (long lPrice);
 END_INTERFACE_PART(StockInfo)
If CMyClient is the name of the containing class, then the interface's methods would be implemented in the nested class as shown in Figure 1. Notice that AddRef, Release, and QueryInterface simply delegate to similarly named functions inherited from CCmdTarget. The implementation of IStockInfo::NewValue is not shown because it will be different for every client.

Putting It All Together

The sample programs named CPServer and CPClient (see Figure 2) demonstrate the principles discussed in the previous sections. CPServer is a simple MFC-based out-of-proc COM server that implements a custom interface named IPosition. IPosition adds just two methods to the three it inherits from IUnknown. IPosition::NewPos accepts a long that becomes the object's current position. IPosition::GetPos copies the current position to an address specified by the caller. CPServer's main window displays the current position in numeric form.
Figure 2 CPServer and CPClient
Figure 2 CPServer and CPClient

CPClient is a client for CPServer. At startup, CPClient either creates a new CPServer object or connects to an existing instance. CPClient's main window features a slider control with stops at incremental positions from 0 to 10. When the slider's thumb is moved, CPClient calls IPosition::SetPos to communicate the latest thumb position to CPServer. CPServer, in turn, updates the numeric value displayed in its window.
CPServer and CPClient would hardly be noteworthy if it weren't for the fact that they use connection points. CPServer supports an outgoing interface named INotifyPos whose one method, NewPos, is called whenever the current position changes. CPClient implements the INotifyPos interface and plugs it into CPServer's INotifyPos connection point. When NewPos is called, the slider's thumb moves to the new position reported by CPServer. As a result, you can start n instances of CPClient running, and if you grab one thumb and move it, all n thumbs will move in unison.
Pertinent portions of the sample programs' source code are shown in Figure 3 and Figure 4. In DlgProxy.cpp (see Figure 3) you can see the server's connection map and an interface map that includes IConnectionPointContainer. You'll also find a call to EnableConnections and a function named MulticastPositionChange that's called to notify interested clients of the latest current position. On the client side, the OnInitDialog code in CPClientDlg.cpp (see Figure 4) instantiates the server and plugs into its connection point. The actual implementation of INotifyPos comes from the nested XNotifyPos class. The XNotifyPos methods are implemented at the end of CPClientDlg.cpp.
Another feature that sets CPServer apart from a run-of-the-mill MFC COM server is that it uses a singleton class factory—that is, a class factory that creates just one instance of an object and hands out pointers to that instance in response to subsequent activation requests. For stateless objects, a singleton class factory is a convenient way to enable multiple clients to connect to the same object instance, no matter where that instance lives.
To create the singleton, I derived a class named COleSingleObjectFactory from the MFC class factory class, COleObjectFactory, and overrode OnCreateObject in the derived class (see Figure 3). I then replaced the MFC DECLARE_OLECREATE and IMPLEMENT_OLECREATE macros, which are hardwired to declare an instance of COleObjectFactory, with my own macros named DECLARE_OLECREATE_CUSTOM and IMPLEMENT_ OLECREATE_CUSTOM, which accept a class factory class name in their parameter lists. See DlgProxy.h and DlgProxy.cpp to find out how the macros are declared and used.
Since IPosition and INotifyPos are custom COM interfaces, you must install marshaling support for both before running CPServer and CPClient on your machine. (Both interfaces are simple enough that I could have avoided a custom marshaling proxy by marking the interfaces as Automation-compatible and registering the type libraries, but type library marshaling is not always a viable solution due to the complexity of real-world COM interfaces.) To that end, I've supplied a proxy/stub DLL named CPProxy.dll. To register the DLL, open a command prompt window, navigate to the folder where CPProxy.dll is stored, and type the command:

 regsvr32 cpproxy.dll
CPServer also needs to be registered on your system before it can be created by CPClient. To register CPServer, simply run it once as a standalone application. As it runs, it will automatically update the registry to register itself as an out-of-proc server.

Have a tricky issue dealing with Windows? Send your questions via email to Jeff Prosise: JeffPro@msn.com

From the May 1998 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe.

© 1998 Microsoft Corporation. All rights reserved.
Terms of Use
.

© 2016 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy & Cookies
Microsoft