February 25, 2001

VB6/MSMQ (Reading messages asynchronously)

To access MSMQ from VB, you need to set a reference to "Microsoft Message Queue Object Library," which gets added when MSMQ is installed. The object hierarchy lets you find your way to any queue and then create, send, or receive message objects. The following code shows the two methods added to my BagHandler class to provide basic queue-handling functionality:

Public Sub writeToQ(bStream() As Byte, _
sQName As String)
Dim oQInfo As New MSMQQueueInfo
Dim oQueue As MSMQQueue
Dim oMessage As New MSMQMessage
oQInfo.PathName = sQName
Set oQueue = oQInfo.Open(MQ_SEND_ACCESS, _
MQ_DENY_NONE)
With oMessage
.Label = "Betting Message"
.Body = bStream
.Send oQueue
End With
oQueue.Close
End Sub
Public Function readFromQ(sQName As String) _
As Byte()
Dim oQInfo As New MSMQQueueInfo
Dim oQueue As MSMQQueue
Dim oMessage As New MSMQMessage
oQInfo.PathName = sQName
Set oQueue = oQInfo.Open(MQ_RECEIVE_ACCESS, _
MQ_DENY_NONE)
Set oMessage = oQueue.Receive
readFromQ = oMessage.Body
oQueue.Close
End Function


Reading messages asynchronously

By default, the MSMQQueue object's Receive method will wait indefinitely for a message to arrive if there isn't one on the queue already waiting. You can set a timeout parameter to make sure that your program doesn't hang on an empty queue, but even this means that you'd need to keep polling the queue if you wanted to respond dynamically to messages arriving.

Fortunately, there's a better way. By subscribing to a queue on any machine, you can receive an event for every message that gets added to that queue. You can then read the messages from the queue and respond instantly. In other words, responding to a message arriving at a remote queue is no harder than responding to a button-click on a form.

First, declare two object variables at the module level of the form:

Private moQueue As MSMQQueue
Private WithEvents moNotify As MSMQEvent


The first is a simple queue object. The second, an MSMQEvent object, has been declared "WithEvents" in order to receive notifications. Then we code the check box to enable notification when it's clicked:

Private Sub chkNotify_Click()
Dim oQInfo As New MSMQQueueInfo
oQInfo.PathName = txtQName.Text
Set moQueue = oQInfo.Open(MQ_RECEIVE_ACCESS, _
MQ_DENY_NONE)
Set moNotify = New MSMQEvent
moQueue.EnableNotification moNotify
End Sub


This code opens the queue and links the queue object to the MSMQEvent object. This ensures that an event will fire the next time a message is added to the specified queue. Finally, we need to code the event procedure for the oNotify object:

Private Sub moNotify_Arrived(ByVal Queue As Object, _
ByVal Cursor As Long)
Dim b() As Byte
Dim oHandler As New BagHandler
b = oHandler.readFromQ(txtQName.Text)
Set mBet = oHandler.UnStream(b)
displayBet mBet
DoEvents
moQueue.EnableNotification moNotify
End Sub


When the event fires, you simply read the queue in the normal way. The main thing to note here is that you need to re-enable notification after each event. In other words, calling the EnableNotification method only treats you to an event for the next message -- if you want more, you have to ask for them.

Copying objects (Q'ed objects).

Making a carbon copy of an object generally requires bespoke code, especially when dependent objects are involved. However, for persistable objects, a generic copy function can easily be created:

Private Function CopyObject(obj As Object) As Object

Dim oBag As New PropertyBag
oBag.WriteProperty "obj", obj
Set CopyObject = oBag.ReadProperty("obj")
End Function


The result of calling CopyObject is two completely different objects. This isn't to be confused with using the "Set" statement to assign one object variable to another, which just creates two variables pointing to the same object.