Page #108 (Introduction)

< BACK  NEXT >
[oR]

Tightly Coupled Events (TCEs)

Under this technique, the subscriber knows exactly which publisher to request notification from. During run time, the subscriber enlists itself with the publisher to receive events, and un-enlists itself when it is no longer interested in receiving the events.

In order for this technique to work, the publisher and the subscriber need to agree upon a predefined interface to be used for communication. The subscriber provides the publisher with an object that implements this interface, and the publisher calls a method on it when something interesting happens.

This bidirectional communication between the publisher and the subscriber is illustrated in Figure 10.2.

Figure 10.2. Interaction in a tightly coupled event system.
graphics/10fig02.gif

The agreed-upon predefined interface is referred to as the source or the outgoing interface and the object the subscriber provides to the publisher is called the sink object.

For our example, we will define a source interface, IStockPriceUpdate, that can be used to inform a subscriber if the price of a stock has changed. The following is the definition of this interface:

 interface IStockPriceUpdate : IUnknown  {   HRESULT NewQuote([in] BSTR bsSymbol, [in] double dPrice);  }; 

Each time the price of a stock changes, the publisher is expected to call the NewQuote method on this interface, passing in as arguments the symbol of the stock and its current price.

Now let s look at a commonly used technique for implementing a tightly coupled event system called the connection point.

Connection Points

The connection point technique is also referred to as the connectible object technique, and is used in many Microsoft COM-based technologies. In particular, ActiveX controls, ActiveX scripting engines, and VB class modules use the connectible object technique to fire events.

To understand the connection point technique, the following analogy would be helpful.

You are a stock trader who just bought a pager and are interested in receiving stock quotes on the pager. There are many brokerage firms that would provide their clients with stock quotes. However, not all of them provide paging service. Some of them use e-mail as the means to inform their clients. Some others probably provide both services. It is now up to you to ensure that the brokerage firm you choose provides the paging service. This is how your conversation with the broker would go:

You: Do you have any service to inform the clients of a stock price change?

Broker: Yes.

You: Is paging a service that you provide?

Broker: Yes.

You: OK. Here is my pager number. Put me on the list and give me a tracking number that I can use later in case I wish to cancel the paging service.

This is pretty much how connection points work under COM. A subscriber asks the publisher if it supports the connection point mechanism by querying for a standard COM interface IConnectionPointContainer, as shown here:

 CComPtr<IConnectionPointContainer> spCPC;  hr = spMyBroker->QueryInterface(   __uuidof(IConnectionPointContainer), (void**) &spCPC); 

Interface IConnectionPointContainer supports a method, FindConnectionPoint, that can be used to check if the object supports the connection point of a specific interface type. On success, FindConnectionPoint returns a pointer to another standard interface IConnectionPoint, as shown here:

 CComPtr<IConnectionPoint> spCP;  hr = spCPC->FindConnectionPoint(   __uuidof(IStockPriceUpdate), &spCP); 

At this point, the subscriber is ready to create a sink object. The following code fragment shows an implementation of the outgoing interface IStockPriceUpdate:

 class CMySink :    public IStockPriceUpdate,    public CComObjectRoot  { public:  BEGIN_COM_MAP(CMySink)    COM_INTERFACE_ENTRY(IStockPriceUpdate)  END_COM_MAP()    STDMETHODIMP NewQuote(BSTR bsSymbol, double dPrice)    {     ...    }  }; 

Interface IConnectionPoint supports a method, Advise, that can be used to enlist a sink object. On success, Advise returns a cookie (the tracking number), as shown here:

 CComPtr<CComObject<CMySink> > spSink;  hr = CComObject<CMySink>::CreateInstance(&spSink);  spSink->InternalAddRef();  DWORD dwCookie;  hr = spCP->Advise(spSink, &dwCookie); 

The subscriber is now ready to receive the data. Whenever the price of a stock changes, the publisher can call the NewQuote method for all the enlisted objects.

If the subscriber is no longer interested in receiving the data, it can call Unadvise method on the IConnectionPoint interface, passing the cookie (the tracking number) as a parameter, as shown here:

 hr = spCP->Unadvise(dwCookie); 

graphics/01icon01.gif

The pointers to IConnectionPointContainer and IConnectionPoint can be released after Advise has been called. They can be recreated whenever needed.


If you observe the subscriber code, it should be obvious that the connection point is not an efficient mechanism. It takes as many as five round trips to enlist and un-enlist the sink object. [2] It should be noted that a connection point is not the only way to establish a bi-directional communication. All that is needed is for the subscriber to hand out a sink object to the publisher. Whenever possible, one should look for a simpler mechanism for the subscriber to hand out the sink object to the publisher.

[2] The subscriber s code shows only four round trips. The fifth round trip comes from the publisher. The publisher receives the sink object as IUnknown reference from the Advise method. Therefore, the publisher ends up calling QueryInterface on the sink object to receive the typed event interface.

The implementation of the publisher code requires a little more work. Obviously, the publisher should support the IConnectionPointContainer interface. It should also implement a mechanism to manage a list of sink objects, and should provide the logic to detect the change in data and to fire the events. Fortunately, ATL wizard makes it easy to generate the mundane connection point code. The detection of data change is specific to each component and has to be implemented by the developer. The broker example implementation generates some fake data and fires an event every five seconds apart. The source can be found on the CD.

graphics/01icon02.gif

Beware of ATL-generated connection point code. It is not thread-safe. It works fine for an apartment-threaded publisher. For a non-STA object or if the events need to be fired from a worker thread, you not only have to provide the thread-safe logic but also the code to marshal the sink interface to the worker thread. Using GIT (see Chapter 6) would be a good way to achieve both thread-safety as well as marshaling.


The outgoing interface that was defined for our example is suitable for typed languages. Untyped languages such as VBScript can work only with dispatch interfaces. If the interface is defined as dual, it should work from an ActiveX Script supported environment, such as Microsoft Internet Explorer (IE). However, marking a sink interface as dual is a problem for the publisher. Should the publisher fire events on the custom interface or on the dispatch interface? This issue is discussed at length in Effective COM [Box-98].

Under no clear guidelines, a typical implementation ends up using the IDispatch::Invoke mechanism. Therefore, a dual interface as an outgoing interface doesn t serve any real purpose. A pure dispatch interface (dispinterface) is more preferable as an outgoing interface.

For those interested, I have included on the CD a modified version of the sample that uses a dispinterface -based outgoing interface.

Receiving Messages with MSMQ Events

Recall from Chapter 9 that the listener sample received messages synchronously. The Receive method on the IMSMQQueue interface is a blocking call. It doesn t return until it receives a message or a timeout occurs. While this style of coding allows you to process messages as they arrive, it also holds the calling thread hostage.

To support receiving messages asynchronously, MSMQ supports connection points. It defines the following outgoing interface:

 dispinterface _DMSMQEventEvents { properties:  methods:    [id(0)] void Arrived([in] IDispatch* Queue, [in] long Cursor);    [id(1)] void ArrivedError([in] IDispatch* Queue,      [in] long ErrorCode, [in] long Cursor);  }; 

Some programming environments such as VB and ActiveX script engines are capable of creating a sink object based on the definition of the source interface. The interface should be present in the publisher s type library.

A type library defines many interfaces. How would one know which of the interfaces is the source interface?

To address this problem, IDL supports an attribute called source that can be set on an interface. For instance, MSMQ defines a publisher class, MSMQEvent, to describe outgoing asynchronous events. The class definition is shown here:

 [uuid(D7D6E07A-DCCD-11D0-AA4B-0060970DEBAE)]  coclass MSMQEvent {   interface IMSMQEvent;    ...    [default, source] dispinterface _DMSMQEventEvents;  }; 

Let s see how we can implement an MSMQ event subscriber.

Though we can use C++ language to develop the code, let s use VBScript for a change. After all, dispinterface is meant for late-bound languages such as this.

Since the version of IE that ships with Windows 2000 supports VBScript, we will develop and host a web page in IE. To have some fun, we will develop a DHTML-based web page instead of the more traditional HTML web page. For those not familiar with DHTML, here is a little background. When a portion of a web page needs updating, an HTML-based web page requires the whole page to be refreshed. DHTML is an extension of HTML. Under DHTML, only a section of the web page can be updated, thus reducing the flicker on the screen. Why is it fun to use DHTML? You would know if you are a programmer.

To activate an object within IE, an HTML tag called OBJECT needs to be used, as shown here:

 <OBJECT VIEWASTEXT ID=MyRequestEvent    CLASS>  </OBJECT> 

The CLSID of the component is specified within the OBJECT scope. In the above code, the CLSID is that of the MSMQ event class described earlier.

When IE parses this tag, it activates the MSMQ object. This object is referred to by the name MyRequestEvent anywhere in the HTML document.

Based on the type library, IE also recognizes that the object supports firing events. IE then parses the rest of the document to see if either the MyRequestEvent_Arrived or MyRequestEvent_ArrivedError subroutines have been defined (recall that Arrived and ArrivedError are the two methods available on the source interface). If any of the methods are found, IE creates a sink and sets up the connection point. When the publisher invokes a method on the source interface, IE is capable of receiving the event and invoking the appropriate VBScript subroutine.

The main body of the DHTML page is shown here:

 <BODY>    <H2>Receive MSMQ messages</H2>    <INPUT TYPE="button" VALUE="Start"  ID=MyStartButton><BR/><BR/>    <DIV ID=MyDisplayLine></DIV>  </BODY> 

For those unfamiliar with DHTML, the following VBScript line of code would update just the section identified as MyDisplayLine.

 MyDisplayLine.innerText = "Hello World" 

The DHTML body above sets the browser environment that, when the user clicks the Start button, a user-defined VBScript function, MyStartButton_OnClick, will be called. Our implementation of this method will enable receiving MSMQ messages, as follows:

 Sub MyStartButton_onclick    MyStartButton.disabled = true 'Start can be clicked just once    set MyQueueInfo = CreateObject("MSMQ.MSMQQueueInfo")    MyQueueInfo.PathName = ".\MyPubQueue"    Set MyRequestQueue = MyQueueInfo.Open(1, 0)    MyRequestQueue.EnableNotification MyRequestEvent    MyDisplayLine.innerText = "Waiting for messages..."  End Sub 

The logic of opening an MSMQ queue is similar to the one we saw in Chapter 9.

After opening the queue, we call the EnableNotification method on the IMSMQQueue interface (as opposed to the Receive method we called in the earlier samples). As the name indicates, EnableNotification turns on asynchronous messaging. The method takes the sink object as a parameter.

All that is left is to implement the logic to receive and process the message:

 Sub MyRequestEvent_Arrived(ByVal queue, ByVal Cursor)    Dim MyMsg    Set MyMsg = queue.Receive(0)    if Not (MyMsg is Nothing) then      MyDisplayLine.innerText = MyMsg.Body    end if    queue.EnableNotification MyRequestEvent  End Sub 

When the message arrives, we use our familiar Receive method to retrieve the message. Note that the timeout value we specify is zero milliseconds. Since we were notified that a message has arrived, the message will most likely be there in the queue and therefore we don t really need to wait. However, there is a distinct possibility that someone else has received the message. Therefore, we explicitly check to see if the message is not NULL before updating the display.

Also note that the above code calls EnableNotification every time an Arrived event is received. This is required because MSMQ sets up a notification only for the next message when EnableNotification is called. To continue receiving ongoing notifications, you must keep calling EnableNotification each time you process the Arrived event.

Also note that once EnableNotification is set, MSMQ raises events for messages already present in the queue as well as newly arrived messages.


< BACK  NEXT >


COM+ Programming. A Practical Guide Using Visual C++ and ATL
COM+ Programming. A Practical Guide Using Visual C++ and ATL
ISBN: 130886742
EAN: N/A
Year: 2000
Pages: 129

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net