SUMMARY
The ActiveX model for Microsoft Message Queue (MSMQ) cursors and
asynchronous notification is somewhat complex and, if improperly used,
error-prone. The ActiveX design trades off simplicity for functionality in
order to allow the ActiveX programmer (virtually) full control over
asynchronous processing. A side effect of the resulting model is that it
allows the programmer to create potentially useless constructs. Therefore,
the ActiveX programmer must understand fundamentally how MSMQ cursors work
in order to effectively process messages asynchronously.
Note that the ActiveX model in this case is not harder than the C API
model, it is just not significantly easier, which is something MSMQ strives
to do with the ActiveX model in other areas.
The purpose of this article is fourfold as follows:
- To provide a fundamental understanding of what the two ActiveX cursors
are, how they behave and the effect of the API calls on them.
- To provide an appreciation of the potential complexity of manipulating
the cursors.
- To provide programming guidelines for correct and simple handling of the
cursors.
- To overview the common pitfalls and errors encountered when the cursors
are not handled correctly.
This is not an exhaustive documentation of all cursor behavior and
asynchronous notification. This article assumes that the reader is familiar
with these topics as documented in the online Software Development Kit
(SDK) Help file and related Microsoft Knowledge Base articles.
For simplicity of illustration, this article, and sample considers only the
case of a single application scanning through a populated queue of
messages. Enough information is presented to extrapolate the behavior of
multiple applications and/or empty queues.
MORE INFORMATION
Cursor Position Terminology
Following are definitions of some important terms used to describe cursor
position:
"Front" - This is a location of the first message in a populated queue,
or the location where the first message arrives in an empty
queue.
"Before the Front" - This is a location in the queue where no message
can reside. It is conceptually located before the
"front" of the queue. The Implied cursor points
here when it is created.
"End" - This is the location of the last message in a populated queue.
In an empty queue, or a queue with only one message, the "End of
the queue" and the "Front of the queue" are the same location.
"After the End" - This is an empty location where the next message
arrives. In an empty queue, "After the End" is the
same location as "Front" and "End".
What the ActiveX Cursors are and how they Behave
For each instance of queue opened through the ActiveX API, there are two
cursors created:
- A 'Null' cursor.
-and-
- An 'Implied' cursor.
NOTE: The ActiveX API does not allow the creation of any additional
cursors.
The 'Null' cursor always points to the front of a queue and does not
require initialization. This cursor is used by the Receive and Peek methods
to return the first message in the queue. Calling EnableNotification with
the MQMSG_FIRST argument enables notification on the NULL cursor. Note that
MQMSG_FIRST is the default value of EnableNotification's Cursor parameter.
So, calling EnableNotification with no cursor argument also enables
notification on the Null cursor. It is called 'Null' because the
representing variable always has the value NULL.
The 'Implied' cursor is initialized to point to a location before the front
of the queue. That is, it is not initialized to point at any message. To
initialize the Implied cursor to point to the front of the queue, call the
Reset method. This cursor is movable and used in the PeekCurrent, PeekNext
and ReceiveCurrent APIs. It never moves automatically but must be
programmatically moved. Calling EnableNotification with the MQMSG_CURRENT
and MQMSG_NEXT cursor argument enables notification on the implied cursor.
This cursor is called 'Implied' because you do not have to create it
explicitly.
When EnableNotification is called for the Implied cursor (using MQMSG_FIRST
or MQMSG_CURRENT), a notification event fires when the cursor points at a
message. When EnableNotification is called for the Null cursor (using
MQMSG_FIRST or no argument), a notification event fires when there is any
message in the queue.
There are other possible positions in a queue that do not contain messages.
In the case that the cursor points to one of these positions when
EnableNotification is called, no event fires:
- "Before the Front"
- An empty slot in the queue where a message previously existed.
- "After the End"
Effect of the API Calls on the Cursors
- Peek reads non-destructively from the front of the queue. There is no
effect on the position of Implied cursor, but a call to PeekCurrent is
required before it can be used.
- Receive reads destructively from the front of the queue. There is no
effect on the position of Implied cursor, but there is now possibly no
longer a message at the location referenced by the implied cursor if it
was pointing to the first message in the queue. As above, a call to
PeekCurrent is required before the implied cursor can be used again.
- PeekCurrent reads non-destructively from the location pointed to by the
implied cursor. The implied cursor is re-initialized if this call
succeeds a Peek or Receive.
- PeekNext explicitly advances the implied cursor and reads that location
non-destructively if a message is present.
- ReceiveCurrent reads destructively from the location pointed to by the
NULL implied cursor and implicitly advances the implied cursor to the
next location.
Appreciating the Potential Complexity of Cursor and Asynchronous
Notification
There are three components of handling asynchronous notification:
- Enabling asynchronous notification.
- Handling the notification event.
- Re-enabling notification.
You enable notification with the queue's EnableNotification method. There
are three possible action parameters: MQMSG_FIRST for the NULL cursor,
MQMSG_CURRENT, and MQMSG_NEXT for the implied cursor.
There are five possible methods for handling the notification event:
Receive and Peek are intended for use with the NULL cursor. ReceiveCurrent,
PeekCurrent and PeekNext are intended for use with the implied cursor.
You re-enabling notification using EnableNotification with three possible
action parameters.
Given the preceding information, there are 3 * 5 * 3 = 45 permutations to
implementing the three steps. Of these 45, only three (3) provide a
sensible, efficient use of the ActiveX mechanism.
The accompanying sample iterates through all 45 possibilities. A document
containing the output is provided for printing.
-----------------------
Programming Guidelines
-----------------------
There are three general uses for notification and cursors:
- Removing messages from the front of the queue as they arrive.
- Scanning through a queue, non-destructively peeking messages.
- Scanning through a queue, destructively reading messages.
There are two cursor\API families for implementing asynchronous
notification:
- Using the NULL cursor: Peek and Receive, EnableNotification with the
MQMSG_FIRST argument.
-or-
- Using the implied cursor: PeekCurrent, PeekNext and ReceiveCurrent,
EnableNotification with the MQMSG_CURRENT and MQMSG_NEXT arguments.
These API calls are made inside the Arrived() Event handler.
Although the complexity of the model permits otherwise, the recommended
approach is to use only the cursor and APIs from one family in the handling
of any one instance of an open queue.
Here are examples for each of the three general uses. Each assumes the
following declarations and initialization:
Sub Form_Load()
Dim qinfo As New MSMQQueueInfo
Dim q As MSMQQueue
Dim m As MSMQMessage
Dim WithEvents TheEvent As MSMQEvent
qinfo.PathName = "comptuer\queue"
Set q = qinfo.Open(MQ_RECEIVE_ACCESS, MQ_DENY_NONE)
End Sub
Case One
--------
Removing messages from the front of the queue as they arrive (Null cursor):
Sub Form_Click()
q.EnableNotification Event:=TheEvent, Cursor:=MQMSG_FIRST
End Sub
Private Sub TheEvent_Arrived(ByVal q As Object, ByVal Cursor As Long)
Set m = q.Receive
q.EnableNotification Event:=TheEvent, Cursor:=MQMSG_FIRST
End Sub
Case Two
--------
Scanning through a queue, non-destructively peeking messages (Implied
cursor):
Sub Form_Click()
Set m = q.PeekCurrent
q.EnableNotification Event:=TheEvent, Cursor:=MQMSG_CURRENT
End Sub
Private Sub TheEvent_Arrived(ByVal q As Object, ByVal Cursor As Long)
Set m = q.PeekCurrent
q.EnableNotification Event:=TheEvent, Cursor:=MQMSG_NEXT
End Sub
Case Three
----------
Scanning through a queue, destructively reading messages:
Sub Form_Click()
Set m = q.PeekCurrent
q.EnableNotification Event:=TheEvent, Cursor:=MQMSG_CURRENT
End Sub
Private Sub TheEvent_Arrived(ByVal q As Object, ByVal Cursor As Long)
Set m = q.ReceiveCurrent
q.EnableNotification Event:=TheEvent, Cursor:=MQMSG_CURRENT
End Sub
---------------------------
Common Pitfalls and Errors
---------------------------
Infinite Loop
-------------
Re-enabling notification without either explicitly or implicitly advancing
the cursor results in an infinite loop.
Error 0xC00E001C: MQ_ERROR_ILLEGAL_CURSOR_ACTION
------------------------------------------------
Error text: MQ_ACTION_PEEK_NEXT specified to MQReceiveMessage can not be
used with the current cursor position.
In the sample, error C00E001C occurs when the cursor is advanced past the
end of the queue (not at the last message) and EnableNotification is called
with an action parameter of MQMSG_NEXT. This results in an attempt to
further advancing the cursor from this position which raises the error.
Error 0xC00E001D: MQ_ERROR_MESSAGE_ALREADY_RECEIVED
---------------------------------------------------
Error text: A message that is currently pointed at by the cursor has been
removed from the queue by another process or by another call to
MQReceiveMessage without the use of this cursor.
In the sample, error C00E001D occurs after a message is received using the
Receive method. This method does not advance the cursor; therefore, the
cursor is not pointing at a message after the operation completes. The
error results when EnableNotification is called while the cursor is
pointing at the vacant message position.