cs545_callbacks

Document Sample
cs545_callbacks Powered By Docstoc
					                                                                                              1


              COM Events, Callbacks - Connection Points
         So far the relationship between a COM client and a server has been unidirectional,
i.e., the only the client makes a call to the methods on the server. The client’s call is
blocking type, i.e., the client has to wait to get the results of the call before proceeding its
execution. In many practical situations, we would like the COM server to notify the client
if something interesting has happened. For example a client may want to know if the
price of a stock has exceeded a certain threshold. Rather than client making periodic calls
to the “COM stock quote server”, it would be more efficient if the stock server called
back to the client. Thus there are two mechanisms in COM for this purpose, one is called
callbacks and the other events (connection points).
         The conceptual difference between an event interface and a callback interface is
that an event is designed to be more like an anonymous broadcast by the server, while a
callback functions identifies the destination of the client precisely by the COM server.
         In Visual Basic, Any client object that has a reference to an event source (in the
server) can handle the events the source raises by simply placing that reference into a
WithEvents variable. In C++, handling connection points is a little more involved, but the
idea is similar. In either case, the event source has no idea what objects may be handling
its events. There may be a hundred objects paying close attention or there may be none.
The key point is that the event source blindly notifies each connected client through its
event (connection point) interface, without any knowledge of whether the client is
handling the event or what the interface to the client actually looks like.
         In contrast, a server designed to perform callbacks must have an explicit reference
to every object that needs notification, it must connect and manage those references, and,
finally, it must perform the notification. Basically, the server must know exactly how
many clients there are and how to connect and interact with each of them.
         A Callback mechanism provides for a more efficient mechanism to delivering
notifications. The event mechanism is very popular in ActiveX controls (COM
components with a GUI), as the user may be generating events by interacting through an
ActiveX control, and some of these events may need to be passed to the container (client)
of the ActiveX control.

        COM provides a general mechanism to establish the bi-directional relationship
between the client and the server through a set of interfaces known as “Connection Point
Interfaces”. The general idea is that the server will list the interfaces on whose methods
the server will actually call the client (known as outgoing interfaces). The client
obviously will implement these interfaces and also register itself with the server so that it
can receive callbacks. In general, the responsibility of the COM server is to:
    1. Advertise that it supports outgoing interfaces (callbacks).
    2. List the outgoing interfaces that it supports.
    3. Provide a generic means for the client to register and unregister its
        implementation of the outgoing interfaces with the server.

There are four main interfaces in the Connection Points architecture. These are
IConnectionPointContainer, IConnectionPoint, IEnumConnections, and
IEnumConnectionPoints. There is also a minor interface called IProvideClassInfo.
                                                                                             2


Supporting the IConnectionPointContainer interface allows a COM server to advertise
that it supports outgoing interfaces and to advertise the outgoing interfaces it supports.
IconnectionPoints provides a generic means for the client to register its implementation
of a particular outgoing interface. IEnumConnections, and IEnumConnectionPoints are
collections (or also known as enumerations) that contain connection points and
connection-to-connection points, respectively. IProvideClassInfo is used to return the
type library information, such as parameter list for the methods in an outgoing interface.

IConnectionPointContainer: this interface has a method called
EnumConnectionPoints that lets the client find out the list of IConnectionPoint interfaces
the server supports. The interface has another method called FindConnectionPoint that a
client can use to find a particular IConnecPoint implementation if it knows the GUID of
the outgoing interface. Here is the idl for IConnectionPointContainer interface from
ocidl.idl.
interface IConnectionPointContainer : IUnknown
{
   typedef IConnectionPointContainer * PCONNECTIONPOINTCONTAINER;
   typedef IConnectionPointContainer * LPCONNECTIONPOINTCONTAINER;

    HRESULT EnumConnectionPoints
    (
       [out] IEnumConnectionPoints ** ppEnum
    );

    HRESULT FindConnectionPoint
    (
       [in] REFIID riid,
       [out] IConnectionPoint ** ppCP
    );
}

IConnectionPoint: This allows a client to register itself for receiving callbacks from
the COM server. An object that implements the IConnectionPoint interface is called a
source because it produces events. The client’s implementation of the outgoing interface
is called a sink. The client passes its implementation of the outgoing interface to the
Advise method of the IConnectionPoint interface. Similarly, the client can unregister
itself for receiving notifications by calling the UnAdvise method of the interface. There is
also a method in the IConnectionPoint interface called GetConnectionInterface that
returns the IID of the interface that is associated with this Connection point.
Here is the idl for the IConnectionPoint interface from ocidl.idl.
interface IConnectionPoint : IUnknown
{
   typedef IConnectionPoint * PCONNECTIONPOINT;
   typedef IConnectionPoint * LPCONNECTIONPOINT;

    HRESULT GetConnectionInterface(
          [out] IID * pIID
       );

    HRESULT GetConnectionPointContainer(
         [out] IConnectionPointContainer ** ppCPC
                                                                                       3

        );

    HRESULT Advise(
          [in] IUnknown * pUnkSink,
          [out] DWORD * pdwCookie
       );

    HRESULT Unadvise(
          [in] DWORD dwCookie
       );

    HRESULT EnumConnections(
         [out] IEnumConnections ** ppEnum
    );
}

Note that outgoing interface are almost always dispinterfaces.




                                         IConnectionPointContainer   Server -
               Client
                                                                     Connectable
                                                                     Object



                                          IconnectionPoint
                                                                     Connection
                 Sink                                                Point Object
                                      Outgoing interface
                                          IconnectionPoint
                 Sink                                                Connection
                                                                     Point Object
                                      Outgoing interface



Example:
        We will implement a simple multithreaded StockQuote server which will support
connection points. There will be one interface in it called IStockEvents with one method
in the interface called PriceChange. To create the COM server, the IStockEvents interface
will be declared as a pure automation interface. The server will provide the
implementation of IConnectionPoint interface which will work with the IStockEvent
interface (Advise, UnAdvise etc..). The server will also implement the
IConnectionPointContainer interface where the EnumConnectionPoints returns the
enumeration containing the single connection point in this case. The
FindConnectionPoint method of the IConnectionPointContainer returns the connection
point when it is passed the IID of the IStockEvents interface.
                                                                                               4


If the client is written in C++ and wants to connect to the StockQuote server, the protocol
is as follows:
     1. Client calls the QI (QueryInterface) to request the IConnectionPointContainer
         interface of the connectable object (server).
     2. Client calls the FindConnectionPoint specifying the IID of the outgoing interface
         (e.g., IStockEvents) to get the IConnectionPoint associated with the IStockEvents.
     3. Client calls the Advise method on IConnectionPoint interface passing in the
         client’s implementation of the IStockEvents interface.
     4. Since the server expects an IUnknown pointer in the Advise call, the server has to
         call QI to get the ISTockEvents interface.
     5. Release the IConnectionPoints interface.

Events using the connection point scheme described above work well for inproc COM
servers (e.g., ActiveX controls). For out-of-process servers, it is more efficient to create
your own interfaces for custom callbacks to the client rather than using the general
connection point protocol.

Create a new ATL Appwizard project. Name the project StockQuote. Choose the server
type to be an executable.




By default, ATL creates the server type to be apartment threaded. You need to manually
change the following line in Stdafx.h file
#define _ATL_APARTMENT_THREADED                     to free threaded as shown below.
#define _ATL_FREE_THREADED

Now we need to add a free threaded Connection-Point enabled class. Choose Insert-
>New ATL object from the menu, then choose simple object. Select StockMonitor as the
short name, but make sure you set choose the free threading model from the attributes
tab. Check also the “Support Connection Points” check box, and the “ISupportErrorInfo”
check box.
                                                                                          5




If you examine the StockMonitor.cpp file, you will notice that it is being derived from
IConnectionPointContainerImpl class. Similarly a COM_MAP entry has been added for
the IConnectionPointContainer interface as shown below. A blank entry for
CONNECTION_POINT_MAP has also been added.

class ATL_NO_VTABLE CStockMonitor :
        public CComObjectRootEx<CComMultiThreadModel>,
        public CComCoClass<CStockMonitor, &CLSID_StockMonitor>,
        public ISupportErrorInfo,
          public IConnectionPointContainerImpl<CStockMonitor>,
          public IDispatchImpl<IStockMonitor, &IID_IStockMonitor, &LIBID_STOCKQUOTELib>
{
public:
          CStockMonitor()
          {
          }

DECLARE_REGISTRY_RESOURCEID(IDR_STOCKMONITOR)

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CStockMonitor)
      COM_INTERFACE_ENTRY(IStockMonitor)
      COM_INTERFACE_ENTRY(IDispatch)
      COM_INTERFACE_ENTRY(ISupportErrorInfo)
          COM_INTERFACE_ENTRY(IConnectionPointContainer)
END_COM_MAP()
BEGIN_CONNECTION_POINT_MAP(CStockMonitor)
END_CONNECTION_POINT_MAP()

The COM_MAP is used to implement the QueryInterface to return the list of interfaces
supported by the COM class. The CONNECTION_POINT_MAP is used to implement
the EnumConnectionPoints and FindConnectionPoint methods in
IConnectionPointContainer.
                                                                                      6


By checking the “Support Connection Points” check box, the wizard also added an
outgoing interface called _IStockMonitorEvents to the IDL file as shown below.
       [
               uuid(E51E75D8-B6C6-4A7C-8F05-2805BD813326),
               helpstring("_IStockMonitorEvents Interface")
       ]
       dispinterface _IStockMonitorEvents
       {
               properties:
               methods:
       };

Also note that _IStockMonitorEvents interface has been added to coclass in IDL as a
source interface, as shown below.
        [
               uuid(68DF21DD-5432-42F3-94AF-71CACEBFE8A9),
               helpstring("StockMonitor Class")
        ]
        coclass StockMonitor
        {
               [default] interface IStockMonitor;
               [default, source] dispinterface _IStockMonitorEvents;
        };

Now we will add two methods to _IStockMonitorEvents outgoing interface. From the
class view tab, add the PriceChange merthod to the _IStockMonitorEvents interface.
The IDL for this method is:
[in]BSTR ticker, [in]float newPrice, [in] float oldPrice




Similarly add another method called MonitorInitiated to the _IStockMonitorEvents
interface. The IDL for the method is:
[in] BSTR ticker, [in] float currentPrice
                                                                                          7




To implement the connection point interface, you must first compile the IDL to generate
the type library. Right click on the IDL file and choose compile from the context menu.
Next, from the class view tab, right click on the CStockMonitor class and choose
“Implement Connection Point” from the context menu, then check the
_IStockMonitorEvents checkbox and click OK.
                                                                                                8




This will add a new template class to the project that implements the IConnectionPoint
interface. It also adds the ConnectionPoint class as one of the base classes in the
CStockMonitor class, so the methods in this new class will be automatically available to
the CStockMonitor class. The new class called CProxy_IStockMonitorEvents derives
from the ATL class IConnectionPointImpl and contains Fire methods for the two
methods that you had added to the _IStockMonitorEvents outgoing interface, as shown
below.
template <class T>
class CProxy_IStockMonitorEvents : public IConnectionPointImpl<T, &DIID__IStockMonitorEvents,
CComDynamicUnkArray>
{
         //Warning this class may be recreated by the wizard.
public:
         HRESULT Fire_PriceChange(BSTR ticker, FLOAT newPrice, FLOAT oldPrice)
         {
         …… code omitted
         }
         HRESULT Fire_MonitorInitiated(BSTR ticker, FLOAT currentPrice)
         {
         ……. code emitted
         }
};
#endif

The server code calls these Fire methods whenever it wants to send a notification to the
client.

You can also note that the CStockMonitor class is now being derived from
CProxy_IStockMonitorEvents and also that a connection point map entry has been added
as shown below:
BEGIN_CONNECTION_POINT_MAP(CStockMonitor)
CONNECTION_POINT_ENTRY(DIID__IStockMonitorEvents)
END_CONNECTION_POINT_MAP()
                                                                                           9


The COM server also has a regular interface called IStockMonitor (in addition to the
Connection point interfaces). We will add two methods to this main interface. Right click
on the IStockMonitor interface and add a method called GetPrice to it. The IDL for this
method is:
[in] BSTR ticker, [out,retval] float * price. Similarly add another method called
AddNewStock with the IDL as,
[in] BSTR ticker, [in] float price, [in] short propensityToRise

Next we will add a simple member function to the CStockMonitor class (not a method in
an interface). This member function is called “MonitorStocks” and its purpose is to
randomly fluctuate the stocks that are being monitored and Fire events to the client
through the Fire_PriceChange method in the connection point implementation.
Right click on the CStockMonitor class (from the class view tab), then choose add
member function, type the following for this member function.




Now we will add a thread function to the project (recall that a thread is a simple function,
not a member function).
Add the prototype for the worker thread in the stockmonitor.h file as,
DWORD WINAPI threadProc(void *pv); // worker thread

Add the following code for the thread function in the stockmonitor.cpp file.
DWORD WINAPI threadProc(void *pv)
{
     DWORD retval;
     CStockMonitor * stMon;
     HRESULT hres;
     hres = CoInitializeEx(NULL,COINIT_MULTITHREADED);
             // join an MTA if one already exists else create MTA
     if (SUCCEEDED(hres))
     {
             stMon = (CStockMonitor *) pv;
             while (!stMon->m_bHaltThread);
             {
                       stMon->MonitorStocks();
                       ::Sleep(3000);
             }
             CoUninitialize();
             SetEvent(stMon->m_hEventShutdown); // Winapi SetEvent
                       // this lets the WaitForSingleObject to proceed
             retval=0;
                                                                                              10

        }
        else
                  retval = 2;
        return retval;
}

The thread function shown above, receives a CStockMonitor object as a parameter, then
it periodically calls the MonitorStocks() function, until the data member of
CStockMonitor class called m_hEventShutDown is set to true.

Modify the stockmonitor.h file and add the following lines shown in
bold.
// StockMonitor.h : Declaration of the CStockMonitor

#ifndef __STOCKMONITOR_H_
#define __STOCKMONITOR_H_

#include "resource.h"  // main symbols
#include "StockQuoteCP.h"
#include <map>
#include <vector>
#include <time.h>
using namespace std;
/////////////////////////////////////////////////////////////////////////////
// CStockMonitor
class ATL_NO_VTABLE CStockMonitor :
             public CComObjectRootEx<CComMultiThreadModel>,
             public CComCoClass<CStockMonitor, &CLSID_StockMonitor>,
             public ISupportErrorInfo,
             public IConnectionPointContainerImpl<CStockMonitor>,
             public IDispatchImpl<IStockMonitor, &IID_IStockMonitor, &LIBID_STOCKQUOTELib>,
             public CProxy_IStockMonitorEvents< CStockMonitor >
{
public:
        CStockMonitor()
        {
              DWORD threadID;
              HANDLE hThreadHandle;
              srand((unsigned)time(NULL));
              m_bHaltThread = false;
              AddNewStock(CComBSTR("MSFT"),75,55);
              AddNewStock(CComBSTR("IBM"),95,78);
              AddNewStock(CComBSTR("INTC"),45,25);
              AddNewStock(CComBSTR("SUNW"),25,10);
              AddNewStock(CComBSTR("ORCL"),35,5);
              hThreadHandle=CreateThread(0,0,threadProc,this,0,&threadID);
        }
……..
…….. // other code omitted
                                                                                             11

public:
         void MonitorStocks();
         STDMETHOD(AddNewStock)(/*[in]*/ BSTR ticker, /*[in]*/ float price, /*[in]*/ short
propensityToRise);
         STDMETHOD(GetPrice)(/*[in]*/ BSTR ticker, /*[out,retval]*/ float * price);
void FinalRelease()
{
       m_hEventShutdown = CreateEvent(NULL,false,false,NULL);
       m_bHaltThread=true;
       WaitForSingleObject(m_hEventShutdown,INFINITE);
       m_StockPriceList.clear();
       m_StockPropensityList.clear();
       m_StockTickerList.clear();
}
bool m_bHaltThread;
HANDLE m_hEventShutdown;
private:
       map<CComBSTR,float>m_StockPriceList;
       map<CComBSTR,short>m_StockPropensityList;
       vector<CComBSTR>m_StockTickerList;
       };
DWORD WINAPI threadProc(void *pv); // worker thread
#endif //__STOCKMONITOR_H_

Notice that the constructor for the CStockMonitor class calls the CreateThread winapi
function to create the thread. The parameter to the thread function is the fourth parameter
in the CreateThread call. The FinalRelease method will be called when the interface
count goes to zero and the COM server object is being destroyed. We need to shut down
the worker thread and clear all the STL collections in this case.

Next, implement the MonitorStocks member function, GetPrice and AddNewStock
methods (in the stockmonitor.cpp file). The code is shown below.
const
E_STOCKNOTFOUND=MAKE_HRESULT(SEVERITY_ERROR,FACILITY_ITF,0x2
00+99);
const
E_STOCKEXISTALREADY=MAKE_HRESULT(SEVERITY_ERROR,FACILITY_IT
F,0x200+99);
STDMETHODIMP CStockMonitor::GetPrice(BSTR ticker, float *price)
{
     // TODO: Add your implementation code here
     map <CComBSTR,float>::iterator iter;
     ObjectLock lock(this);
     iter = m_StockPriceList.find(ticker);
     if (iter != m_StockPriceList.end())
                *price=m_StockPriceList[ticker];
     else{
                *price = -1.0;
                return Error(_T("This Stock does not exist"),IID_IStockMonitor,
                                                                                            12

                         E_STOCKNOTFOUND);
        }

        return S_OK;
}

STDMETHODIMP CStockMonitor::AddNewStock(BSTR ticker, float price, short propensityToRise)
{
     // TODO: Add your implementation code here
     map <CComBSTR,float>::iterator iter;
     ObjectLock lock(this);
     iter=m_StockPriceList.find(ticker);
     if (iter==m_StockPriceList.end())
     {
               m_StockPriceList[ticker]=price;
               m_StockPropensityList[ticker] = propensityToRise;
               m_StockTickerList.push_back(ticker);
               Fire_MonitorInitiated(ticker,price);
     }
     else
               return Error(_T("This Stock already exists"),IID_IStockMonitor,
                        E_STOCKEXISTALREADY);

        return S_OK;
}

void CStockMonitor::MonitorStocks()
{
        map <CComBSTR,float>::iterator iter;
        short propensityValue, numStocks, randomNumber, index;
        CComBSTR ticker;
        float oldPrice, newPrice;
        ObjectLock lock(this);
        numStocks = m_StockPriceList.size();
        if (numStocks > 0) {
                 randomNumber = rand() * 1000;
                 index = randomNumber % numStocks;
                 ticker = m_StockTickerList[index];
                 oldPrice = m_StockPriceList[ticker];
                 propensityValue = m_StockPropensityList[ticker];
                 if ((randomNumber % 100) < propensityValue)
                           newPrice = oldPrice+4.0;
                 else
                 {
                           if (oldPrice > 2.0)
                                     newPrice=oldPrice-2.0;
                 }
                 m_StockPriceList[ticker]=newPrice;
                 Fire_PriceChange(ticker,newPrice,oldPrice);
        }
}

Note that since our COM server is multithreaded (each creation of the CStockMonitor
object by the client results in a new thread being created - recall the constructor of
CStockMonitor was creating a thread), each member function and method in the
                                                                                           13


CStockMonitor class needs protection from multiple access. ATL provides a very easy
mechanism in the form of a Critical Section and takes care of the initialization and
unlocking issues. All you have to do is to protect each function by calling ObjectLock
lock(this) in the beginning of the function (unlocking is taken care of when the lock
object goes out of scope).

Before you build the server, turn the exception handling on through project->settings
menu item. Then build the server.


Creating the Client:
       We will create a Visual Basic client for the StockQuote server. Create a standard
exe type VB application.
From the project->components menu, add “Microsoft windows common controls 2” as
shown below. This is done in order to use the Up/Down spinner control.




Draw the user interface consisting of a list box (with name lstEvents), three text boxes
(with names txtTicker, txtPrice, txtPropensity), one Up/Down control (with name
updPropensity) and a button (with name cmdAddStock) as shown below.
                                                                                     14




Add a reference to the StockQuote type library from the Project->References menu
item.




Note that you can pick the object and the event you want to write from the code window
drop down list boxes as shown below.
                                                                                       15


Name of object                                    Name of event




The code for the different handlers is shown below.
Note that when you declare an object using WithEvents key word, VB checks the type
library for source interfaces, then it presents the methods in these as handlers. If you
choose the mStockServer from the left list box in the code window, you will see that the
two events are available to us in the right list box.

Private WithEvents mStockServer As StockMonitor
Private Sub cmdAddStock_Click()
 On Error GoTo L1
 Call mStockServer.AddNewStock(txtTicker.Text, txtPrice.Text, _
                    txtPropensity.Text)
 Exit Sub
L1:
 MsgBox (Err.Description)
End Sub

Private Sub Form_Load()
  On Error GoTo L1
  Set mStockServer = New StockMonitor
  Exit Sub
L1:
  MsgBox (Err.Description)
End Sub

Private Sub Form_Unload(Cancel As Integer)
                                                                                                  16

 Set mStockServer = Nothing
End Sub

Private Sub mStockServer_MonitorInitiated(ByVal ticker As String, ByVal currentPrice As Single)
  Dim msg As String
  msg = "Monitor Initialized for " & ticker & _
      "CurrentPrice $" & CStr(currentPrice)
  MsgBox (msg)
End Sub

Private Sub mStockServer_PriceChange(ByVal ticker As String, ByVal newPrice As Single, ByVal
oldPrice As Single)
 Dim msg As String
 msg = "Stock: " & ticker & " New Price: $" & _
   CStr(newPrice) & "Old Price: $" & CStr(oldPrice)
 lstEvents.AddItem (msg)
 lstEvents.TopIndex = lstEvents.NewIndex
End Sub


Private Sub updPropensity_DownClick()
 txtPropensity.Text = CInt(txtPropensity.Text) - 1
End Sub

Private Sub updPropensity_UpClick()
 txtPropensity.Text = CInt(txtPropensity.Text) +1
End Sub

Build and test the program.

				
DOCUMENT INFO
Shared By:
Categories:
Tags:
Stats:
views:6
posted:11/2/2011
language:English
pages:16