Using MSMQ with Microsoft Visual FoxPro 6.0
Summary: This article guides Visual FoxPro® developers through successfully writing Visual FoxPro code to access the Microsoft® Messaging Queue (MSMQ) COM objects directly. Many tips and tricks specific to Visual FoxPro are scattered throughout the code samples. (37 printed pages)
|Click to copy the MSMQwVFP6 sample file.|
Microsoft® Messaging Queue (MSMQ) is an exciting technology that every Visual FoxPro® developer can employ in his or her application. This document will guide you through successfully writing Visual FoxPro code to access the MSMQ COM objects directly. Many tips and tricks specific to Visual FoxPro are scattered throughout the code samples. Many of the code samples contained in this document are included with the Visual FoxPro MSMQ web pack.
The first part of this document describes general MSMQ concepts that you need to know. If you are already familiar with MSMQ, you can go directly to the programming sections.
In preparation for reading this article, you may want to first install MSMQ and read through the documentation. The MSMQ SDK documentation is more detailed and recommended for developers. This article repeats sections from topics in the MSMQ documentation. If you have installed Microsoft Windows® 2000, the Component Services documentation contains expanded detail on messaging services:
There are differences between MSMQ 1.0 and 2.0 features not documented here, which appear in the Windows 2000 docs.
MSMQ 1.0 is included as part of the Windows NT4 Options Pack, which is a free download from the Microsoft MSMQ Web site:
MSMQ 1.0 is not installed by default, but rather as an option available in the Custom setup. MSMQ 2.0 will be incorporated directly with future versions of Windows. This document describes MSMQ in a version-independent manner, however, differences between versions, will be noted where applicable.
Throughout this document, you will see references to e-mail. E-mail and Messaging are often compared and confused. In fact, many people often distinguish between the two with following analogy: E-mail is to People as Messaging is to Applications.
All About MSMQ
With the trend toward distributed computing in enterprise environments, it is important to have flexible and reliable communication among applications. Businesses often require independent applications running on different systems to communicate with each other and to exchange messages even though the applications may not be running at the same time.
MSMQ is a “fast store-and-forward” service for Windows NT Server Enterprise Edition (Windows NT Server/E), that enables applications running at different times to communicate across heterogeneous networks and systems that may be temporarily offline. Applications send messages to MSMQ, and MSMQ uses queues of messages to ensure that the messages eventually reach their destination. MSMQ provides guaranteed message delivery, efficient routing, security, and priority-based messaging.
Message queuing is like e-mail (asynchronous) versus the telephone (synchronous). See the example in Figure 1:
Figure 1. Asynchronous versus synchronous communication
Features of MSMQ
MSMQ version 1.0 supports the following features:
- Asynchronous communication
- Message-Oriented Middleware (MOM) Connectionless messaging. With store-and-forward message queuing, applications aren’t affected by network fluctuations and do not have to establish sessions. Because MSMQ uses a sessionless model at the application level, the sender and receiver don’t need to support the same protocol. MSMQ supports Internet Protocol (IP) and Internet Packet eXchange (IPX).
- Network traffic prioritization. Message prioritization allows urgent or important traffic to preempt less-important traffic so you can guarantee adequate response time for critical applications at the expense of less important applications.
- Guaranteed delivery. Messages can be logged to a disk-based queue to provide guaranteed delivery.
- Transactions. The MSMQ transaction flag can be used to implement transaction-based applications, ensure messages are delivered in order, ensure messages are delivered no more than once, and confirm messages reached or were retrieved from the destination queue.
- Dynamic queues. Queue information resides in a dynamic/replicated database so administrators can change queue properties without affecting messaging applications. Using MSMQ Explorer, administrators can make these changes from any computer running MSMQ Explorer.
- Routing. MSMQ supports smart routing, based on the physical topology of the network, session concentration, and transport connectivity. Session concentration allows efficient usage of slow links.
- Security. MSMQ supports privacy and security through access control, auditing, encryption, and authentication. Access control is implemented using Windows NT security and digital signatures. Auditing is implemented through the Windows NT event logging service. Encryption and authentication (using digital signatures) are supported using public and private keys.
- Disparate system integration. MSMQ-based applications can be implemented across a wide variety of hardware platforms using MSMQ connectivity products provided by Level 8 Systems.
Dynamic queues, integrated security, manageable scalability, and smart routing differentiate MSMQ from other middleware implementations available today.
MSMQ differs from Remote Procedure Calls (RPC), where applications are required to maintain sessions, and from Windows Sockets, and messaging API (MAPI). Although Windows Sockets provides low-level functions for writing applications, Windows Sockets does not allow applications to run at different times in the way that MSMQ does and MSMQ uses a more general-purpose message queuing model than MAPI.
MSMQ applications communicate between computers using a unit of information (text or binary data) called a message. Transactional messages, those which can be discarded if the transaction is aborted, can be used to pair the sending or receiving of any message with an action in another operation. Using transactional messages ensures that the unit of work is carried out as an atomic operation, that is, the operation succeeds or fails as a whole. Transactional messages can also be used to ensure that a message is delivered only once and that all messages sent from one computer to another are delivered in order. Positive and negative acknowledgements can be used to confirm messages reached or were retrieved from the destination queue. See Figure 2.
Figure 2. Transactional messages
MSMQ supports two delivery methods: express and recoverable. Choosing between express and recoverable delivery is a matter of trading performance and resource use for reliability and failure recovery. Express messages use fewer resources and are faster than recoverable messages, but cannot be recovered if the computer storing the memory-mapped message fails. Recoverable messages use more resources and are slower than express messages, but can be recovered no matter which computer fails.
MSMQ uses public and private queues to store and forward messages. All MSMQ queues, regardless of their function, can be manipulated with the same MSMQ functions. This includes the special journal, dead letter, transactional dead letter, administration, system, and report queues. Each of the queues is simply a standard MSMQ queue used for a specific purpose. For more information on the MSMQ API, see the Microsoft Message Queuing Services Software Development Kit (MSMQ SDK).
MSMQ routes and delivers messages based on a combination of queue priority and message priority. Messages are routed and delivered by queue priority first, and message priority second.
MSMQ supports dependent clients, independent clients, and servers. Independent clients and servers run the MSMQ Services and can communicate asynchronously, while MSMQ-dependent clients require synchronous access to an MSMQ Service.
Some components of MSMQ Services hold copies of the MSMQ information store (MQIS) database. The MQIS is a distributed database that holds enterprise topology, enterprise settings, computer information, and queue information. MSMQ-based applications can query the MQIS to find queues and get queue properties.
With MSMQ 2.0, the MSMQ information store can also be Microsoft Active Directory™ services.
All computers operate within one MSMQ enterprise, divided into sites, and connected through site links. Site link costs define the cost of sending messages between sites, making communication between any two computers fast and inexpensive. Computers running in MSMQ communicate over connected networks (CNs), a collection of computers where any two computers can communicate directly. MSMQ Services designated as in routing servers (InRSs), out routing servers (OutRSs), and site gates can be used to control the flow of messages and provide session concentration. MSMQ Services take all these factors into account when routing messages within your MSMQ enterprise.
Many of the MSMQ topology concepts have changed with MSMQ 2.0. PECs (Primary Enterprise Controllers) are now Domain Controllers, and the concept of connected networks no longer exists. Make sure to read through these documents carefully.
Programming MSMQ with VFP
This section of this article focuses on Visual FoxPro-specific programming practices for integrating MSMQ into your applications.
Using the MSMQ ActiveX® objects, you can program all common messaging needs including:
- Creating a message queue
- Getting a List of Available Queues
- Opening and deleting queues
- Sending messages
- Reading messages
- Sending messages with acknowledgments
- Responding to an event triggered when message arrives in queue
There are various ActiveX objects (COM servers) available for accessing MSMQ, including those for handling queues, messages and events. This article is not a language reference, so not all the available objects, properties and methods are mentioned below. The focus is on specific scenarios with emphasis on real-world sample code. For information on all the MSMQ objects, refer to the Microsoft Message Queuing Services Administrator’s Guide:
You may not have a need for all of these objects (especially some of the transaction ones), but it’s a good idea to be somewhat familiar with them.
In addition to the core set of objects, MSMQ also provides a rich set of automation components that support composing and parsing the body of MSMQ mail messages, which are used to communicate with e-mail based applications through the MSMQ mail services. These include objects such as MSMQMailEMail, MSMQMailFormData, and MSMQMailRecipient. See the MSMQ SDK docs for more details.
Before you begin using VFP and MSMQ, read some of the comments in source code. They contain valuable tips and idiosyncrasies with using the MSMQ objects.
It is highly recommended that you use Visual FoxPro 6.0 SP3 or higher when programming against MSMQ. There are API functions that require this version.
Listed below are a few examples of why you should incorporate MSMQ into your applications.
Applications that are often disconnected – many Fox developers today are creating distributed multi-tier applications. With these types of applications, it’s sometimes the case where client machines are taken offline from the host server machine(s). This can apply to both Web or LAN based situations. One of the big advantages of MSMQ is that it handles both offline and online scenarios transparently, saving you from writing separate offline and online application logic.
Think of this being similar to your e-mail client. You can write e-mail messages both offline and online without any behavioral distinction.
Asynchronous messaging – have you ever written a Visual FoxPro application where one of the application operations takes a long time to perform (e.g. printing a report, re-indexing a large database, etc.)? Visual FoxPro does not return control to the user until the operation is completed. This can be very annoying to the end user who is trying to be productive with your application. Many Visual FoxPro developers employ kludges to handle these situations such as Timers and DO WHILE loops which continuously poll some resource (e.g., file or table) for a change (semaphore) made by the application. These types of coding habits are not efficient and needlessly waste valuable system resources and processor time. With asynchronous messages, you can setup a VFP object that sits in limbo until a message arrives in a queue. Once this event happens, a method on your VFP object is called. You can call a VFP COM server, which runs in its own process, to handle the application request and allow the user to continue working. The MSMQ Message can contain all the information being passed from your application to your VFP COM “helper” server.
Workflow type applications – typical office settings today require efficient passing of information such as documents between various members of the organization. Along the way, this information is often manipulated and refined. With MSMQ, you can keep a nice audit trail and information can be passed in a secured fashion with guaranteed delivery. Because messages can be sent in a transaction, they will be protected from various risks to the system. See the Response Messages section below for more details.
Basic Queue Operations
Creating a Message Queue
One of the first tasks you will want to do with MSMQ is to create a queue so that you can store messages. The simplest way to do this is by using the MSMQ explorer.
- In the MSMQ Explorer, right-click a computer node and select New -> Queue.
- In the dialog, enter a name for your queue. You will need to decide whether to make it transactional.
Programmatically, you can create a queue with just a few lines of code. Here is the basic code you need to create a public message queue.
lcQueueName = “MyQueue1” oQueueInfo = CreateObject(“msmq.msmqqueueinfo”) oQueueInfo.Pathname = “.\”+lcQueueName &&must be unique oQueueInfo.Label = lcQueueName oQueueInfo.Create
The important Pathname property controls the name and location of the queue. Because it is based on UNC naming conventions, you can include the computer name, or for local queues, use the “.\” qualifier. Alternately, you can use the FormatName property instead of Pathname. The FormatName property is the recommended strategy because it is better suited for offline work. You should be getting a reference to the QueueInfo object. There is a QueueInfos object that represents a collection of QueueInfo objects. Think of a QueueInfo as simply an object containing specific information about the Queue itself such as location. However, there is one important distinction, a QueueInfo does not contain any information about the contents of the queue. The information about the contents is stored in the Queue object. The Queue Query object, which is used to locate a specific queue, returns a QueueInfos object, distinguishing it from possible multiple queues with that same name existing within the enterprise.
**To create a public message queue, you must be online (attached to a valid MSMQ Service (e.g., primary site controller). Independent clients cannot create public queues while offline.
Before creating a public queue, check to see if it already exists by using the following syntax.
lcQueueName = “myqueue1” * Try to locate queue first oQuery = create(“msmq.msmqquery”) * Lookup queue to see if it exists * Important - queue names are case-sensitive oQueueInfos = oQuery.LookupQueue(,,lcQueueName) * Move to first record in queue set oQueueInfo = oQueueInfos.Next() IF ISNULL(oQueueInfo) &&queue not yet created so create it oQueueInfo = CreateObject(“msmq.msmqqueueinfo”) oQueueInfo.Pathname = “.\”+lcQueueName oQueueInfo.Label = lcQueueName oQueueInfo.Create ENDIF
You may need to create a private queue for a variety of reasons, including performance and offline usage. A private queue is named similarly to that of a public queue except it has a “PRIVATE$\” qualifier immediately preceding the name of the queue.
oQueueInfo.Pathname = “.\PRIVATE$\”+lcQueueName &&must be unique
Deleting a Message Queue
While not necessarily a common operation, you may need to delete a particular queue. The following code snippet has this operation.
IF MESSAGEBOX(“Delete queue?”,36+256)=6 oQueueInfo = create(“msmq.msmqqueueinfo”) oQueueInfo.FormatName = cFormatName oQueueInfo.Delete RETURN ENDIF
The QueueInfo’s PUBLIC format name is a unique identified key such as following:
Getting a reference to a specific queue involves just two lines of code. This is an important concept to remember, that is, obtaining a reference to a specific queue whether it be for deleting, opening it up to send a message, opening it up to read a message, etc.
oQueueInfo = create(“msmq.msmqqueueinfo”) oQueueInfo.FormatName = cFormatName
Opening a Queue
Queues are like tables with the messages being similar to records. Think of them also like cursors in that you can have many instances of the same queue open. The QueueInfo is 1:1 relation and points to the Queue object itself. A queue can be opened to either send messages, read (peek) messages, or retrieve (receive) messages. A queue can not be opened to both send and read a message with the same Open() call. However, you can open multiple instances of the same queue in different modes.
To open a queue, you use the QueueInfo Open() method:
oSendQueue = oQueueInfo.Open(MQ_SEND_ACCESS, MQ_DENY_NONE) IF EMPTY(oSendQueue.IsOpen) RETURN ENDIF
You are probably wondering about the checking here with IsOpen. You should always check to see if your Open operation succeeded. The queue may be unavailable or exclusively in use. The EMPTY() function is used because some versions of MSMQ return logical while others return 0/1.
The source code includes an MSMQ.H file which is the #INCLUDE file for all the MSMQ constants. These constants are stored in the MSMQ type library. The samples include this file, but can alternately be recreated using the following code (you will need to have VB installed for the special COM server library that reads type libraries).
* GETCONSTANTS.PRG LOCAL lctlbfile, oTLB, oTInfos, lcNewfile, i, j, lnTmpValue * MSMQ specific files lctlbfile = “c:\winnt\system32\mqoa.dll” lcNewFile = HOME()+”msmq.h” oTLB=create(“tli.tliapplication”) oTInfos=oTlb.TypeLibInfoFromFile(lctlbfile) SET TEXTMERGE ON NOSHOW TO (lcNewFile) \\* Constants for type library: <<lctlbfile>> \* <<oTInfos.HelpString>> \ FOR i = 1 TO oTInfos.Constants.Count \* <<oTInfos.Constants[m.i].Name>> Enum \* <<oTInfos.Constants[m.i].HelpString>> FOR j = 1 TO oTInfos.Constants[m.i].Members.Count \#DEFINE \\<<oTInfos.Constants[m.i].Members[m.j].Name>> lnTmpValue = oTInfos.Constants[m.i].Members[m.j].Value \\<<lnTmpValue>> ENDFOR \ ENDFOR SET TEXTMERGE TO SET TEXTMERGE OFF MODIFY FILE (lcNewFile) NOWAIT
Getting a List of Available Public Queues
You may have a need to get a list of all public queues available (independent clients need be online). The following code snippet populates an array aQueues with pathnames of public queues available.
* Create/locate queue LOCAL oQuery,oQueueInfo,oQueueInfos,aQueues DIMENSION aQueues oQuery = create(“msmq.msmqquery”) oQueueInfos = oQuery.LookupQueue() * Move to first record in queue set oQueueInfo = oQueueInfos.Reset oQueueInfo = oQueueInfos.Next DO WHILE !ISNULL(oQueueInfo) IF !EMPTY(aQueues[ALEN(aQueues)]) DIMENSION aQueues[ALEN(aQueues)+1] ENDIF aQueues[ALEN(aQueues)] = oQueueInfo.pathname oQueueInfo = oQueueInfos.Next ENDDO
Getting List of Available Private Queues
Obtaining a list of private queues is a little less intuitive, and the objects themselves do not support specific language to do this. The following work-around seems to handle this fine with MSMQ 1.0 and 2.0 (Microsoft does not support this strategy so use it at your risk).
#DEFINE MSMQ_KEY SOFTWARE\Microsoft\MSMQ\Setup” #DEFINE HKEY_LOCAL_MACHINE 2147483646 LOCAL oQuery, QueueInfo, QueueInfos, aQueues, i, j, aKeys LOCAL aFiles,lcQueue,lcMSMQPath, oReg, oIni DIMENSION aQueues DIMENSION akeys[1,1] DIMENSION aFiles oReg = NewObject(“registry”,”ffc\registry.vcx”) oIni = NewObject(“oldinireg”,”ffc\registry.vcx”) IF oReg.EnumOptions(@akeys, MSMQ_KEY, HKEY_LOCAL_MACHINE, .F.) = 0 FOR i = 1 TO ALEN(akeys,1) IF ATC(“Directory”,aKeys[m.i,1])#0 lcMSMQPath = akeys[m.i,2]+”\Storage\LQS\” ADIR(aFiles, lcMSMQPath + “*.*”) FOR j = 1 TO ALEN(aFiles) lcQueue = ““ oIni.GetIniEntry(@lcQueue,”Properties”,; “QueueName”,lcMSMQPath+aFiles[m.j]) IF ATC(“private$”,lcQueue)#0 IF !EMPTY(aQueues[ALEN(aQueues)]) DIMENSION aQueues[ALEN(aQueues)+1] ENDIF aQueues[ALEN(aQueues)] = “.”+lcQueue ENDIF ENDFOR EXIT ENDIF ENDFOR ENDIF
Basic Message Operations
Once you have your queue created, you are going to want to create or read messages in it. By far, these are the most common things you do with messaging, just as you would do with e-mail.
Creating/Sending a Message
We now have our queue open specifically for sending a message. Remember, each object reference returned by the Open method can either be used for sending, peeking or receiving a message. However, you can have multiple concurrent object references, each performing a different function. Let’s go ahead and create a simple message.
oMsg = create(“msmq.msmqmessage”) oMsg.Label = “My First Message” oMsg.Body = “This is body for my first message.” oMsg.Send(oSendQueue)
In this example, we compose a message that is not dependent of any queue. The body of the message is text we want to post. Later, we will look at alternative content for the message body. When we are finished composing the message, we send it to a queue that we already have opened for sending messages. Sending messages is made simple using this core set of code, but there are lots of options/attributes that can be used.
Sending Offline Messages
If you are running an Independent Client machine that is temporarily offline, or the machine with the destination queue unavailable (e.g., offline), then you will need to handle opening a queue a little differently than the typical way using Pathname:
oQueueInfo = create(“msmq.msmqqueueinfo”) oQueueInfo.Pathname = “randybr9\myQueue1”
Earlier in this document, the FormatName was described as an alternative approach to using Pathname. If you are sending a message to a public queue while offline (even to your own machine), you must use the FormatName when getting a reference to the queue.
For private queues, it is fine to use Pathname.
The reason for using FormatName is that MSMQ needs to access the MSMQ Services when getting information about a public queue to route the message (in MSMQ 1.0, this routing info is stored in a Microsoft SQL Server™ database called MQIS). In offline mode, this information is unavailable, so the queue cannot be opened.
This concept is similar to working with e-mail offline. If you have your Microsoft Outlook® client opened offline and send a message, the message sits in limbo until you connect to your network where the e-mail server is available to resolve and provide proper routing information. The process of sending a message, when all information for proper routine is unavailable, is known in messaging lingo as Store and Forward.
MSMQ provides this same service if your MSMQ Service is unavailable to route the message to a public queue. Instead of using the Pathname for queue specification, you use the FormatName. MSMQ supports several different types of Format Names for use. My preference is the Direct Format Name type (see MSMQ reference for details on other types).
oQueueInfo = create(“msmq.msmqqueueinfo”) oQueueInfo.FormatName = “DIRECT=OS:randybr9\myQueue1”
Here are some more examples of Direct Format Names:
“DIRECT=TCP:184.108.40.206\MyQueue” “DIRECT=OS:elvisp.ms.com\myqueue” “DIRECT=OS:randybr2\PRIVATE$\myqueue”
If using the Direct Format Name, MSMQ will not try to access queue information from the MQIS database. The message is in essence sent blindly to an address that may or may not exist. Until the machine is back online, that address cannot fully be resolved. This strategy can also improve performance. MSMQ will periodically check to see if you are online so that it can process the outgoing queues.
Checking if you are Offline
It’s a good idea if you are working with Independent Clients to have a check in your code to see if MSMQ is online/offline. Again, MSMQ does not support specific language for this, so you can try the following code:
* This program checks to see if MSMQ is offline so * demos will use Private queues LOCAL x x = create(“checkoffline”) RETURN x.IsOffline() DEFINE CLASS checkoffline AS custom PROCEDURE IsOffline LOCAL lcQueueName,oQuery,oQueueInfos,oQueueInfo lcQueueName = ““ oQuery = create(“msmq.msmqquery”) oQueueInfos = oQuery.LookupQueue oQueueInfo = oQueueInfos.Reset oQueueInfo = oQueueInfos.Next RETURN VARTYPE(oQueueInfo)#”O” PROCEDURE Error(p1,p2,p3) ENDDEFINE
You should make sure you have a routine like this to determine if you can open a queue using Pathname or Formatname.
Reading a Message
Once your message queue starts to fill up with messages, you’ll probably want to read (handle) them. It is important that you understand the difference between reading (peeking) at messages vs. retrieving (receiving) them.
MSMQ offers 2 options for viewing messages, the primary difference being the resulting action after processing of the message. If you Peek at a message, it is left in the queue. If you Receive a message, it is removed from the queue. The idea behind the later is that you are processing the message once and only once. These concepts are important because MSMQ guarantees delivery and is transactional. Acting on the same message twice can cause a redundant transaction. Again, you need to open a queue specifically for either of these types of message viewing.
oRecQueue = oQueueInfo.Open(MQ_RECEIVE_ACCESS, MQ_DENY_NONE) oPeekQueue = oQueueInfo.Open(MQ_PEEK_ACCESS, MQ_DENY_NONE)
Let’s first look at message peeking. When you first open your queue for peeking, you need to see if any messages exist in the queue. The following code snippet shows this.
oMsg = oPeekQueue.Peek(,,100) IF ISNULL(oMsg) ? “Queue is empty” ENDIF
There are three Peek functions you can use (Peek, PeekCurrent, and PeekNext).
The documentation for these three functions for MSMQ 1.0 is incorrect in terms of the ordering of the parameters. The nReceiveTimeout parameter is actually the last parameter.
The default for the nReceiveTimeout parameter is infinity, causing your machine to appear hung if it is trying to find a message in an empty queue. This documentation bug is also prevalent with the Receive and ReceiveCurrent functions. You can use the following code to iterate through all of the messages in your queue.
oMsg = oPeekQueue.PeekCurrent(,,100) DO WHILE !ISNULL(oMsg) ? oMsg.Body,oMsg.Class oMsg = oPeekQueue.PeekNext(,,100) ENDDO oPeekQueue.Close
Again, since we are only peeking at messages, none of the messages will be deleted from the queue. You can see them in the MSMQ Explorer. The next example shows actually Receiving messages for processing.
nMessages = 0 oMsg = oRecQueue.ReceiveCurrent(,,,100) DO WHILE !ISNULL(oMsg) nMessages = nMessages+1 oMsg = oRecQueue.ReceiveCurrent(,,,100) ENDDO ? “Total messages:”,nMessages oRecQueue.Close
If you run this code on your message queue, you will notice that the messages no longer appear in the Queue under MSMQ Explorer. Again, this is because Receiving messages causes their removal from the queue.
Using a Journal
You’re probably thinking that having messages removed from the queue is a bad thing since there is no way to trace their activity. And it’s likely that you want some sort of activity (audit) log for your application. MSMQ makes it simple to do this. When you create your public/private queue, you can automatically have an associated Journal queue created at the same time (or later on).
oQueueInfo.Journal = 1
A Journal is simply an associated queue for processed messages. When a message is Received (via Receive or ReceiveCurrent), it is automatically processed and moved to the Journal queue. You can view the Queue’s Journal queue in the MSMQ Explorer by expanding the Queue’s tree node.
In fact, you can also programmatically read through the messages in a particular Journal:
* cFormatName is derived from original Queue (see above) oQueueInfo = create(“msmq.msmqqueueinfo”) oQueueInfo.FormatName = cFormatName+”;JOURNAL” oJournalQueue = oQueueInfo.Open(MQ_PEEK_ACCESS,MQ_DENY_NONE) oMsg = oJournalQueue.PeekCurrent(,,100) DO WHILE !ISNULL(oMsg) oMsg = oJournalQueue.PeekNext(,,100) ? oMsg.Body,oMsg.Class ENDDO oJournalQueue.Close
Sending a Message with Acknowledgment
We’ve now added some simple messages to a queue and even found a way to read/process them. No doubt you’ve sent e-mail to a friend in the past and never received an acknowledgment. While this might be fine for simple e-mail, often with your application, you are going to want some sort of acknowledgment that the message was received.
MSMQ offers two types of acknowledgments.
- When message reached queue (MQMSG_ACKNOWLEDGMENT_FULL_REACH_QUEUE)
- When message was actually read (MQMSG_ACKNOWLEDGMENT_FULL_RECEIVE)
As with most operations involving MSMQ, acknowledgments are handled via sending a new message to a queue. The only difference with an Ack message is that there is no attached body. A special queue is setup for the acknowledgment message. This is referred to as an Admin Queue. An Admin Queue is actually just a normal queue (you can have a Journal associated with it), and Ack messages which you process (Receive) are removed (and optionally forwarded to associated Journal). An Admin Queue is created in the normal fashion like any other queue (see above).
The primary difference with message acknowledgments is not in the Admin Queue, but rather in the composition of the message itself. Here is an example of a message being sent with instructions for acknowledgment.
* Create new messages oSendQueue = oQueueInfo.Open(MQ_SEND_ACCESS, MQ_DENY_NONE) IF EMPTY(oSendQueue.IsOpen) RETURN ENDIF oMsg = create(“msmq.msmqmessage”) oMsg.Label = “Test Message” oMsg.Body = “This is a sample message.” oMsg.Ack = MQMSG_ACKNOWLEDGMENT_FULL_RECEIVE *oMsg.Ack = MQMSG_ACKNOWLEDGMENT_FULL_REACH_QUEUE oMsg.AdminQueueInfo = oAdminQueueInfo oMsg.Send(oSendQueue) oSendQueue.Close
The only difference with a normal message is setting of the Ack and AdminQueueInfo properties.
Setting of AdminQueueInfo property requires Visual FoxPro 6.0 SP3 or higher.
Reading Message Acknowledgments
You are going to want to check to see whether all of the messages reached their target queue, were received within a timeout period, etc. This is all handled by the Class property of the message.
oRecQueue = oAdminQueueInfo.Open(MQ_RECEIVE_ACCESS, MQ_DENY_NONE) oMsg = oRecQueue.PeekCurrent(,,100) IF !ISNULL(oMsg) DO CASE CASE oMsg.Class = MQMSG_CLASS_ACK_REACH_QUEUE MESSAGEBOX(“The message reached the queue.”) CASE oMsg.Class = MQMSG_CLASS_ACK_RECEIVE MESSAGEBOX(“The message was acknowledged as received.”) CASE oMsg.Class = MQMSG_CLASS_NACK_RECEIVE_TIMEOUT MESSAGEBOX(“Message was not removed from the queue in time.”) OTHERWISE MESSAGEBOX(“The message did not reach the queue.”) ENDCASE oMsg = oRecQueue.Receive(,,,100) &&remove from queue ENDIF oRecQueue.Close
If you are receiving Ack messages based on when the target message queue actually processes (Receive, ReceiveCurrent) the message, you can also impose a timeout. This is handled by the message’s MaxTimeToReceieve property.
If the time-to-be-received timer expires before the message is removed from the queue, MSMQ discards the message, sending it to the dead letter queue if the message’s Journal property is set to MQMSG_DEADLETTER.
MSMQ can also send a negative acknowledgment message back to the sending application if the message is not retrieved before the timer expires.
oMsg = create(“msmq.msmqmessage”) oMsg.Label = “Test Message” oMsg.Body = “This is a sample message.” oMsg.Ack = MQMSG_ACKNOWLEDGMENT_FULL_RECEIVE oMsg.MaxTimeToReceive = 20 oMsg.AdminQueueInfo = oAdminQueueInfo oMsg.Send(oSendQueue)
There is also a similar MaxTimeToReachQueue property.
If the queue is local, the message always reaches the queue.
These two Acknowledgment operations are facilitated by MSMQ’s built-in Message Timers.
More About Message Timers
MSMQ provides two timers to help you maintain better control of your messages: a time-to-be-received and a time-to-reach-queue timer.
The time-to-be-received timer determines how long a message remains in the system, starting from the time the message is sent, to the time it is removed from the target queue.
The time-to-reach-queue timer determines how long a message has until it reaches the target Queue Manager of the target queue. Typically, this timer is set to a value less than the time-to-be-received setting.
When both timers are used, and if the time-to-be-received timer is set to a shorter time interval than the time-to-reach-queue timer, it takes precedence over the time-to-reach-queue timer. MSMQ does not allow messages to remain in the system longer than the time allowed by their time-to-be-received timer.
When either timer expires, MSMQ discards the message. However, MSMQ can also send a copy of the message to a dead letter queue or an acknowledgment message to an administration queue. If the message’s acknowledgment property specifies full or negative acknowledgments, MSMQ sends the appropriate negative acknowledgment message to the administration queue specified by the message. If the message’s journal property specifies a dead letter queue, a copy of the message is sent to one of two places. The copies of non-transactional messages are sent to the dead letter queue on the computer where the timer expired. Copies of transactional messages are copied to the transactional dead letter queue on the source machine.
Message acknowledgments are convenient for many application needs, especially those involving workflows. You might also want to consider a more detailed approach using an actual response message. Similar to the Admin Queue used for Ack messages, you will need to set up a special Response Queue for Response messages. A major difference with Ack messages is that the Response message contains a body. In reality, the Response Queue is nothing fancy and could easily be handled without the built-in functionality.
Sending Message with Response
Sending a message that asks for a response requires use of the ResponseQueueInfo property of your outgoing message.
* Create new messages oSendQueue = oQueueInfo.Open(MQ_SEND_ACCESS, MQ_DENY_NONE) oMsg = create(“msmq.msmqmessage”) oMsg.Label = “Test Message” oMsg.Body = “This is a sample message.” oMsg.Delivery = MQMSG_DELIVERY_RECOVERABLE oMsg.ResponseQueueInfo = oResponseQueueInfo oMsg.Send(oSendQueue) oSendQueue.Close
Reading Message from Response Queue
Messages can be read from the Response Queue. When a message arrives at its target queue, you can query the ResponseQueueInfo property to get an object reference to the Response Queue. Once you have this, you simply create a new message (with body) to send.
* Read message from target queue oRecQueue = oQueueInfo.Open(MQ_RECEIVE_ACCESS, MQ_DENY_NONE) oMsg = oRecQueue.Receive(,,,100) IF ISNULL(oMsg) RETURN ENDIF * See is a reponse queue is setup and send message if so IF !ISNULL(oMsg.ResponseQueueInfo) oRespQueue = oMsg.ResponseQueueInfo.Open(MQ_SEND_ACCESS,; MQ_DENY_NONE) oMsg2 = create(“msmq.msmqmessage”) oMsg2.Label = “Response Message” oMsg2.Body = “This is a response message” oID = oMsg.id cTmpStr=““ FOR i = 1 TO ALEN(oID) cTmpStr = cTmpStr+CHR(oID[m.i]) ENDFOR oMsg2.CorrelationId = CreateBinary(cTmpStr) oMsg2.Send(oRespQueue) MESSAGEBOX(“A response message was returned.”) oRespQueue.Close ENDIF
Take a close note at the code for the CorrelationID property. This property is a 20-element array of bytes. You will need to first convert it to a string and then run it through Visual FoxPro’s CreateBinary function.
Advanced VFP Programming for MSMQ
In this section, we will explore a number of advanced topics that will be of value in your MSMQ application.
Sending Private Messages
MSMQ provides a secured channel for sending private, encrypted messages throughout your MSMQ enterprise. MSMQ ensures that the body of private messages are kept encrypted from the moment they leave the source Queue Manager to the moment they reach their target Queue Manager.
With encryption and decryption provided by MSMQ Queue Managers, applications do not have to encrypt messages when they are sent, or decrypt received messages. When a private message is sent, the source Queue Manager encrypts the body of the message, then sends the message on to the target Queue Manager. When the target Queue Manager receives the message, it decodes the body of the message and passes the clear message on to the queue. The receiving application can then read the message from the queue without ever knowing it was encrypted.
The receiving application sees the message as clear text. However, it can look at the message’s privacy level to determine whether the message was sent encrypted, or look at the encryption algorithm used when the message was sent.
Sending private messages is easy:
Optional. Verify that the queue can receive private messages. The MSMQQueueInfo object’s PrivLevel must be set to MQ_PRIV_LEVEL_BODY or MQ_PRIV_LEVEL_OPTIONAL. If set to MQ_PRIV_LEVEL_BODY, the queue can only accept private messages. Non-private messages will be ignored.
oQueueInfo.PrivLevel = MQ_PRIV_LEVEL_BODY
Open the queue for sending messages.
Set the MSMQMessage object’s PrivLevel property to MQMSG_PRIV_LEVEL_BODY.
oMsg.PrivLevel = MQMSG_PRIV_LEVEL_BODY
Optional. Set the encryption algorithm used to encrypt the message.
oMsg.EncryptAlgorithm = MQMSG_CALG_RC4
Send the message.
Here is a sample VFP snippet putting it all together:
* Create the queue with Privacy setting oQueueInfo = create(“msmq.msmqqueueinfo”) oQueueInfo.pathname = “.\”+lcQueueName &&must be unique oQueueInfo.Label = lcQueueName oQueueInfo.PrivLevel = MQ_PRIV_LEVEL_OPTIONAL oQueueInfo.Journal = 1 oQueueInfo.Create * Create new message oSendQueue = oQueueInfo.Open(MQ_SEND_ACCESS, MQ_DENY_NONE) oMsg = create(“msmq.msmqmessage”) oMsg.Label = “My Private Message” oMsg.Body = “This is a private message.” oMsg.PrivLevel = MQMSG_PRIV_LEVEL_BODY oMsg.EncryptAlgorithm = MQMSG_CALG_RC4 oMsg.Send(oSendQueue) oSendQueue.Close * Read the message oRecQueue = oQueueInfo.Open(MQ_RECEIVE_ACCESS, MQ_DENY_NONE) oMsg = oRecQueue.ReceiveCurrent(,,,100) If oMsg.PrivLevel = MQMSG_PRIV_LEVEL_BODY ?”Private message: “ + oMsg.Label ELSE ?”Public message: “ + oMsg.Label ENDIF ? + oMsg.Body ? “Encryption alogithm is: “ + TRANS(oMsg.EncryptAlgorithm) oMsg = oRecQueue.ReceiveCurrent(,,,100) oRecQueue.Close
Message Delivery Options
An important concept you should understand when sending messages is the various Delivery options.
- MQMSG_DELIVERY_RECOVERABLE - In every hop along its route, the message is forwarded to the next hop or stored locally in a backup file until delivered. This guarantees delivery even in the case of a machine crash.
- MQMSG_DELIVERY_EXPRESS - The default. The message stays in memory until it can be delivered. (In-memory message store and forward.)
When the message’s delivery mechanism is set to MQMSG_DELIVERY_EXPRESS, the message has faster throughput. When set to MQMSG_DELIVERY_RECOVERABLE, throughput may be slower. However, MSMQ guarantees that the message will be delivered, even if a computer crashes while the message is en-route to the queue.
Handling this is in your code is simply a matter of setting a single message property.
oMsg.Delivery = MQMSG_DELIVERY_EXPRESS oMsg.Send(oSendQueue)
Messages must be sent in recoverable mode if the offline client computer is turned off. Messages sent in express mode are held in RAM and will be lost when the computer is turned off.
Binding Excel Objects to Message Bodies
One of the truly powerful features of MSMQ is the ability to attach COM objects to the bodies of messages. This works for quite a few common objects, including ADO recordsets and Excel spreadsheets. In this section, we will look at spreadsheets. Before you think about trying to attach a Visual FoxPro object, be aware of the following rule for message body content.
The body of an MSMQ message can be a string, an array of bytes, or any persistent COM object that supports IDispatch and IPersist (IPersistStream or IPersistStorage).
Visual FoxPro 6.0 objects do not support the ability to persist themselves via IPersist.
There are several ways to attach Excel data to your message. The following example demonstrates creating a message and attaching an existing Excel file to the message. You can use Visual FoxPro’s GETOBJECT() function to get an object reference to the file and set it directly to the Body property.
* Attach existing Excel worksheet to message body #DEFINE cXLFile “c:\offices.xls” oMsg = create(“msmq.msmqmessage”) oMsg.Label = “Excel OFFICES.XLS Message” oMsg.Body = Getobject(cXLFile) oMsg.Send(oSendQueue) oSendQueue.Close
Alternatively, you can create an Excel spreadsheet object on the fly and persist its contents to the message body.
* Attach Excel worksheet to message body oMsg = create(“msmq.msmqmessage”) oMsg.Label = “New Excel Object Message” oXL=Create(“excel.application”) oXL.workbooks.add oXL.rows(1).columns(1).value = “Dogs” oXL.rows(1).columns(2).value = “Cats” oXL.rows(2).columns(1).value = 333 oXL.rows(2).columns(2).value = 555 oMsg.Body = oXL.activesheet.parent oMsg.Send(oSendQueue) oSendQueue.Close oXL.DisplayAlerts = .F. oXL.quit
Before reading the contents of your Excel spreadsheet object, first check the type of object you are dealing with. The following code uses the TYPE() function to check the object type, and shows accessing the Active Sheet object reference to obtain specific spreadsheet content. There is also code to iterate through the contents of the spreadsheet, however, you can use whatever automation calls you prefer.
The message body contains a reference to the application object.
* Read in the Excel object oRecQueue = oQueueInfo.Open(MQ_RECEIVE_ACCESS, MQ_DENY_NONE) oMsg = oRecQueue.PeekCurrent(,,100) oObj = oMsg.Body DO WHILE !ISNULL(oObj) IF TYPE(“oObj.Application.Name”)=“C” AND; ATC(“Excel”,oObj.Application.Name)#0 oMsg = oRecQueue.ReceiveCurrent(,,,100) oObj = oMsg.Body oSheet = oObj.ActiveSheet ? “Excel contents:” FOR i = 1 TO 100 IF EMPTY(oSheet.Rows(m.i).Columns(1).Text) EXIT ENDIF ? FOR j = 1 TO 100 lcText = oSheet.Rows(m.i).Columns(m.j).Text IF EMPTY(lcText) EXIT ENDIF ?? lcText+”,” ENDFOR ENDFOR EXIT ENDIF oMsg = oRecQueue.PeekNext(,,100) oObj = oMsg.Body ENDDO oRecQueue.Close
Binding ADO Recordset Objects to Message Bodies
ADO recordsets represent a valuable medium for passing data via MSMQ messages. Rather than passing a long string representing a record from your FoxPro table, which you would later need to parse, you can pass a more efficient object. Additionally, the data is stored in an encrypted format.
Using the new VFPCOM utility available from the Visual FoxPro Web site, (http://msdn.microsoft.com/vfoxpro/downloads/updates.asp), you now have an excellent mechanism for passing Visual FoxPro data. VFPCOM contains methods for converting VFP data to/from ADO recordsets. The following sample shows VFPCOM used to convert a VFP cursor to a recordset, and then attach that recordset to a message.
oMsg = create(“msmq.msmqmessage”) oMsg.Label = “ADO Message” USE _samples+”\data\employee” AGAIN SHARED oComUtil = create(“vfpcom.comutil”) oRS = CreateObject(“ADODB.Recordset”) oComUtil.CursorToRS(oRS) oMsg.Body = oRS oMsg.Send(oSendQueue) oSendQueue.Close
Reading and detaching the ADO recordset is easy as demonstrated in the following code, but don’t forget to first check for a valid ADO object. Use the TYPE() function with a common known member of the object.
* Receive the messages oRecQueue = oQueueInfo.Open(MQ_RECEIVE_ACCESS, MQ_DENY_NONE) * Get the ADO object oMsg = oRecQueue.PeekCurrent(,,100) oObj = oMsg.Body DO WHILE !ISNULL(oObj) IF TYPE(“oObj.eof”)=“L” oMsg = oRecQueue.ReceiveCurrent(,,,100) oObj = oMsg.Body oObj.MoveFirst DO WHILE !oObj.eof ? oObj.Fields.Value oObj.MoveNext ENDDO EXIT ENDIF oMsg = oRecQueue.PeekNext(,,100) oObj = oMsg.Body ENDDO oRecQueue.Close
Using XML with Messages
One of the more exciting uses for messages is passing of XML data. XML data can be used for a variety of purposes, including storage of relational data and other structured information. MSMQ messages are nicely suited for passing XML data, because the message body can persist this structured data.
We’ve already discussed passing ADO recordsets and Microsoft Office objects such as Excel spreadsheets. You’re probably wishing that it was easy to attach VFP cursors and objects to messages as well. XML is a wonderful medium for doing just this.
Rick Strahl’s Web site (www.west-wind.com/) contains an excellent set of XML classes called wwXML, which you can use with your applications. The following table contains some of the wwXML methods of interest.
|CursorToXML||Converts a cursor into an XML representation.|
|XMLToCursor||Converts an XML document created with CursorToXML back into a cursor.|
|ObjectToXML||Converts a live reference of an object to XML. All variables are converted to text and stored. Optionally can walk nested objects.|
|XMLToObject||Creates an object from an XML structure.|
Here is an example using wwXML to persist a VFP cursor.
#DEFINE WWXML_PATH “C:\VFP\WWXML\” LOCAL lcQueueName,oQueueInfo,oSendQueue, oXML,oMSG SET CLASSLIB TO (WWXML_PATH + “wwXML.vcx”) ADDITIVE SET PROCEDURE TO (WWXML_PATH + “wwUtils.prg”) ADDITIVE lcQueueName = “myXMLqueue” oQueueInfo = CreateObject(“msmq.msmqqueueinfo”) oQueueInfo.Formatname = “DIRECT=OS:.\”+lcQueueName oSendQueue = oQueueInfo.Open(MQ_SEND_ACCESS, MQ_DENY_NONE) USE _samples+”\data\customer” AGAIN SHARED oXML = NewObject(“wwXML”) oXML.lCreateDataStructure = .T. oMsg = create(“msmq.msmqmessage”) oMsg.Label = “Persisted VFP Cursor - “ + TRANS(DATETIME()) oMsg.Body = oXML.CursorToXML() oMsg.Send(oSendQueue) oSendQueue.Close
One of the considerations when using XML with MSMQ 1.0, is that the XML will be stored as simple text, which typically is not encrypted. You may need to take some precautions and use security. There is also an EncryptAlgorithm message property that can be used to encrypt the message, however, this only applies for private messages.
Working with MSMQ Events and Asynchronous Message Reading
Applications such as Visual Basic® allow you to bind to events of COM objects. Visual FoxPro 6.0 supports this with ActiveX controls, but not with COM objects such as an ADO Recordset. This means you will not have the ability to bind a VFP object to a COM object and have user code executed when an event of the COM object is triggered.
The new VFPCOM utility (see above) addresses this critical need. You can now have VFP code executed when a message arrives in a particular queue. MSMQ triggers an event that VFP can respond to. The advantage here is that your application can now handle reading messages in the queue asynchronously. Instead of having some object using up lots of processor time consumed in an endless DO WHILE loop, you can have your dormant objects awaken only when a message arrival event occurs.
The process of setting up a queue for asynchronous reading of messages is as follows:
- Create your VFP MSMQ events class to handle reading of messages as they arrive in queues. This class needs to contain Arrived and ArrivedError methods to handle the message arrival events.
- Use VFPCOM to bind an instance of your VFP MSMQ events class to an instance of an MSMQEvent object. This is done with VFPCOM’s BindEvents method.
- Open the queue and call its EnableNotification method passing the MSMQEvent object you just setup with BindEvents.
You must leave the queue open and the BindEvents call intact, otherwise your event connection loses scope and will fail.
The following class definition is a sample template that can be used for your event binding. Notice that you can use the AppSpecific property to special case handling for certain messages.
DEFINE CLASS foxevent AS custom Procedure Arrived(oQueue,Cursor) oMsgRec = oQueue.PeekCurrent(,,0) ? “Message Arrived Event: “+TRANS(oMsgRec.AppSpecific) IF oMsgRec.AppSpecific = 33 &&get only this one oMsgRec = oQueue.ReceiveCurrent(,,,0) oMsgRec = oQueue.PeekCurrent(,,0) oQueue.EnableNotification(oMSMQEvent,MQMSG_CURRENT,1000) ELSE oQueue.EnableNotification(oMSMQEvent,MQMSG_NEXT,1000) ENDIF ENDPROC Procedure ArrivedError(Queue,ErrorCode,Cursor) ? “Message Arrived Error” ENDPROC ENDDEFINE
Once you have your class defined, you can set things up for asynchronous reading of messages. The remainder of the code below shows how to set up the event handler. This is also a simple way to test the event handling by adding a bunch of messages.
* Setup up event handler to receive the messages oMSMQEvent = create(“msmq.msmqevent”) oComUtil = create(“vfpcom.comutil”) oFoxEvents = create(“foxevent”) oComUtil.BindEvents(oMSMQEvent,oFoxEvents) oRecQueue = oQueueInfo.Open(MQ_RECEIVE_ACCESS,MQ_DENY_NONE) oRecQueue.EnableNotification(oMSMQEvent,MQMSG_CURRENT,1000) * Add a bunch of new message oSendQueue = oQueueInfo.Open(MQ_SEND_ACCESS, MQ_DENY_NONE) oMsg = create(“msmq.msmqmessage”) oMsg.AppSpecific = 11 oMsg.Send(oSendQueue) oMsg.AppSpecific = 22 oMsg.Send(oSendQueue) oMsg.AppSpecific = 33 oMsg.Send(oSendQueue) oMsg.AppSpecific = 44 oMsg.Send(oSendQueue) oMsg.AppSpecific = 55 oMsg.Label = “Message55” oMsg.Body = “This is body of message 55.” oMsg.Send(oSendQueue) oSendQueue.Close
Working with MSMQ Transactions
MSMQ transactions are used by the sending and receiving application. In this model, MSMQ uses two transactions, one to send messages to the queue and the other to retrieve messages from the queue. The sending transaction can commit to sending the messages to the queue and the receiving application can commit to retrieving the messages; MSMQ provides its own confirmation process to notify the sending application that either the messages were retrieved from the queue or why the receiving application failed to retrieve them. MSMQ provides both implicit and explicit mechanisms for incorporating transactions. There are two types of implicit transactions, MTS Transactions and XA-Compliant Transactions. Let’s quickly look at the two explicit ones (MSMQ Internal Transactions and MS DTC External Transactions) and then come back to the MTS Transactions.
MSMQ Internal Transactions
MSMQ Internal Transactions provide better performance for transactions that only send or receive MSMQ messages. Unlike MS DTC external transactions, MSMQ internal transactions cannot be passed to another resource manager. It is the cost of coordinating between several resource managers that make MSMQ internal transaction less expensive in terms of memory than MS DTC external transactions.
When sending a single message, MSMQ provides a single-message send operation that uses an MSMQ internal transaction. This mode of sending a message provides the best performance of all transaction types. When using this mode, MQBeginTransaction and Commit are implied.
When setting up your queue for handling internal transactions, you must ensure that the queue is created to allow for transactions. This is handled in the Create() method shown below.
oQueueInfo = create(“msmq.msmqqueueinfo”) oQueueInfo.pathname = “.\”+lcQueueName oQueueInfo.Label = lcQueueName oQueueInfo.Journal = 1 * First parameter determines if queue is transactional oQueueInfo.Create(.T.)
Sending Messages with Transactions
Transactions can be included at the sending and receiving end of messages. The process for setting up messages for transactions is quite simple. You create an object instance of the MSMQ Transaction Dispenser object, to start a transaction. This object has a single method called BeginTransaction, which returns an MSMQ Transaction object. This object is then used to control Commit/Abort operations on sending/receiving messages. The following code demonstrates how this works for sending messages.
* Setup Transaction and Transaction Dispenser objects oTransDisp = create(“msmq.msmqtransactiondispenser”) oTrans = oTransDisp.BeginTransaction * Create new message oMsg = create(“msmq.msmqmessage”) oMsg.Label = “My Trans Message” oMsg.Body = “This is a trans message.” oMsg.Send(oSendQueue, oTrans) oSendQueue.Close
If your queue is not set up to handle transactions, you will get an error during the Send method call.
At this point, we have a message that we sent to our queue. The message does not appear in the queue in the MSMQ Explorer because the Send transaction was not yet committed. If you commit the operation by calling the Transaction object’s commit method, then the message is posted to the queue. If you abort, then it is discarded.
IF MESSAGEBOX(“Do you want to commit sending message(Yes) ; or abort(No)?”,36)=6 oTrans.Commit ELSE oTrans.Abort && causes message to be disgarded MESSAGEBOX(“Queue should be empty since message was disgarded.”) RETURN ENDIF
In the example I was actually able to commit/abort the transaction even after I closed the queue.
Receiving Messages with Transactions
A transaction can be associated with receiving message, just as it was with sending messages.
oTransDisp = create(“msmq.msmqtransactiondispenser”) oTrans = oTransDisp.BeginTransaction oMsg = oRecQueue.ReceiveCurrent(oTrans,,,100) ? oMsg.Label ? oMsg.Body oRecQueue.Close IF MESSAGEBOX(“Do you want to commit receiving message(Yes) ; or abort(No)?”,36)=6 oTrans.Commit MESSAGEBOX(“Queue should be empty with message moved to Journal.”) ELSE oTrans.Abort MESSAGEBOX(“Queue should contain unread message.”) ENDIF
The Abort operation does not actually discard the message. It instead treats it as unread.
When message are not processed, this can be used as an alternate approach to using the Peek functions to read messages.
MS DTC External Transactions
MS DTC External Transactions are used when the transaction includes more actions than simply sending or retrieving MSMQ messages (more than one resource manager is used). In this case, the application must ask MS DTC (Microsoft® Distributed Transaction Coordinator) for a transaction object and explicitly reference that object each time it sends a message, retrieves a message, or executes an action of another resource manager.
When an application is performing a MS DTC transaction, MSMQ is acting as part of a transaction processing system that includes a transaction manager and any number of resource managers.
Programming to use explicit external transactions is not much different from the examples shown above with internal transactions. In fact, the only difference is the Transaction Dispenser being used.
oTransDisp = create(“msmq.msmqCoordinatedTransactionDispenser”)
If you get an error message after your BeginTransaction call, you probably don’t have DTC running. You can turn it on via the SQL Server Service Manager (note: many people set their system to automatically turn on DTC during startup).
oTransDisp = create(“msmq.msmqCoordinatedTransactionDispenser”) oTrans = oTransDisp.BeginTransaction
One interesting difference between the DTC External and Internal Transactions is that when you abort Receiving a message with a DTC External Transaction, the message is left intact in the queue. With Internal Transactions, the message is purged and moved to the Xact Dead Letter queue.
One of the nice features of MSMQ is its implicit coordination with Microsoft® Transaction Server to handle transactions. As you may know, MTS is a runtime environment used to handle robust distributed transactions. Visual FoxPro works very well with MTS, and with Visual FoxPro 6.0 SP3, you can achieve highly scalable applications using new multi-threaded DLL servers. When the application is running in the MTS environment, MSMQ can use the current MTS transaction if one is available.
Programming for MTS Transactions requires using an MTS context object to obtain and make transactional calls. Again, you can control transactions in MSMQ for both sending and receiving messages. Unlike the explicit types shown above, the Send and Receive calls do not take a Transaction object as parameter, but rather a constant (MQ_MTS_TRANSACTION).
oMsg.Send(oSendQueue, MQ_MTS_TRANSACTION) oMsg = oRecQueue.ReceiveCurrent(MQ_MTS_TRANSACTION,,,100)
In your MTS server component, you can control Commit/Abort operations by using the SetAbort and SetComplete methods of the MTS context object. Below is pseudo-sample of how your MTS server might be structured to handle an MSMQ message in a transaction.
* Your MTS server oMTX = Create(“MTXAS.APPSERVER.1”) oContext = oMTX.GetObjectContext() * Create new message and send it to queue oSendQueue = oQueueInfo.Open(MQ_SEND_ACCESS, MQ_DENY_NONE) oMsg = create(“msmq.msmqmessage”) oMsg.Label = “My MTS Message” oMsg.Body = “This is a MTS message.” oMsg.Send(oSendQueue, MQ_MTS_TRANSACTION) oSendQueue.Close * Do some other stuff here which may not cause abort action IF lHadSomeError oContext.SetAbort ELSE oContext.SetComplete ENDIF
Transaction Programming Considerations
If some operations in a transaction fail, it is the application’s responsibility to decide whether to terminate the entire transaction (by calling the Transaction object’s abort member function) or commit the transaction anyway (if the failures are such that the transaction is still viable). If the application does commit to a transaction where some operations have failed, the failed operations will not be part of the transaction.
There is no limit to the number of messages sent, the number of messages retrieved, or the number of queues used in a single transaction. However, an application cannot send a message to a queue and then try to retrieve it during the same transaction.
Calling MQSendMessage does not actually send the message within the transaction. The actual sending is done at some time after MS DTC commits the transaction. When MS DTC returns a successful commit return value, the sending application is guaranteed that the message will be sent. If a transaction is aborted, all MSMQ transaction operations are rolled back: no messages are sent, and all retrieved messages are returned to their original place in the queue.
MSMQ guarantees exactly-once-delivery. This means that all messages sent to a queue will arrive once and only once. MSMQ takes special measures to prevent any message duplication or loss.
MSMQ guarantees that all messages sent to a specific transaction queue will arrive in the order they were sent by the transaction. This means that if transaction T1 sends messages M1 and M2 to queue Q1, M1 will arrive before M2. However, there is no guarantee if two transactions are sending messages to the same queue. If transaction T1 sends messages M1 and M2 to Q1, and a second transaction T2 sends messages M3 and M4 to Q1, MSMQ only guarantees that M1 will arrive before M2, and that M3 will arrive before M4. In order to guarantee that M1 and M2 will arrive before M3 and M4, the application must commit to T2 only after getting a successful return code from T1.
MSMQ does not guarantee order of delivery to different queues, nor does it guarantee order of delivery from different computers.
Message authentication allows the receiving application to verify the source of a message and that the message was not modified on its way to the queue. This is done by attaching a digital signature to the message when it is sent, then verifying the digital signature when the message reaches the queue. The receiving MSMQ Queue Manager uses the digital signature to verify the sender and that the message was not modified.
To digitally sign a message, the sending application uses a public and private signing key pair to create the digital signature. MSMQ provides the key pair when an internal security certificate is used or when an external security certificate is used. External certificates are obtained from a certificate authority (CA).
When an internal security certificate is used, the private signing key is registered the first time that the MSMQ Control Panel application is run. The public signing key is provided within the internal certificate.
Internal certificates are used when the receiving application needs to validate the sender identifier attached to a message. When using an internal certificate, only the sender identifier is guaranteed correct.
External certificates are used when you want to use the information in the certificate (not just the sender identifier sent with the message) to verify the source of a message. The information in the external certificate is guaranteed by the certificate authority that created the certificate.
MSMQ does not validate an external certificate. The receiving application must validate the certificate before using an authenticated message. MSMQ generates the digital signature of a message when it is sent and verifies the digital signature when the message is received, but does not validate the certificate itself.
External certificates are required when communicating with operating environments other than Windows NT® where the sender identifier is meaningless.
As you no doubt have seen throughout the article, MSMQ offers endless opportunities for Visual FoxPro developers. MSMQ is a wonderful technology for use with Windows DNA applications where Visual FoxPro COM components can play an essential role in the middle tier.
The samples included with this article contain valuable code you can use in your own applications. You might consider creating reusable classes for common operations such as opening queues, sending and receiving messages. Additionally, it is also useful to have a handy set of tools for working with MSMQ. The following tools included with the samples are invaluable in helping to develop and debug your MSMQ applications:
MSGDISPATCH.SCX - lets you send a message to a queue. The form allows you to send normal text messages, but also lets you drag and drop Office documents (e.g., DOC, XLS and PPT files) onto the body area to have them attached to the message. Messages can be sent to both private and public queues. An admin queue can also be set up for message acknowledgments.
MSGREADER.SCX - while the MSMQ Explorer does not allow you to send messages to a queue, you can view them. Unfortunately, the body contents displayed in the dialog is not very usable, especially with non-text bodies. This form lets you view all sorts of message bodies including plain text, Office documents, ADO recordsets and XML from Rick Strahl’s wwwXML classes.
EVENTHANDLER.SCX - asynchronous events . The EventHandler uses the new VFPCOM utility to bind a Visual FoxPro object, which implements the same interface as the MSMQEvent class, to an MSMQ queue so that you can have Fox code executed when a message arrives in that queue.
All of the tools and samples are “smart” in detecting whether you are online or offline. If you are offline, then the tools will display Private queues to select.
Finally, more information on using MSMQ can be found at the following:
- Microsoft MSMQ Web site: www.microsoft.com/ntserver/appservice/exec/overview/MSMQ_Overview.asp - contains the best reference set for using MSMQ including samples, technical papers, and other available references.
- MSDN: http://msdn.microsoft.com/default.asp - lots of great articles on using MSMQ.
- Microsoft Systems Journal: www.microsoft.com/msj/default.asp - a number of useful MSMQ articles have appeared in MSJ over the past few years. Many of the samples are written in Visual Basic, which can easily be converted to Visual FoxPro syntax.
- Books - although slow in release, good books are starting to appear on the shelves of your local bookstore. You can search on some of the online book web sites for keywords including MSMQ, MTS, and Distributed Applications.