Using MSMQ in a Distributed Application
Compuware Corporation Compuware NuMega home page NuMega Lab
teal

Great Jobs!
Search Tools
  DevPartner Studio Enterprise Edition
 · DevPartner Studio
 · Product information
 � Tracking
 · Services and support DriverStudio
 · DriverStudio
 · SoftICE Driver Suite
Products for:
 · Visual C++
 · Visual Basic
 · Java
 · ASP and HTML
 · Team development
 · Device drivers
 · SQL Development
 · SQL Tuning
 · Delphi
 · C++Builder
 · DevPartner Suites
 � Error Detection
 · Previews
 · System Requirements
 · Request information Shop NuMega
 · Buy it!
 · Try it!
 · Price list
 · How to buy
 · Subscriptions
 · Academic Program
 · Sales offices Resources
 · Knowledge Base
 · Press
 · Product brochures
 · Technical papers
 · Customer Stories
 · Recent Reviews Support and Services
 · Technical support
 · Services
 · Training
 · Knowledge base
 · Problem submission
 · Updates
 · Product registration
 · Customer service Register
 · Product updates
 · Product registration
 · Beta programs More information
 · Site map
 · Who to call

  
Legal

Using MSMQ in a Distributed Application

Download this paper here
(PDF Format 60KB)

Download the code files associated with this paper. 
(WinZip 12KB)

Contents

Introduction
When you look at the components that make up the Windows DNA 2000, you see there are various technologies included in the package. One of those technologies is not exactly new--messaging. Why, you may ask, is such an old technology included in the newest vision of Windows DNA? Messaging has been around for years and has an image of being pretty plain.

Using messaging in a single-tier application is like calling yourself on the phone. What's the point? You are going to be there to answer, assuming you dialed the right number, and you already know what you are thinking, hopefully. So, why bother? Applications have been moving toward n-tier architecture and away from the single, monolithic desktop machine.

Imagine you live in one city and your brother lives in another. You pick up the phone to call and say hello. Nobody answers. What do you do? Leave a message on the machine? Call again later? Maybe he left on a vacation and didn't tell you. You might try calling for days and never get through with an urgent message (the prize van and cameras are in front of his house).

This is the sort of problem you will run into more and more as your applications become disconnected and spread over unreliable or unknown connections. In the world of internal applications across a network, you have the luxury of knowing your network is up and running. Applications can talk cross process and you know, with a pretty high certainty, that the communication works. However, many applications are now more than internal apps running on a solid network. I'm not just talking about web sites. An application may use the Internet to talk with servers half way around the world to access a business object.

As an example, say you own a few small bookstores and you need each store to send daily sales reports to the home office. You could use a dial-up modem to connect and send some data. Or you could even be attached through DSL or cable to the Internet and then on to your office. Now, what if the system fails, the dial-up disconnects, the server crashes or the senders system goes down? You reconnect and try again. You now have the possibility of several full copies or partial copies of your data in the system. Can you be absolutely sure the data was received correctly?

These are some of the concerns MSMQ addresses: Guaranteed once-only delivery, transactional messages, prioritization of messages, and connectionless messaging. This paper explores these and other MSMQ topics and shows how you can implement message queuing into your next application.

Return to Contents

MSMQ Basics
Setting up MSMQ can be tricky. In order to install it on a client, there has to be a primary controller online. Early in the installation process, you need to decide whether the client is independent or dependent. Dependent clients are online at all times to send a message. They are incapable of operating offline. When you write an application targeted at a dependent client, you must not attempt to access any API/COM methods of the MSMQ object model when the system is offline. Independent clients can have local queues and send messages to remote queues even when they are offline. We will concentrate on how to build an offline application so we will be concentrating on independent clients.

Figure 1
Figure 1

When setting up an independent client, you will need to know the 'address' of the queue that you wish to send to. To understand how to find out that information, we first need an overview of MSMQ and to look at the basic structures and objects in the MSMQ model.

Return to Contents

Queues
There are two types of queues--public queues and private queues. The name "private" is a little misleading. If the queue was truly private, in the sense of a class member being private, no one could ever see it and send messages to it. In the context of MSMQ, private means only that the queue is not available to users who query the server looking for a queue to send to. You must know the name of the queue in order to access it. Normally, private queues are used as response queues. Let's get into more detail about response queues later.

Let's look at how we search for a queue that is up and online out there in MSMQ land. The steps required to get a list of available public queues is not as straightforward as you would hope. First, you need to create an MSMQQuery object to get a MSMQQueueInfos object from which you get a list of all public queues through a MMSMQQueueInfo object.

Dim oQuery As MSMQQuery
Dim oQInfos As MSMQQueueInfos
Dim oQInfo As MSMQQueueInfo

Set oQuery = New MSMQQuery
Set oQInfos = oQuery.LookupQueue

oQInfos.Reset
Set oQInfo = oQInfos.Next
Debug.Print oQInfo.PathName
Do While Not (oQInfo Is Nothing)
   Set oQInfo = oQInfos.Next
   If Not oQInfo Is Nothing Then
      Debug.Print oQInfo.PathName
   End If
Loop

Then, by interrogating the oQInfo object, you access any of the information available through the properties dialog of the queue. Perhaps the most import information you need is the name and/or ID for the queue. As we develop our offline application, we need one or the other of these bits of information in order to send to the queue offline.

Figure 2
Figure 2

Figure 2 shows the properties for a queue on my \\southside server. To access this queue offline, we need the ID, its address, and to use it as shown in the following code sample.

Dim oQInfo As New MSMQQueueInfo
Dim oQueue As New MSMQQueue

oQInfo.FormatName = "PUBLIC=564cb252-3caf-43bd-86c4-fa1c2a350528"

Set oQueue = oQInfo.Open(MQ_SEND_ACCESS, MQ_DENY_NONE)

If oQueue.IsOpen Then
   MsgBox "Queue " & oQInfo.PathName & " is open"
Else
   MsgBox "Queue " & oQInfo.PathName & " is not open!"
End If

You use the queue's ID as part of the FormatName of the queue that you want to open. Now that we know how to find and open a queue, let's see what we need to do to send a message.

Dim oMsg As New MSMQMessage

oMsg.Body = "this is a test"
oMsg.Label = "Test Message"
oMsg.Send oQueue, MQ_SINGLE_MESSAGE 

This most basic of messages shows one simple, but not obvious point. The second parameter of the send method is required if the queue you are sending to was created as a transactional queue. We'll talk more about transactions later, but for now remember that you need to know the transaction state of the queue you are sending to. You can get that information from the IsTransactional property of the MSMQQueueInfo object. A more complete code fragment would look like:

Dim oMsg As New MSMQMessage

oMsg.Body = "this is a test"
oMsg.Label = "Test Message"
If oQueue.QueueInfo.IsTransactional then
   oMsg.Send oQueue, MQ_SINGLE_MESSAGE
Else
   oMsg.Send oQueue
Endif

If you use the MQ_SINGLE_MESSAGE constant and the queue is not a transactional queue, you get an error. It is important to know about your queue beforehand or to query the MSMQQueueInfo object before you send. Making an assumption here could be a big problem for your customers.

Return to Contents

Messages
If all you wanted to do was send a simple text message, we could end this section right now. Often, you may want to send a more complex message. Perhaps you have some binary data you need to transfer or you have some application-specific, custom data to send via MSMQ. You can send any data that can be represented as a byte array. You can create a PropertyBag and send it as the message or you can send recordsets through the message body. In fact any object that can be persisted can be sent in an MSMQ message body.
 

Dim oPropBag As New PropertyBag

' fill up the property bag
oPropBag.WriteProperty "Title", txtTitle.Text
oPropBag.WriteProperty "Author", txtAuthor.Text
oPropBag.WriteProperty "ISBN", txtISBN.Text

' create an MSMQMessage object and fill it up
Dim oMsg As New MSMQMessage

oMsg.Body = oPropBag.Contents
oMsg.Label = "Message to demonstrate Passing PropertyBag Data"
oMsg.Send m_oRequestQueue

This code sample shows how easily you can create a PropertyBag object on the fly, fill it with data and send it on its way.

rsNew.AddNew
rsNew("Title").Value = txtTitle.Text
rsNew("Author").Value = txtAuthor.Text
rsNew("ISBN").Value = txtISBN.Text
rsNew.Update

' create an MSMQMessage object and fill it up
Dim oMsg As New MSMQMessage

oMsg.Body = rsNew
oMsg.Label = "Message to demonstrate Passing Recordset Data"
oMsg.Send m_oRequestQueue

Passing a recordset is just as easy--you use a dynamically built recordset or one obtained by querying a database. You pass any object that implements IPersistStorage and assign it to the message body. How do you get the IPersistStorage interface in Microsoft Visual Basic? It is easier than you think. Create a class that has its persistable property set to (1) � Persistable. You then create an instance of that class, fill it with data and assign it to the body of the message.

Dim oBook As New CBook

' assign values to our CBook object
oBook.m_strTitle = txtTitle.Text
oBook.m_strAuthor = txtAuthor.Text
oBook.m_ISDN = txtISBN.Text

' create a MSMQMEssage object and fill it up
Dim oMsg As New MSMQMessage

oMsg.Body = oBook
oMsg.Label = "Message to demonstrate Passing Persistent Classes"
oMsg.Send m_oRequestQueue

The preceding sections and code show how to find a queue and send a message to it. But there is much more to MSMQ than sending simple, or complex, messages.

Return to Contents

Getting a Reply and Asynchronous Events
Often when using MSMQ, you may want the receiver to send a message back to you to tell you that the data was received and some action was taken. To do this, you pass an MSMQQueueInfo object as a part of the message. A savvy and knowledgeable receiver application looks at this object and respond to you through this queue. As we mentioned earlier, response queues are usually private queues and you may want several, one for each receiver queue expected to respond back to your messages.

In order to send the response queue to the receiver, you will need to create it and get a reference to the object. Let's look at how to create a queue.

Dim oQueueInfo As New MSMQQueueInfo
Dim bIsTransactionable As Boolean
Dim oRespQueue As MSMQQueue

' If the queue already exists, ignore the error
On Error Resume Next

oQueueInfo.PathName = "\\someserver\private$\response1"
oQueueInfo.Label = "Private Queue for Response 1"

oQueueInfo.Create IsTransactional:=bIsTransactionable

Notice that you actually create an MSMQQueueInfo object and not an MSMQQueue object. Once you have created an MSMQQueueInfo object, open it to get an MSMQQueue object. You then pass this object in the message.

Set oRespQueue = oQueueInfo.Open(MQ_RECEIVE_ACCESS, MQ_DENY_NONE)
' Tell the receiver which queue to respond to
Set oMsg.ResponseQueueInfo = oRespQueue
oMsg.Send oRequestQueue

We will look at the receiver end of this communication later, but first let's see how you receive the response back. Remember the idea of our application is to talk to a possibly disconnected client. If we send this message and then sit in a loop waiting for a response, we have sort of shot ourselves in the foot.

In order to make this work, we need to use MSMQ events to receive the response asynchronously. First, you connect an event handler with a specific queue.

Private WithEvents qevtRecEvents As MSMQEvent

   Set qevtRecEvents = New MSMQEvent
   ResponseQueueInfo.EnableNotification qevtRecEvents

Once the connection is made, you continue with program execution and the qevtRecEvents_Arrived event occurs when the receiver application responds to your message. As always, there is a pitfall to watch for. Each time the event fires, it must be reset so that it fires the next time.

Sub qevtRecEvents_Arrived(ByVal Queue As Object, ByVal Cursor As Long)
   Dim oMsg As MSMQMessage
   Dim oQueue As MSMQQueue

   Set oQueue = Queue
   Set oMsg = oQueue.Receive(ReceiveTimeout:=0)

   ' reset the notification to enabled
   oQueue.EnableNotification qevtRecEvents
End Sub

The Queue is passed in to the event and you use that object to receive the returned message. In the sample above, note the assignment of the Queue parameter to an MSMQQueue object. This is done to take advantage of IntelliSense. While not necessary, it is a very useful trick.

On the receiver end of this communication, things will look very similar to what we have done so far. In order to work asynchronously, we use the MSMQEvent to watch for a message to come into our queue. Once one has arrived, we look at it to see if a response is requested. If a response was requested, we open the queue that was passed to us, create a new MSMQMessage object, create our message and send the reply back to the original client.

Sub qevtRecEvents_Arrived(ByVal Queue As Object, ByVal Cursor As Long)
   Dim oMsg As MSMQMessage
   Dim oRespQueue As MSMQQueue
   Dim oRespMsg As MSMQMessage

   Set oMsg = Queue.Receive(ReceiveTimeout:=0)

   ' in the real world you would process the message here
   lstMessages.AddItem "We got a message: " & oMsg.Label
   lstMessages.Refresh

   ' Let's check to see if there is a request queue that we need 
   ' to send to
If Not oMsg.ResponseQueueInfo Is Nothing Then
   Set oRespQueue = oMsg.ResponseQueueInfo.Open(MQ_SEND_ACCESS, 
MQ_DENY_NONE)

   Set oRespMsg = New MSMQMessage

   oRespMsg.Label = "Response for: " & oMsg.Label
   oRespMsg.Body = "Received your message and processing is 
complete"
   oRespMsg.CorrelationId = oMsg.Id
   oRespMsg.Journal = MQMSG_JOURNAL
   oRespMsg.Send oRespQueue

   End If

   ' Reset the notification for the queue
   Queue.EnableNotification qevtRecEvents

End Sub

One additional piece of data we need in the reply message is some method to correlate the reply with the original message. The sending client may use the same queue for replies from multiple clients or it may send several messages to the same client, expecting responses for each message. When MSMQ sends a message it assigns and ID to the message in the form of a GUID. This ID is unique for each message, as all GUIDS are, and can be used by the sending client to correlate it original message with the reply. In the sample code above, you can see where this ID is assigned to the CorrelationID property of the newly created message.

Transactions and MSMQ
The reply back from the receiver client may be a simple message to let you know that the message was received and acted on. What if the message needs to be a part of a larger event? Perhaps there is a database update that needs to occur before the message is really 'complete.' MSMQ provides two methods of wrapping a series of messages or events into a single transaction. One is an internal MSMQ supported transaction and the other is considered an external transaction, and is related to MTS/COM+ transactions. Let's start with the easiest of all transactions--an internal, single message transaction. You can pass MQ_SINGLE_MESSAGE as the second parameter of the messages' Send method. This guarantees the message is delivered once and only once. This type of transaction cannot participate in any transaction that involves other resource managers. A single message transaction is also an MSMQ internal transaction.

An internal transaction has a simple limitation, it can only send or receive MSMQ messages. As with the special case of a single message transaction, more complex internal transactions cannot be passed to external resource managers to participate in external transactions. The code to send an internal message transaction includes a reference to the MSMQTransactionDispenser object.

Dim oxDispenser As New MSMQTransactionDispenser
Dim oxTrans As MSMQTransaction 

' Start internal transaction
' Commits only occur if explicitly invoked.
Set oxTrans = oxDispenser.BeginTransaction

' Send message to queue as an internal transaction
mSend.Label = "Internal Transaction"
mSend.Body = "This is a test message"
mSend.Send qSend, xact

mSend.Label = "Second IT Message"
mSend.Body = "This is another test message"
mSend.Send qSend, xact

oxTrans.Commit

qSend.Close

Any messages sent between the BeginTransaction and Commit calls on the MSMQTransactiondispencer are considered to a 'part' of the transaction. This is a very simplified example. In real life the BeginTransaction and Commit method calls may not be in the same procedure scope. When reading these messages from the message queue, you need to verify the transaction boundaries of the messages in the queue. You also need to use three properties of the MSMQMessage object to verify if a message is in a transaction. The IsFirstInTransaction and IsLastInTransaction properties are self-explanatory. If the message is a single-message transaction, then both properties are 'True.' The third property used to determine the boundaries of a transaction is TransactionID. All messages sent from an internal transaction must all come from the same machine. This becomes important when reading the messages from a single transaction in a receiving queue. The messages in the transaction are not sent until the Commit method is called. At this point, they are all sent and will be found in the queue in the order that they were sent. They will also be grouped together. If you have two transactions occurring in your sending application, whichever one is committed first will arrive on the receiving queue first, regardless of when the messages were sent on the sending machine.

External transactions are used when a transaction involves more than just sending and receiving messages. They use the Microsoft Distributed Transaction Coordinator as the external resource manager.

Dim xoExtDispenser As New MSMQCoordinatedTransactionDispenser
Dim xoTrans As MSMQTransaction

Dim qSend As MSMQQueue
Dim msg As New MSMQMessage

Set xoTrans = xoExtDispenser.BeginTransaction

qinfo.PathName = ".\vbcTransactionsQueue"

'Assumes queue already exists and is transactional.
Set qSend = qInfo.Open(MQ_SEND_ACCESS, MQ_DENY_NONE)
msg.Label = "External Transaction"
msg.Body = "A test message body"
msg.Send qSend, xoTrans
xoTrans.Commit

Notice the transaction dispenser object used is different from the one used for internal transactions. Messages to a receiving queue, which are a part of an external transaction, may come from different sending computers. This can happen when two different machines have to send MSMQ messages as a part of a single transaction. In this case, the assumption we stated earlier about all messages being grouped together in the receiving queue is no longer valid. The messages from the two different machines will not necessarily be grouped together. This makes it a little trickier to retrieve the messages from the queue, as you need to pay close attention to the TransactionID property.

Lastly, we need to look at MTS transactions. You may have thought this would be covered as an External Transaction, and that would be partially correct. The COM+ transaction works together with the DTC to handle these type of transactions. In fact, MTS transactions are the default for sending MSMQ messages. The transaction parameter of the MSMQ Message Send method has one of five values.

MQ_NO_TRANSACTION
MQ_MTS_TRANSACTION (default)
MQ_SINGLE_TRANSACTION
MQ_XA_TRANSACTION
A dispenser object 

If you specify no argument for this parameter, MSMQ assumes you are participating in& an MTS transaction. Because you are sending to a queue that was created as a transactional queue, you will need to check COM+ to see if you are running in the context of a COM+ transaction. This gives you the constant to pass to the Send method of the MSMQMessage.

Set oCtxObject = GetObjectContext

qSend.Body = "MTS Transaction Test message"
qSend.Label = "Test Message"

' check to see if we are even running under MTS
If (Not oCtxObject Is Nothing) Then
   ' okay, we are in MTS, are we in a transaction?
   If (oCtxObject.IsInTransaction) Then
      qSend.Send ' MQ_MTS_TRANSACTION is the default
   Else
      qSend.Send qInfo, MQ_SINGLE_MESSAGE
   End If
Else
   qSend.Send qInfo, MQ_SINGLE_MESSAGE
End If

qInfo.Close

' if we are under MTS, close the object
If (Not ctxObject Is Nothing) Then
ctxObject.SetComplete
' use oCtxObject.SetAbort to abort the transaction
End If

Set oCtxObject = Nothing
 

While MSMQ itself is rather straightforward, transactions can become quite complex and code heavy. In this paper, we have tried to touch all of the important points you need to be concerned with when writing an MSMQ application. There is an accompanying file with this paper containing all of the code samples in Visual Basic 6.0 project files. Using the sample code and the information in this paper, you should be able to get a quick start on the development of your first, or next, MSMQ application.

Return to Contents

Get the Scoop on the Many Useful Features of Windows 2000!
Download this insightful article written by Dan Fergus, Software Engineer at Compuware, NuMega Lab in Nashua, NH.  This article can be seen in The Developer's Guide Special Fall 1999 Issue of VBPJ. Download the PDF.

NuMega Home · Products · Shop NuMega · Resources · Support · Register · More Info.
Compuware NuMega · Tel: +1 603 578-8400 · Fax +1 603 578-8401
Problems? Contact our webmaster.