.NET includes a managed MSMQ API (System.Messaging) to send and receive messages, but unfortunately there's a key functionality missing: obtaining the number of messages currently in the queue. Yoel Arnon talks about this problem with more detail here and here. After reading that, we learn that there are three ways to overcome this limitation: using the MSMQ performance counters (with the .NET Performance Counter API or via WMI), using the MSMQ COM wrapper, or using the MSMQ Admin API directly. The first option is not very reliable and the second one is not available on all versions of MSMQ, so the third one is the preferable way. It's also the most complex. Jared Evans has explained how to do it using a C++ Managed wrapper here, but that approach adds some complexity to the deployment of your application. It forces you to redistribute the Microsoft Visual C++ Redistributable Package, and if you want to support the x64 versions of Windows, you have to build the C++ wrapper for both x86 and x64 and have additional logic in the application installer to know which version of your assembly to install (and the same for the Visual C++ Redistributable which also has two versions). Some months ago, I upgraded my home PC to Windows Vista x64, so at the time I decided to develop a C# only solution for this, as there wasn't any publicly available on the net.

The first approach I tried was to look at the mq.h file and translate all the required structures to C#. This proved to be a nightmare, as at the time the P/Invoke Interop Assistant hadn't been created yet and the structures proved to be a little complex. So instead of trying to translate the whole structures correctly, I turned on the debugger and started examining the fields that were really needed, defining dummy spacer fields for the rest. This took a while, but it was worth it. Here's the code:

using System;
using System.Messaging;
using System.Runtime.InteropServices;

static class MessageQueueExtensions {
 
    [DllImport("mqrt.dll")]
    private unsafe static extern int MQMgmtGetInfo(char* computerName, char* objectName, MQMGMTPROPS* mgmtProps);
 
    private const byte VT_NULL = 1;
    private const byte VT_UI4 = 19;
    private const int PROPID_MGMT_QUEUE_MESSAGE_COUNT = 7;
 
    //size must be 16
    [StructLayout(LayoutKind.Sequential)]
    private struct MQPROPVariant {
        public byte vt;       //0
        public byte spacer;   //1
        public short spacer2; //2
        public int spacer3;   //4
        public uint ulVal;    //8
        public int spacer4;   //12
    }
 
    //size must be 16 in x86 and 28 in x64
    [StructLayout(LayoutKind.Sequential)]
    private unsafe struct MQMGMTPROPS {
        public uint cProp;
        public int* aPropID;
        public MQPROPVariant* aPropVar;
        public int* status;
    }   
    
    public static uint GetCount(this MessageQueue queue) {
        return GetCount(queue.Path);
    }
 
    private static unsafe uint GetCount(string path) {
        if (!MessageQueue.Exists(path)) {
            return 0;
        }
 
        MQMGMTPROPS props = new MQMGMTPROPS();
        props.cProp = 1;
 
        int aPropId = PROPID_MGMT_QUEUE_MESSAGE_COUNT;
        props.aPropID = &aPropId;
 
        MQPROPVariant aPropVar = new MQPROPVariant();
        aPropVar.vt = VT_NULL;
        props.aPropVar = &aPropVar;
 
        int status = 0;
        props.status = &status;
 
        IntPtr objectName = Marshal.StringToBSTR("queue=Direct=OS:" + path);
        try {
            int result = MQMgmtGetInfo(null, (char*)objectName, &props);
            if (result != 0 || *props.status != 0 || props.aPropVar->vt != VT_UI4) {
                return 0;
            } else {
                return props.aPropVar->ulVal;
            }
        } finally {
            Marshal.FreeBSTR(objectName);
        }
    }
}

Note that you'll have to go to the project properties and check the "Allow unsafe code" option for this to build.

Here's an example usage:

public static void Main() {
 
    string queueName = @".\Private$\MyQueue";
 
    if (MessageQueue.Exists(queueName)) {
        MessageQueue.Delete(queueName);
    }
 
    MessageQueue queue = MessageQueue.Create(queueName);
    Console.WriteLine("Count should be 0: " + queue.GetCount());
 
    queue.Send("ping", "ping");
    Console.WriteLine("Count should be 1: " + queue.GetCount());
 
    queue.Send("ping2", "ping2");
    Console.WriteLine("Count should be 2: " + queue.GetCount());
 
    queue.Receive();
    Console.WriteLine("Count should be 1: " + queue.GetCount());
 
    queue.Send("ping3", "ping3");
    Console.WriteLine("Count should be 2: " + queue.GetCount());
 
    MessageQueue.Delete(queueName);
    Console.WriteLine("Count should be 0: " + queue.GetCount());
}

After building and running, it should give you the following output:

Count should be 0: 0
Count should be 1: 1
Count should be 2: 2
Count should be 1: 1
Count should be 2: 2
Count should be 0: 0

Last week my colleague Rui Eugénio at OutSystems was doing a merge that involved similar code and commented to me "Couldn't this be done without using unsafe code?". That left me wondering, so I decided to give it a try. It turns out it wasn't that hard. Here's the safe version of the previous code:

using System;
using System.Messaging;
using System.Runtime.InteropServices;
 
public static class MessageQueueExtensions {
 
    [DllImport("mqrt.dll")]
    private static extern int MQMgmtGetInfo([MarshalAs(UnmanagedType.BStr)]string computerName, [MarshalAs(UnmanagedType.BStr)]string objectName, ref MQMGMTPROPS mgmtProps);
 
    private const byte VT_NULL = 1;
    private const byte VT_UI4 = 19;
    private const int PROPID_MGMT_QUEUE_MESSAGE_COUNT = 7;
 
    //size must be 16
    [StructLayout(LayoutKind.Sequential)]
    private struct MQPROPVariant {
        public byte vt;       //0
        public byte spacer;   //1
        public short spacer2; //2
        public int spacer3;   //4
        public uint ulVal;    //8
        public int spacer4;   //12
    }
 
    //size must be 16 in x86 and 28 in x64
    [StructLayout(LayoutKind.Sequential)]
    private struct MQMGMTPROPS {
        public uint cProp;
        public IntPtr aPropID;
        public IntPtr aPropVar;
        public IntPtr status;
    }
 
    public static uint GetCount(this MessageQueue queue) {
        return GetCount(queue.Path);
    }
 
    private static uint GetCount(string path) {
        if (!MessageQueue.Exists(path)) {
            return 0;
        }
 
        MQMGMTPROPS props = new MQMGMTPROPS { cProp = 1 };
        try {
            props.aPropID = Marshal.AllocHGlobal(sizeof(int));
            Marshal.WriteInt32(props.aPropID, PROPID_MGMT_QUEUE_MESSAGE_COUNT);
 
            props.aPropVar = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(MQPROPVariant)));
            Marshal.StructureToPtr(new MQPROPVariant { vt = VT_NULL }, props.aPropVar, false);
 
            props.status = Marshal.AllocHGlobal(sizeof(int));
            Marshal.WriteInt32(props.status, 0);
 
            int result = MQMgmtGetInfo(null, "queue=Direct=OS:" + path, ref props);
            if (result != 0 || Marshal.ReadInt32(props.status) != 0) {
                return 0;
            }
 
            MQPROPVariant propVar = (MQPROPVariant)Marshal.PtrToStructure(props.aPropVar, typeof(MQPROPVariant));
            if (propVar.vt != VT_UI4) {
                return 0;
            } else {
                return propVar.ulVal;
            }
        } finally {
            Marshal.FreeHGlobal(props.aPropID);
            Marshal.FreeHGlobal(props.aPropVar);
            Marshal.FreeHGlobal(props.status);
        }
    }
}
 
public class MessageQueueExtensionsTest {
 
    public static void Main() {
 
        string queueName = @".\Private$\MyQueue";
 
        if (MessageQueue.Exists(queueName)) {
            MessageQueue.Delete(queueName);
        }
 
        MessageQueue queue = MessageQueue.Create(queueName);
        Console.WriteLine("Count should be 0: " + queue.GetCount());
 
        queue.Send("ping", "ping");
        Console.WriteLine("Count should be 1: " + queue.GetCount());
 
        queue.Send("ping2", "ping2");
        Console.WriteLine("Count should be 2: " + queue.GetCount());
 
        queue.Receive();
        Console.WriteLine("Count should be 1: " + queue.GetCount());
 
        queue.Send("ping3", "ping3");
        Console.WriteLine("Count should be 2: " + queue.GetCount());
 
        MessageQueue.Delete(queueName);
        Console.WriteLine("Count should be 0: " + queue.GetCount());
    }
}

Technorati Tags: ,
posted on Wednesday, August 27, 2008 11:29 AM | Filed Under [ .NET ]

Comments

Gravatar
# re: Counting the number of messages in a Message Queue in .NET
Posted by Jared Evans
on 8/28/2008 12:38 AM
This is great news... good find! I still think it's a bit silly that this function isn't already included in MSMQ.
Gravatar
# re: Counting the number of messages in a Message Queue in .NET
Posted by Michel Zehnder
on 10/19/2008 11:51 AM
Pretty cool, thanks!

If you remove the check that the MSMQ exists, then it even works remotely... ;)

Thanks again!

Cheers
Michel
Gravatar
# re: Counting the number of messages in a Message Queue in .NET
Posted by Puneet Shadija
on 11/19/2008 10:37 AM
I tried using this class, but it always returns zero for me :( ...

Is there something that I am doing wrong !!!

Private Sub MessageQueue1_ReceiveCompleted(ByVal sender As System.Object, ByVal e As System.Messaging.ReceiveCompletedEventArgs) Handles MessageQueue1.ReceiveCompleted
Dim res As IAsyncResult = e.AsyncResult
Dim myObject As myClass = CType(res.AsyncState, myClass)
MessageQueue1.BeginReceive()
Dim cnt As UInt16 = GetCount(MessageQueue1)
End Sub
Gravatar
# re: Counting the number of messages in a Message Queue in .NET
Posted by Andy
on 11/21/2008 2:59 AM
Hi ,

Could you pls help me to get outgoing queues messagges using that API. Pls help me. I urgently needs
Gravatar
# re: Counting the number of messages in a Message Queue in .NET
Posted by Puneet Shadija
on 11/24/2008 7:33 AM
I referred to one of your posting on www.eggheadcafe.com/.../post27084497.asp, where you have stated: "As for resolving your issue, you can assume that if oMgmt.Init failed, the
queue is empty (use On Error)."

So, I tried, adding an adding an Item to the Queue first and then calling the Init() function for MSMQManagement type object. It doesn't throw any Generic code error but upon making call to the function MSMQManagement.MessageCount() still returns 0 always. Plz help :(
Gravatar
# re: Counting the number of messages in a Message Queue in .NET
Posted by Uday Karuppiah
on 9/21/2009 9:27 PM
I am trying to use this to access msg count from queues on a remote machine. I am running under a ID that is a under the administrator group on the remote machine but the queue count is always coming is zero.

Can't we use this to access msg counts from queus on remote machines?

Please advise.

Thanks
Uday
Gravatar
# re: Counting the number of messages in a Message Queue in .NET
Posted by Justin Caton
on 12/1/2009 3:24 AM
Works great! I figured out how to count queues on a different server, you have to pass in the server name instead of null on the following line of code.

int result = MQMgmtGetInfo(null, "queue=Direct=OS:" + path, ref props);
Gravatar
# re: Counting the number of messages in a Message Queue in .NET
Posted by DM
on 2/12/2010 12:17 AM
Thanks, that helped a lot!
Post Comment
Title *
Name *
Email
Url
Comment *  
Please add 2 and 6 and type the answer here: