ServicesResourcesConferencesOur TeamWeblogsAboutContact
   
The Missing Parts Of System.Messaging

I really love the managed code incarnation of the MSMQ API. Sending a message to a remote destination is as easy as the following:

String remoteQueueFormatName = @"DIRECT=OS:testsrv2\Private$\perf_server";
MessageQueue remoteQueue = new MessageQueue(@"FormatName:" + remoteQueueFormatName);
Message m = new Message();
m.Body = "Foo";
m.Label = "Test";
que.Send(m);

But what happens if the destination machine is not online? Well, all messages will be queued in a local "outgoing queue" which can be seen in Computer Management->Services and Applications -> Message Queueing->Outgoing Queues:

You can also purge outgoing messages if you need to do so:

If you'd try to achieve the same behavior by running the following code in your application, you'll receive a very different result:

String remoteQueueFormatName = @"DIRECT=OS:testsrv2\Private$\perf_server";
MessageQueue remoteQueue = new MessageQueue(@"FormatName:" + remoteQueueFormatName);
remoteQueue.Purge();

In this case, the remote computer will be contacted and the complete content of the remote queue will be purged! Not exactly what you tried to achieve ...

This used to be different. Whenever you opened a remote queue in the old-style MSMQ API (both, in the COM and the C-API), you had the possbility to specify whether you wanted to access the "real" remote queue on the remote machine, or whether you'd just like work with the matching outgoing queue which contains the messages which have not yet been delivered to the remote queue. This is not possible using System.Messaging.

Another missing piece in this API is related to the management of the disk-based queue files. Whenever you send a message with the flag Recoverable=true (or, in old-style API speak, with the delivery option MQMSG_DELIVERY_RECOVERABLE), it will be first stored to disk and will only be accepted for delivery afterwards. The client-side Send() call will in this case block until the file has been flushed to disk to make sure that the message survives an eventual computer crash or power-loss. If you on the other hand send a message using Recoverable=false (or MQMSG_DELIVERY_EXPRESS), the message will be transferred in memory, but might also end up on disk depending on the time it stays in the queue and depending on memory conditions. The message will however be lost as soon as you stop/start the MSMQ service or reboot the machine.

For this on-disk storage, MSMQ uses a number of files each of which is 4 MB in size. They are used to store one or more messages, depending on the message's sizes. (This is, by the way, also the reason for the original message size limit of 4 MB, as one message had to fit into one file.) These files are accessed as memory mapped files which are either flushed to disk immediately (if recoverable) or not (in express mode). You can find them in %SYSTEMROOT%\System32\msmq\storage (which is c:\windows\system32\msmq\storage on my machine).

If your application receives a queue's messages (or if you purge them), these files will not be deleted immediately, but MSMQ will only do so every once in a while (I guess I remember something like once every six hours, but this might have been changed.) Both the COM and C API allow you to tell MSMQ to TINY its message store to get rid of already deleted messages.

As this is not available in System.Messaging, I've created the following helpers to allow you access this additional functionality:

class MessagingHelpers
{
   [DllImport("mqrt.dll", CharSet=CharSet.Unicode, ExactSpelling=true, PreserveSig = false)]
   static extern void MQMgmtAction(string machineName,  string objectName, string action);

   [DllImport("mqrt.dll", CharSet=CharSet.Unicode, ExactSpelling=true, PreserveSig = false)]
   static extern void MQOpenQueue(String formatName, 
               MQAccess access, MQShareMode sharemode, ref int queueHandle);

   [DllImport("mqrt.dll", ExactSpelling=true, PreserveSig = false)]
   static extern void MQCloseQueue(long queueHandle);

   [DllImport("mqrt.dll", ExactSpelling=true, PreserveSig = false)]
   static extern void MQPurgeQueue(long queueHandle);

   [Flags]
   enum MQAccess: uint
   {
      MQ_RECEIVE_ACCESS = 0x00000001,
      MQ_SEND_ACCESS = 0x00000002,
      MQ_PEEK_ACCESS = 0x00000020,
      MQ_ADMIN_ACCESS =0x00000080
   }

   enum MQShareMode: uint
   {
      MQ_DENY_NONE = 0x00000000,
      MQ_DENY_RECEIVE_SHARE = 0x00000001
   }

   public static void PurgeOutgoingQueueForRemoteQueue(String formatname)
   {
      int queueHandle=0;
      MQOpenQueue(formatname, 
                MQAccess.MQ_ADMIN_ACCESS | MQAccess.MQ_RECEIVE_ACCESS, 
                MQShareMode.MQ_DENY_NONE, 
                ref queueHandle);
      MQPurgeQueue(queueHandle);
      MQCloseQueue(queueHandle);
   }

   public static void TidyLocalStorage()
   {
      MQMgmtAction(null, "MACHINE", "TIDY");
   }
}

(The secret here lies in MQ_ADMIN_ACCESS which tells MSMQ that you'd like to work with the "outgoing queue" for the specified remote queue.)

Usage sample:

class ClientApp
{
   static void Main(string[] args)
   {
      String remoteQueueFormatName = @"DIRECT=OS:testsrv2\Private$\perf_server";
      MessagingHelpers.PurgeOutgoingQueueForRemoteQueue(remoteQueueFormatName);
      MessagingHelpers.TidyLocalStorage();
   }
}

Please note that this functionality of the C API is by default only available if you are running on Windows Server 2003 or Windows XP. You can download an add-on, the "MSMQ Local Admin API" from Microsoft to provide similar functionality for Windows 2000 and NT 4 if you create applications with the C API.

Update: Interestingly enough, one of my first contacts with MSMQ was just about this very functionality (to access the outbound queues' states) in NT 4.0 back in 1998. Short answer: it wasn't possible at that time. I'll add a second weblog post later today to answer my own 1998's question with .NET.

posted on Saturday, June 12, 2004 1:47 PM

# re: The Missing Parts Of System.Messaging @ Wednesday, September 05, 2007 5:46 PM

Hello,<br><br>

I got the following error when I ran the code<br><br>

"Error: Exception from HRESULT: 0xC00E001E"
<br><br>

I'm using vis studio 2005.

<br><br>

Thanks in advance
Chris


Powered by Community Server, by Telligent Systems