INFO: Using Cursors and ActiveX Asynchronous Notification (196574)



The information in this article applies to:

  • Microsoft Message Queue Server (MSMQ) 1.0

This article was previously published under Q196574

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:
  1. Enabling asynchronous notification.
  2. Handling the notification event.
  3. 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.
				

REFERENCES

Microsoft Message Queue Software Development Kit (SDK) Help.

Modification Type:MajorLast Reviewed:1/14/2000
Keywords:kbinfo kbprogramming KB196574