How It Works
What goes on when a call is made to a queued component, and an object is passed as an argument?
Here's an example: Suppose you have an object you use to manage customer information called Customer.CCustomer. This object is implemented in the Customer.dll file and has the following code. Note that it is marked as persistable, and note its implementation of the appropriate methods:
'CCustomer Class. Marked as 'Persistable'Option ExplicitPublic Name As StringPublic Age As IntegerPublic Address As StringPrivate Sub Class_ReadProperties(PropBag As PropertyBag) Name = PropBag.ReadProperty("Name") Age = PropBag.ReadProperty("Age") Address = PropBag.ReadProperty("Address") MsgBox "Person read!!"End SubPrivate Sub Class_WriteProperties(PropBag As PropertyBag) PropBag.WriteProperty "Name", Name PropBag.WriteProperty "Age", Age PropBag.WriteProperty "Address", Address MsgBox "Person Written!!"End SubPrivate Sub Class_Initialize() MsgBox "CCustomer Class_Initialize"End SubPrivate Sub Class_Terminate() MsgBox "CCustomer Class_Terminate"End Sub
This sample class does nothing except cache information. In a real world scenario, however, you would probably have other methods (and no message boxes in Class_Initialize!).
Now imagine that there is an object that is designed for server use. This object is called Orders.COrder, is in a separate file called Orders.dll, and has a function with the following code:
Public Sub AddOrder(ByVal ItemID As Long, ByVal Amount As Double, ByVal Customer As Customer.CCustomer) MsgBox "Customer order for item '" & ItemID & "' for " & Customer.Name End Sub
Note that the customer information for the order is directly specified as an object of type Customer.CCustomer. In the real world, you would probably engage in some database activity in this method. However, in this sample all you do is raise a message box.
And last but not least, there is the client EXE application. All this has is a form and a command button in which you type the following code:
Dim oOrder As COrder Dim oCust As CCustomer Set oCust = New CCustomer oCust.Name = "Eduardo A. Jezierski" oCust.Age = 23 oCust.Address = "One Microsoft Way, Redmond, WA" Set oOrder = New COrder oOrder.AddOrder 123, 100, oCust Set oOrder = Nothing Set oCust = Nothing
Say you have configured your orders component inside of COM+, adding it to a COM+ application. When a user clicks the button on the client, the following happens:
- A CCustomer object is created (raising the CCustomer Class_Initialize message box) and populated by the client.
- A COrder object is created.
- The AddOrder method is executed in the COM+ application.
- A message box saying "Customer order for item '123' for Eduardo A. Jezierski" appears.
- The COrder instance is destroyed.
- The CCustomer instance is destroyed, bringing up the CCustomer Class_Terminate message box.
Bring in the Queues
So what happens now if you bring on Queued Components?
To test this, first configure your COrder component as queued. See the following Microsoft Knowledge Base article for instructions:
How To Configuring a COM+ application for Queued Components
Next, modify the client application so that the components created are queued. To do so, change the line where the COrder is created to be as follows:
Set o = GetObject("queue:/new:Orders.COrder")
This will effectively give you a "stand-in" COrder object that will record the method calls and will dispatch them to the COrder object sitting in your COM+ application.
The rest of the application remains the same. When you test it, however, a very different set of events occurs:
- First, a customer object is created and populated by the client.
- A COrder queued recorder instance is created using the GetObject function with the specified moniker.
- The AddOrder method is invoked. The QC recorder, behaving as a COrder, takes in the Customer object, which gets its Class_WriteProperties function called so that its state is persisted, together with all other parameters (ItemID and Amount).
- The COrder-recorder instance is set to nothing. At this point, all the calls invoked on it (just the AddOrder in this case) are packed together with the client security token and other information, wrapped in a Microsoft Message Queue Server (MSMQ) message, and sent off to the destination.
- Having finished its job, the client destroys the customer instance and finishes its work.
- Once the message is sent, you assume that it eventually reaches the intended application's queue.
- Because the application was set to "listen" for messages, it detects the MSMQ message arrival and figures out that it belongs to a COrder class used by user X. After doing access checks, it creates a COrder instance on the server.
- Now the "playback" part of the story begins. The QC player starts invoking the methods the recorder stored in the message.
- In this case, that is just the AddOrder method. However, because the parameters include a Customer object, first a CCustomer instance is created, and it is directed to "load" itself through the Class_ReadProperties function.
- Once the instance is loaded, the player actually invokes the AddOrder method on the real COrder object, so that it does its chores. It passes the new Customer instance as the call argument, and once the call is done, it destroys this temporary instance. It then invokes any other recorded calls in a similar fashion, and then releases the COrder object and temporary CCustomer instances.
Your COrder code executed without realizing that the customer instance it was using was really a local and different instance of the customer object that stored itself in the client, maybe minutes or hours ago. In this way, the whole asynchronous model is maintained.