Page #103 (MSMQ COM Components)

< BACK  NEXT >
[oR]

Queued Components

A queued component looks and feels like any other COM component that you develop. A client application makes method calls on a queued object much like any other COM object, except that the infrastructure can queue the method calls.

Developing queued components is just as easy. You just write a COM component in the way you are used to, with whatever tools you like (ATL, VB, etc.). There are some relatively minor restrictions on the interface method, such as it cannot have [out] parameters. Next, install the component as a COM+ application, marking its interfaces as queued via the Component Services snap-in. This is all you have to do. COM+ will take care of listening for incoming MSMQ messages from clients and calling your component s methods when they arrive.

Before we get into the architectural details, let s see the queued components in action.

A Simple Phone Book Example

Let s create a simple phone book application. The application stores phone numbers of your friends in an MSDE database. Let s name the database as PhonebookDB. This database contains a table, Friends, that has three columns LastName, FirstName, and PhoneNumber. A friend is identified by his last name and first name.

The following SQL statements can be used to create the database (see Chapter 8 on how to execute these statements):

 create database PhoneBookDB  go  use PhoneBookDB  create table Friends (       [LastName] varchar (15) NOT NULL,        [FirstName] varchar (15) NOT NULL,        [PhoneNumber] varchar (15) NOT NULL)  create unique index MyFriend on Friends([LastName], [FirstName])  go  quit 

Let s develop a component that a client can use to enter the phone number. The following is the VBScript use case:

 Set phoneEntry = CreateObject("PhoneBook.MyPhoneEntry")  phoneEntry.FirstName = "Pradeep"  phoneEntry.LastName = "Tapadiya"  phoneEntry.PhoneNumber = "(222) 333-4444"  phoneEntry.Update  MsgBox "Added a new person to the hit-list" 

Using the ATL wizard, I generated the component code and added the requisite logic. The code can be found on the CD. The following code snippet is relevant to our discussion:

 STDMETHODIMP CMyPhoneEntry::put_FirstName(BSTR newVal)  {   m_bsFirstName = newVal;    return S_OK;  }  STDMETHODIMP CMyPhoneEntry::put_LastName(BSTR newVal)  {   m_bsLastName = newVal;    return S_OK;  }  STDMETHODIMP CMyPhoneEntry::put_PhoneNumber(BSTR newVal)  {   m_bsPhoneNumber = newVal;    return S_OK;  }  STDMETHODIMP CMyPhoneEntry::Update()  {   try {     ADOConnectionPtr spConn = OpenPhoneBookDB();      InsertEntry(spConn, m_bsLastName, m_bsFirstName,        m_bsPhoneNumber);    }catch(_com_error& e) {     return Error((LPCWSTR) e.Description(),        __uuidof(IMyPhoneEntry), E_FAIL);    }    return S_OK;  } 

This code is very simple. Observe that we do not have a single line of code that deals with MSMQ programming.

Let s see how we can configure this component to take advantage of MSMQ.

  1. Using the Component Services snap-in, create a new COM+ server application. I will call it My Phonebook Application.

  2. From the Queuing tab, mark the application as Queued and Listen, as shown in Figure 9.5.

    Figure 9.5. Queuing options.
    graphics/09fig05.gif
  3. Add the PhoneBook component to this application.

  4. Mark the interface IMyPhoneEntry of this component as Queued, as shown in Figure 9.6.

    Figure 9.6. Configuring an interface for queuing.
    graphics/09fig06.gif

graphics/01icon01.gif

Only server applications can avail COM+ queuing Services.


When the application is marked as Queued, COM+ creates message queues for the use of any queued components that may be added to the application. One public queue and six private queues are created. A snapshot of the queues created for My Phonebook Application is shown in Figure 9.7. When the queue is also marked as Listen, it tells COM+ that, when the application is activated, it should activate a listener service to receive incoming calls on the public queue.

Figure 9.7. Queues created for My Phonebook Application.
graphics/09fig07.gif

When an interface is marked as Queued, COM+ expects to receive messages for that interface.

For our demonstration, I will run the client script from a machine other than the one that has the queued component installed.

Typically, a VBScript client code activates an object using CreateObject (CoCreateInstance in C++). For a queued component, however, the activation is slightly different. It uses a VB function GetObject (CoGetObject in C++). This function takes a textual string and obtains the specified object. [3] The format of the text string for obtaining a queued component is as follows:

[3] CoGetObject converts the passed string into a moniker and then binds to the object identified by the moniker. See MSDN documentation for monikers.

 "queue:ComputerName=MachineName/new:ProgId" 

If the component is running on the local machine, the ComputerName field can be omitted, as shown here:

 "queue:/new:ProgId" 

For our experiment, PVDEV is the machine where the component will be executed. PVTEST is the machine where the client script will be executed. Note that, in order to use queued component, the application has to be installed on both machines.

The following is our revised client script:

 Set phoneEntry = _  GetObject("queue:ComputerName=PVDEV/new:PhoneBook.MyPhon  eEntry")  phoneEntry.FirstName = "Pradeep"  phoneEntry.LastName = "Tapadiya"  phoneEntry.PhoneNumber = "(222) 333-4444"  phoneEntry.Update  MsgBox "Added a new person to the hit-list" 

Before we run our test, let s check the PhonebookDB database (using osql.exe). The database should currently have no records.

 1> use PhonebookDB  2> go  1> select * from Friends  2> go   LastName        FirstName       PhoneNumber   --------------- --------------- --------------- (0 rows affected)  1> 

Now I will unplug the network cable for the PVDEV machine and execute the script.

The script executed just fine, even though PVDEV was not reachable.

If you check the outgoing queues on PVTEST, you will notice that there is a message sitting under a queue named PVDEV\My Phonebook Application.

Let s plug the network cable back in.

Shortly after you check the public queue, My Phonebook Application on PVDEV, you will see that a message is sitting in the queue. The message has moved from the outgoing queue of PVTEST to the public queue of PVDEV.

On PVDEV, from the Component Services snap-in, right-click on My Phonebook Application and select the start menu item.

Soon the message from the public queue will disappear.

Let s check the database.

 1> select * from Friends  2> go   LastName        FirstName       PhoneNumber   --------------- --------------- ---------------  Tapadiya        Pradeep         (222) 333-4444  (1 row affected)  1> 

Voila!

We managed to work with a queued component even when the server machine was not available, and without touching the MSMQ API.

Let s see how this magic happened!

Queued Component Architecture

Recall from Chapter 5 that under classic DCOM, when a client activates a remote object, the client gets connected to a proxy. When a method call is made on the proxy, the data is marshaled and sent over the RPC channel to the stub where the data gets unmarshaled and passed to the actual object s method. The whole operation is synchronous and is depicted in Figure 9.8.

Figure 9.8. Classic DCOM using RPC.
graphics/09fig08.gif

With a queued component, the communication between the client and the server is handled with MSMQ instead, as shown in Figure 9.9.

Figure 9.9. Queued component using MSMQ.
graphics/09fig09.gif
Figure 9.9. Queued component using MSMQ.
graphics/09fig09.gif

When a client activates a queued object, the client is not connected to the actual object but to a queued component recorder. The recorder examines the server component s type library to obtain the type of interfaces supported by the object, the methods and properties each interface contains, and the parameters for each method. The recorder then exposes the interfaces of the actual object to the client. The client makes method calls in the usual manner, but they get recorded on the client side. When the client deactivates the object, the recorder packages all the method calls made, along with the call parameters, into one MSMQ message, and sends it to the application-specific public queue on the destination machine.

COM+ provides a utility component called QC.ListenerHelper. When the server application is launched, [4] COM+ activates the QC.ListenerHelper object. This object listens to the public queue specific to the application, retrieves the message, and passes it to the queued component player. The player invokes the actual server component and makes the same method calls in the order they were recorded.

[4] A server application can be launched either from the Component Services snap-in or programmatically through the Catalog Manager interfaces.

If the destination machine is not reachable, the message gets stored in the outgoing queue of the local machine and is forwarded whenever the connection becomes available. As MSMQ uses a transacted protocol for storeand-forward messages, the messages will not be lost.

Note that QC abstracts the details of MSMQ programming; neither the client developer nor the component developer deals with MSMQ directly.

The higher level of abstraction provided by QC has several advantages:

  1. There is no need to learn the MSMQ programming model.

  2. Developing a queued component is no different than developing a typical COM component.

  3. The component can be used either synchronously (via CoCreateInstance, for example) or asynchronously (using CoGetObject).

Note that the recorder flushes its call buffer only when the object is deactivated. There is no way to forcibly flush the call buffer without deactivating the object. However, by using a queued component, the client is saying that it doesn t really care exactly when the call gets processed. Consequently, flushing the buffer is not a concern for QC developers.

Designing queued components has one restriction: any interface that needs to be queued should be unidirectional, that is, it cannot have any method with [out] or [in, out] parameters. Bidirectional interfaces can only be used with synchronous COM applications, as the method calls get blocked until a result is returned. The asynchronous nature of QC makes it impossible to wait for a result; you do not know when the server will receive (and respond to) your request.

How can we get a response back from the server?

Getting a Response

An obvious solution is to get the response back asynchronously as well. Essentially, the server becomes the client.

To accomplish this, the server object has to get enough information so that it can construct the display name that identifies the object to be created. Recall that a queued component is created using the following syntax:

 "queue:ComputerName=MachineName/new:ProgId" 

All that is needed then is the name of the client s machine and the PROGID of the component interested in receiving the response (as with MSMQ, the component receiving the response need not be the same as the component originating the response).

One way to specify this information is for the client to provide its local machine name and the PROGID as parameters to some method calls on the original QC interface. This gives the server the ability to construct the display name and activate the object using CoGetObject.

To demonstrate this, let s extend our earlier example. We will add two new methods to the original interface. [5] The first method, Response, is the response the client expects to receive. It passes a string as a parameter describing the result of the update operation. The implementation is shown in the following code:

[5] The author believes that an interface is immutable only after it leaves his lab.

 STDMETHODIMP CMyPhoneEntry::Response(BSTR bsDesc)  {   USES_CONVERSION;    ::MessageBox(NULL, W2T(bsDesc), _T("PhoneBook"),  MB_OK);    return S_OK;  } 

Note that I am displaying a message box within a method call. As I am running the application as the interactive user, the message box window would pop up in the interactive window station so that I could see it. In general, it is not a good idea to mix user-interface code with non-user-interface code.

The second method, UpdateWithResponse, is a replacement for our original Update method. It takes a machine name as a parameter. The PROGID is hard-coded for the demonstration. The implementation of UpdateWithResponse is shown in the following code:

 STDMETHODIMP CMyPhoneEntry::UpdateWithResponse(   BSTR bsClientMachine)  {   CComBSTR bsDesc;    try {     ADOConnectionPtr spConn = OpenPhoneBookDB();      InsertEntry(spConn, m_bsLastName, m_bsFirstName,        m_bsPhoneNumber);      bsDesc = "Added entry: ";    }catch(_com_error& e) {     bsDesc = static_cast<LPCWSTR>(e.Description());    }    bsDesc += m_bsFirstName;    bsDesc += " ";    bsDesc += m_bsLastName;    // Construct display name to identify the object    CComBSTR bsDisplayName = "queue:ComputerName=";    bsDisplayName += bsClientMachine;    bsDisplayName += "/new:PhoneBook.MyPhoneEntry";    CComPtr<IMyPhoneEntry> spPhoneEntry;    HRESULT hr = ::CoGetObject(bsDisplayName, NULL,      __uuidof(IMyPhoneEntry), (void**) &spPhoneEntry);    if (FAILED(hr)) {     return hr;    }    spPhoneEntry->Response(bsDesc);    return S_OK;  } 

After updating the database, the server object constructs the string describing the result of the operation, constructs the display name to identify the object, activates the object, and invokes the Response method on it.

This code should also give you an idea of how to activate a queued component from C++.

The original VBScript client code has to be slightly modified to use the newly added method.

 Set phoneEntry = _  GetObject("queue:ComputerName=PVDEV/new:PhoneBook.MyPhoneEntry")  phoneEntry.FirstName = "Pradeep"  phoneEntry.LastName = "Tapadiya"  phoneEntry.PhoneNumber = "(222) 333-4444"  phoneEntry.UpdateWithResponse "PVTEST" 

The client machine in my experiment is PVTEST. When this script was executed from PVTEST, a message box popped up on the screen after a few seconds that said the database could not be updated because a record with the index (FirstName+LastName) already exists. This error message is what was expected. Our earlier experiment had already added the specific record to the database.

Transaction Support

The transaction support that is available in the underlying MSMQ mechanism is also available to QC.

Between the client and the recorder

The recorder takes the same attributes as the client. If the client creates a queued component within the context of an existing transaction, the transaction is propagated to the recorder. When the recorder is deactivated, it hands over the message to MSMQ in the context of the transaction. MSMQ buffers the message pending the outcome of the transaction. If the transaction commits, MSMQ transmits the message to the recipient. If the transaction aborts, MSMQ discards the message; the server never gets the recorded COM calls.

On the server side

QC uses the transactions on the server side as well. If the component is marked as transactional, the QC Listener helper on the server side becomes the root of the transaction. If a server-side operation aborts for some reason, the message that triggered it is put back on the queue for a later retry.

There is a problem associated with putting the message back into the queue for a later retry, however; some messages may cause a transaction to abort repeatedly. If such a message is put back into the queue, the next time the component dequeues the message the transaction will abort and the mes-sage will go back to the queue. Other messages in the queue would never get retrieved. One bad message may just hang up the whole application. This is unacceptable.

In order to solve this problem, QC moves the message to a different queue so that it doesn t block other messages. Now you know why COM+ created six private queues when the application was marked as Queued. An aborted message moves to queue 0 and moves upwards as future retries continue to fail. Higher numbers indicate longer time intervals between the retries. After five attempts (that brings the message to queue 4), the message is moved to the private queue called the DeadQueue, from which it will not be retried.

graphics/01icon02.gif

Once in a while, the system administrators should check the dead queue, fix the cause of failures, and move the message back to a lower queue. Alternatively, you can write some administrative code to periodically check this queue and take action.


Using Non-Transactional Queues

Transactional messages in general are slow. You get robustness at the cost of performance.

When an application is marked as queued, the default behavior of COM+ is to create the application-specific queues as transactional. Recall that the transactional attribute on a queue cannot be changed once the queue is created.

If you know that the components you have do not require transactions, there is a way to make the queues non-transactional. Before installing the application, manually create the queues (as non-transactional). Just remember to use the correct queue names. Install the application after creating the queues. As the queues already exist, COM+ will just reuse them. All of the QC s internal operations will then use the non-transactional queues and execute somewhat faster.


< 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