By taking advantage of the functionality of two Microsoft Message Queuing libraries, you can minimize the time it takes to create MSMQ objects during testing or deployment of your messaging application.
Consider the following scenario: you are trying to decide on a courier service for shipping your products. There are several couriers to choose from and all need to be evaluated before you will make your decision. As part of the evaluation, you will drop a package off at a designated drop box, at which point it will be picked up and delivered. The catch is, since you are evaluating the service and not actually paying for it, it is required that you personally pickup the drop box from each of the courier’s corporate office, install the drop box and remove it when finished. This sounds crazy, but it is exactly what you end up doing when testing or deploying applications using Microsoft Message Queuing (MSMQ).
Before applications can take advantage of the features in Microsoft Message Queuing, MSMQ objects need to be manually created. These objects, queues, triggers and rules are typically created using the tools provided by Microsoft, such as the MSMQ Explorer, which are part of the Message Queuing installation. However, this can become cumbersome if these objects need to repeatedly created and destroyed during testing of your application. More importantly, leaving this task up to your users during deployment can often lead to unexpected results.
In this article, I will show how to automatically create MSMQ objects using the Microsoft Message Queue 1.0 Object and the MSMQTriggerObjects 1.0 Type libraries. I will start by giving a brief overview of MSMQ objects and how they are traditionally created. Then I will discuss a technique to automatically create these objects using the two Microsoft libraries. For more detailed information on MSMQ and integration into Visual Basic applications, consider reading Ted Pattison’s article in the May 1999 issue of MSJ, Using Visual Basic to Integrate MSMQ into Your Distributed Applications.
Sending information using MSMQ is very similar to sending mail using the post office. When you send a letter, you address it with specific information and place it in a mailbox. The post office will guarantee the pickup, routing, processing and delivery of your letter. Once the letter arrives at its destination mailbox, the recipient will read it. The same holds true for MSMQ.
Instead of mailboxes, MSMQ uses Queues as pickup and delivery locations. Messages take the place of letters, which contain the information to be delivered. To add life to your queues, you can implement Triggers and Rules . When a message arrives in a queue, it’s associated trigger will fire if a predefined set of conditions are satisfied. Rules, which are bound to triggers, either invoke methods in COM objects or run standalone executables when they are activated. When this is done, parameters can be passed to the COM component or executable to provide information about the associated trigger or queue.
Installing MSMQ is very simple. If you are running under Windows NT 4.0, MSMQ is installed as a selection of the NT 4.0 Option Pack installation. Under Windows 2000, you can install MSMQ (displayed as Message Queuing Services) as an additional component during the operating system setup. One thing to keep in mind is that Message Queuing Services is not selected by default as a Windows 2000 component. If you did not select Message Queuing Services when you installed Windows 2000, you will need to do so by click on Add/Remove Windows Components under the Add/Remove Programs in the Windows Control Panel.
Unlike queues, which are inherent to MSMQ, triggers and rules are not automatically available when MSMQ is installed. You can add this functionality by installing the MSMQ Triggers Service. The MSMQ Triggers Service installation and documentation may be obtained from the Microsoft Message Queuing website. After installing, verify that the Triggers Service has started by checking for the trigserv.exe process in the Windows Task Manager.
In order to understand the relationship MSMQ objects have with one another, think of queues as objects, triggers as events and rules as methods. When a message arrives in a queue that has an associated trigger, the trigger will fire. If there is a rule bound to the trigger, that rule will be activated. To illustrate, consider the following code for a textbox named Text1:
Private Sub Text1_Change()
Text1.Text = UCase(Text1.Text)
When the text property changes, the Change event fires and the UCase method is run. In this example, a queue can be thought of as the Text1 object, a trigger as the Text1_Change event and a rule as the UCase method.
This example is further illustrated in Figure 1. Client application A sends a message to a queue named MyQueue1 where the trigger named trgMyQueue1 fires. This trigger has a rule bound to it named rulProcessMsg1, which invokes a COM component called MyComponent. A method in the component performs some message processing and passes the message to a second queue called MyQueue2. A second trigger fires and causes a rule to invoke the COM component once again. The end result is that the converted message is passed to receiving Client Application B.
MSMQ queues, triggers and rules are typically created using the tools made available as a result of installing the MSMQ services. If you are running under Windows NT 4.0, you will use the MSMQ Explorer. For those of you using Windows 2000, Message Queuing appears under the MMC Computer Management snap-in Services and Applications node. The Explorer-like interface of both tools makes it easy to add new queues, triggers and rules. Normally you would begin by creating your queues. These can be either public or private in scope. If your computer is connected to a workgroup, you will be restricted to private queues. Rules are created next and require information about the COM component or executable that will be launched when the rule is activated. Triggers are created last since they logically are positioned between the queues and the rules. When the trigger is created, it is associated with exactly one queue. One or more rules are then bound to the trigger.
Setting up your messaging environment is a snap if you only need to perform these tasks once. However, if you are testing a design and are continually rebuilding MSMQ objects or are delegating this task to a user during installation of your software, this may seem a bit overwhelming, not to mention time-consuming.
AUTOMATIC MSMQ OBJECT CREATION
Let’s take a different approach to MSMQ object creation by looking at two Microsoft Message Queuing libraries. The Message Queue 1.0 Object Library is responsible for queue creation. Triggers and rules are created using the MSMQTriggerObjects 1.0 Type Library. Remember, until you install the Trigger Service, the MSMQTriggerObjects Type library will not be available. For those of you running on Windows 2000, keep in mind that both versions 1.0 and 2.0 of the Message Queue Object Library are installed. The earlier version is included for backward-compatibility with applications written on Windows NT 4.0 platforms. For purposes of this discussion, we will limit the focus to the 1.0 version of the Message Queue Object Library.
To keep things simple, let’s create two queues, a trigger for each of the queues and one rule. The sample code included with this article contains a file called queuelist.ini. This file holds all of the information about the MSMQ objects we intend to create. The ini file consists of two sections, Queues and Rules. Beneath each section are keys, which identify properties of either a queue or a rule. Below is a listing of the queuelist.ini file:
Description=MSMQ Trigger Rule - Process Message
The first thing you will notice is that there is no section for triggers. Remember that for every queue, there can be only one trigger, so we can simply use the list of queues to create our triggers. The layout of the Queues section is fairly straightforward. For each queue that will be created, there is a key and the name of the queue. Each key begins with an index and is incremented for every succeeding queue.
The Rules section on the other hand, requires a bit more information. The first two items under the Rules section, Name and Description, identify the name and a brief description of the rule to be created. The Condition property lists the conditions under which the rule will fire. To enter multiple conditions, simply separate them by commas. In our example, we have only one condition that needs to be satisfied in order for the rule to fire:
If you were creating this rule using the MSMQ Explorer, the above statement would represent the selection Message Priority Equals in the Rules Properties dialog.
The table below contains a listing of rule conditional statements, as they would appear in MSMQ Explorer and the equivalent format to be used within the queuelist.ini file.
|Message Label Contains||$MSG_LABEL_CONTAINS|
|Message Label Does Not Contain||$MSG_LABEL_DOES_NOT_CONTAIN|
|Message Body Contains||$MSG_BODY_CONTAINS|
|Message Body Does Not Contain||$MSG_BODY_DOES_NOT_CONTAIN|
|Message Priority Equals||$MSG_PRIORITY_EQUALS|
|Message Priority Not Equal||$MSG_PRIORITY_NOT_EQUAL|
|Message Priority Greater Than||$MSG_PRIORITY_GREATER_THAN|
|Message Priority Less Than||$MSG_PRIORITY_LESS_THAN|
|Message AppSpecific Equals||$MSG_APPSPECIFIC_EQUALS|
|Message AppSpecific Not Equal||$MSG_APPSPECIFIC_NOT_EQUAL|
|Message AppSpecific Greater Than||$MSG_APPSPECIFIC_GREATER_THAN|
|Message AppSpecific Less Than||$MSG_APPSPECIFIC_LESS_THAN|
|Message Source Machine ID Equals||$MSG_SRCMACHINEID_EQUALS|
|Message Source Machine ID Not Equal||$MSG_SRCMACHINEID_NOT_EQUAL|
Finally, if desired, parameters can be passed to the method of our COM component by setting the value of the Parameters property. Table 2 contains a listing of Rule parameter statements and the equivalent format to be used within the queuelist.ini file. In our example, we are passing the Message Queue Pathname to the ProcessMessage method of the MessageProcessor.CProcessMessage component as:
Use commas to separate multiple parameter values just as was done with rule condition values. Keep in mind however, the order in which you pass the parameters is the order they will be received by the method of your COM component. It is also important to pay attention to the data type of the parameter being passed. For example, two parameters can be used to return message body data, $MSG_BODY and $MSG_BODY_AS_STRING. The first returns the message body information as a variant and the second as a string. As subtle as this is, it is an important point to keep in mind.
|Message Body as Variant||$MSG_BODY||Variant|
|Message Body as String||$MSG_BODY_AS_STRING||String|
|Message Correlation ID||$MSG_CORRELATION_ID||Variant|
|Queue Path Name||$MSG_QUEUE_PATHNAME||String|
|Queue Format Name||$MSG_QUEUE_FORMATNAME||String|
|Response Queue Format Name||$MSG_RESPONSE_QUEUE_FORMATNAME||String|
|Admin Queue Format Name||$MSG_ADMIN_QUEUE_FORMATNAME||String|
|Message App Specific Number||$MSG_APPSPECIFIC||Integer|
|Message Sent Time||$MSG_SENTTIME||Variant (date)|
|Message Arrived Time||$MSG_ARRIVEDTIME||Variant (date)|
|Message Source Machine||$MSG_SRCMACHINEID||String|
PUTTING IT ALL TOGETHER
The sample code provided with this article will create and remove MSMQ objects defined in the queuelist.ini file. Queues are created first, followed by our rule and the triggers. Finally the rule is bound to the triggers to tie everything together. Begin by creating an instance of the MSMQ.MSMQQueueInfo class. All we need to do to create the queue is set pathname property and call the Create method of the MSMQQueueInfo object:
oQueueInfo.PathName = ".Private$MyQueue1"
On Error Goto Next
On Error Goto 0
The Pathname property is actually a composite of three items, the name of the computer on which the queue is to be created, the scope and the name of the queue. The local computer name is abbreviated by using the period character. If a queue is being referenced on a computer other than the local machine, the computer name needs to be explicitly referenced. For example, assuming that the name of the local computer is named MyServer, we could have easily created the queue follows:
oQueueInfo.PathName = "MyServerPrivate$MyQueue1"
Queues may either be public or private in scope. The queue we are creating is a private queue. If we were creating a public queue, we would have eliminated “Private$” from the Pathname. Finally, note that error handling is disabled when creating the queue to prevent an error if the queue already exists.
Rules are created by setting an object reference to the MSMQRuleSet class of the MSMQTriggerObjects Type library. Start by initializing the RuleSet object with the name of the computer where the rule will be created:
The rule is then created by calling the Add method of the RuleSet object. Each rule is assigned a unique ID when it is created. This ID, which is returned as a parameter of the Add method, is used when attaching a rule to a trigger.
With udtRuleDefRule.Add .Name, .Description, .Condition, .Action, _
.ImplementationID, .ShowWindow, .RuleID
In order to simplify things, I have created a user-defined-type to hold all of the rule properties. A procedure called LoadRuleProperties is responsible for formatting and populating the UDT properties. Most of the properties are simply read directly from the values in the ini file. The Action parameter of the UDT is unique since it consists of the Mode, ProgramID, Method and Parameter ini values. Special formatting is neccessary to prepare these values for the MSMQRuleSet.Add method. Study this routine and you will have a good idea how the RuleSet Add method parameters are formatted.
It is a good idea to check if a rule already exists before attempting to add it. As indicated earlier, rules may be identified by their unique ID. If you create two rules with the same name, you will get just that, two distinct rules with the same name. Since it is easier to remember the rule by name rather than its ID, check if the rule exists using its name. I have created a routine called IsRuleNameUnique to perform this task for you.
Finally, we can create the triggers. Creating triggers is similar to creating rules. An object reference is set to the MSMQTriggerSet class, properties are initialized and the trigger is added. However, there is an additional step required when creating a trigger – attaching a rule. Existing rules may be attached to a trigger by calling the AttachRule method of the TriggerSet object.
With udtTrigDefTrigger.AddTrigger .Name, .Queue, .SystemQueue, _
.Enabled, .Serialized, .TriggerID
oTrigger.AttachRule .TriggerID, RuleID, 0
Again, a UDT is used to help manage the initialization of the trigger properties. Just as when the rule was created, an ID is returned when the trigger is created. This trigger ID uniquely identifies the trigger and is used in conjunction with the rule ID to attach a rule to the trigger. The last parameter of the AttachRule method indicates the priority of the rule. Think of this as the position in a list. A priority of zero indicates the top-most rule in the list and would be activated first when the trigger is fired.
The sample code for this article will create a pair of queues and triggers and one rule. Load and run the project in the Visual Basic IDE to see how easy it is to create MSMQ objects using the techniques outlined in this article. Click on the Create Queues button on the sample code form to create the MSMQ objects. After doing so, open MSMQ Explorer to view them. You can easily delete the objects by clicking the Delete Queues button.
Keep in mind that Microsoft Message Queuing and the MSMQ Triggers Service must be installed and running on your system in order for the code to function properly. Remember, you will get the same results if you create your MSMQ objects manually using the MSMQ Explorer. However, by taking advantage of functionality that is inherent to MSMQ, you can greatly improve the speed and reliability at which you create your MSMQ objects.