Receiving a Message

One of the things that strikes you as a trifle odd when you run this application is that you don’t actually need two copies of it to try it out. You can simply use the same instance of the application to receive the message that you have just sent out. This is because, whilst queues do have a physical location, they can — security considerations aside — be read to and written from anywhere within the enterprise. You’re not sending the message to a computer — you’re sending it to a queue. In fact, as we’ll see in the next example, it can be quite useful to have a queue that lots of different clients can read from.

So how do we get a list of the titles of all the outstanding messages on a queue? Here’s how:

void CSimpleMailDlg::OnRefresh()
{
   // Open up the incoming queue
   IMSMQQueuePtr pQueue = OpenQueue(IDC_INCOMING, MQ_PEEK_ACCESS);
   if (pQueue == NULL)
      return;

   // Peek at the current (i.e. first) message
   _variant_t vTimeout(100L);
   _variant_t vFalse(false);
   IMSMQMessagePtr pMessage;
   HRESULT hr = pQueue->PeekCurrent(&vFalse, &vFalse, &vTimeout, &pMessage);
   if (FAILED(hr))
   {
      DisplayComError(_T("Failed to peek current"), hr);
      return;
   }

   CListBox* pMessages = ListPtr(IDC_MESSAGES);
   pMessages->ResetContent();

   // Get all the rest of the messages, outputting titles
   while (pMessage)
   {
      BSTR bstrTitle;
      pMessage->get_Label(&bstrTitle);
      CString csTitle = bstrTitle;
      SysFreeString(bstrTitle);
      pMessages->AddString(csTitle);

      pQueue->PeekNext(&vFalse, &vFalse, &vTimeout, &pMessage);
      if (FAILED(hr))
      {
         DisplayComError(_T("Failed to peek next"), hr);
         return;
      }
   }
}

What we’re doing here is enumerating through the messages in the queue. We can optimize the peeking operation by specifying that we don’t want the body of the message or the destination queue information set up — that’s the reason for those two vFalse parameters. If you look at the no-COM version, you’ll see that there is a whole load more flexibility about what information is actually extracted during a peek. However, in either case, the nature of the peek operation remains the same: nothing is actually removed from the queue.

There’s one other point worth mentioning — the vTimeout parameter. Remember that we’re dealing with a loosely-connected system, and that we might not be able to see the queue at all.

Having set up the list of messages in the queue, actually reading one is child’s play. In this implementation, I’ve elected to enumerate all the messages in the queue until I come across one with the same title. I guess you could make this a little more sophisticated, but it’s sufficient for our purposes:

void CSimpleMailDlg::OnDblclkMessages()
{
   // Get title of message to find
   CString csMessage;
   CListBox* pMessages = ListPtr(IDC_MESSAGES);
   int index = pMessages->GetCurSel();
   pMessages->GetText(index, csMessage);
   // Open incoming queue
   IMSMQQueuePtr pQueue = OpenQueue(IDC_INCOMING, MQ_RECEIVE_ACCESS);
   if (pQueue == NULL)
      return;

   _variant_t vTimeout(100L);
   _variant_t vFalse(false);
   _variant_t vTrue(true);

   // Peek at current message
   IMSMQMessagePtr pMessage;
   HRESULT hr = pQueue->PeekCurrent(&vFalse, &vFalse, &vTimeout, &pMessage);
   if (FAILED(hr))
   {
      DisplayComError(_T("Failed to peek current"), hr);
      return;
   }

   // Go through messages, finding specified title
   while (pMessage)
   {
      BSTR bstrTitle;
      pMessage->get_Label(&bstrTitle);
      CString csTitle = bstrTitle;
      SysFreeString(bstrTitle);

      if (csTitle.Compare(csMessage) == 0)
      {
         // We've found it - take it off the queue
         hr = pQueue->ReceiveCurrent(&vFalse, &vFalse, &vTrue,
                                     &vTimeout, &pMessage);
         if (FAILED(hr))
         {
            DisplayComError(_T("Failed to receive current"), hr);
            return;
         }

         // Extract message status
         long msgClass;
         pMessage->get_Class(&msgClass);

         _variant_t vText;
         CString csText;

         switch (msgClass)
         {
            case MQMSG_CLASS_NORMAL:
               pMessage->get_Body(&vText);
               csText = vText.bstrVal;
               AfxMessageBox(csText);
               break;

            case MQMSG_CLASS_ACK_REACH_QUEUE:
               AfxMessageBox("Message has reached destination queue");
               break;
            case MQMSG_CLASS_ACK_RECEIVE:
               AfxMessageBox("Message has been received");
               break;

         case MQMSG_CLASS_NACK_REACH_QUEUE_TIMEOUT:
         AfxMessageBox("Message failed to reach destination queue in time");
               break;

            case MQMSG_CLASS_NACK_RECEIVE_TIMEOUT:
               AfxMessageBox("Message failed to be received in time");
               break;

            default:
               CString csError;
               csError.Format(_T("Message failed with error code %d"),
                              msgClass);
               AfxMessageBox(csError, msgClass);
               break;
         }
         OnRefresh();
         return;
      }

      pQueue->PeekNext(&vFalse, &vFalse, &vTimeout, &pMessage);
      if (FAILED(hr))
      {
         DisplayComError(_T("Failed to peek next"), hr);
         return;
      }
   }
}

The critical method call here is that one to MSMQQueue's ReceiveCurrent(). This is similar to the call to PeekCurrent(), except that here, I’m actually asking for the body of the message. The other crucial difference, of course, is that once this method is invoked, the message is removed from the queue. Note the use of the Class property to determine whether the message being received is a standard one or an administration message telling us what happened to one that we sent previously.

Before we leave this example behind, here, as promised, is a fraction of this example re-implemented using the MSMQ API instead of the component set. Here’s the function that opens up a queue:

QUEUEHANDLE CNocomMailDlg::OpenQueue(int nID, int mode)
{
   CString csError;
   USES_CONVERSION;

   CString csQueue;
   GetDlgItemText(nID, csQueue);

   WCHAR wszFormat[256];
   ULONG nChar = 256;

   MQPathNameToFormatName(T2W(csQueue), wszFormat, &nChar);
   QUEUEHANDLE hQueue;
   HRESULT hr = MQOpenQueue(wszFormat, mode, MQ_DENY_NONE, &hQueue);
   if (FAILED(hr))
   {
      csError.Format("Failed to open message queue, %x", hr);
      AfxMessageBox(csError);
      return NULL;
   }

   return hQueue;
}

The main difference is that in the straight API world, we’re using handles to represent objects rather than interface pointers. There are also one or two other quirks; for example, we can’t use the queue’s pathname directly — we have to convert it to a format name first.

So that’s our e-mail system. But what else can we do with MSMQ? Quite a lot as it turns out.

© 1998 by Wrox Press. All rights reserved.