Microsoft Message Queue Class
MSMQ is a Microsoft messaging technology that enables computers in an enterprise to send and receive messages to global queue objects that are registered with a directory service. OTL provides COtlMSMQ as a convenient wrapper for a queue and its configuration information. Using COtlMSMQ you can:
*Create a new queue.
*Open a queue for send or receive.
*Receive or send a message.
*Receive or send a COM object’s persistent state.
*Use DTC or MSMQ internal transactions for send or receive.
*Delete a queue.
To utilize OTL's MSMQ support, install Microsoft Message Queue server and client. You must also have MSMQ client and PEC (Primary-Enterprise-Controller) or PSC (Primary-Site-Controller) installed on an NT server at your site. Please see the relevant Microsoft MSMQ administration documentation for details on the installation process.
Creating a queue
Queues are created by setting the queue properties with CreateInfo() or SetInfo(), followed by a call to the Create() method. Queue properties are stored internally as an MSMQQueueInfo COM object, which COtlMSMQ maintains a pointer to internally. As mentioned above, you can either create these property settings before attempting to open or create a queue, or you can attach existing queue info using SetInfo(). Transactional queues can be created by passing TRUE as the first parameter to Create(). Queues are not transactional by default. A queue can be destroyed with Delete(). Once created, a queue’s transactional property cannot be changed, which means you cannot change a queue from transactional to non-transactional (and vice versa). The queue must be deleted and re-created. This is an MSMQ limitation. MSMQ Explorer can be used to determine if a queue is transactional by looking at its properties. Creating a queue does not open it for send or receive.
Opening a queue
Queues can be opened for send, receive, or peek using OpenForSend(), OpenForReceive(), OpenForPeek(). The current queue info, as set by SetInfo() or CreateInfo(), is used to determine which queue to open. It is also possible to search for queues matching certain criteria using FindQueue(), which returns a collection of MSMQQueueInfo objects. You can open any of those objects by passing the queue info to SetInfo(), and calling one of the Open() methods above. A queue must exist for the open methods to succeed. The following code shows how to create and open a queue for receive access:
HRESULT hr = q.CreateInfo(L".\\OTLMsmq", // path
L"OTL Test Queue", // label and type GUID
hr = q.Create(TRUE); // transactional queue
hr = q.OpenForReceive();
The preceding code creates a queue on the local hard drive called OTLMsmq. Only the computer name and the queue name are needed. You can use ".\\ " as a shortcut for the local computer name, or you can specify a remote name. This is not a file path. When the queue is created, it is added to the global directory maintained by the server. Private queues can also be created by specifying a queue name in this format: Computername\Private$\QueueName. See the MSMQ documentation for more information, in particular the limitations of private queues.
Receiving Messages
A queue that is open for receive can be read using ReceiveMessage(). ReceiveMessage() operates synchronously. It times out after a specified period or blocks infinitely until a message arrives. You can specify a transaction if you want. By default, no transaction is used. ReceiveMessage() returns a pointer to a message object.
ReceiveObject() is used to create a COM object and then load it with state from the queue. The object must be sent with SendObject(). The Msmq sample demonstrates sending objects across machines using COtlMSMQ. The following code waits for a user defined rectangle object to be sent to the queue. COtlMSMQ recreates the object and its state and then returns the requested interface pointer on the object:
// receive a rect object
CComPtr<IRectObj> pRect;
hr = q.ReceiveObject(IID_IRectObj, (void**)&pRect);
Notice that the format of ReceiveObject() is identical to QueryInterface().
Sending Messages to a Queue
You can write a queue that was opened with OpenForSend() using SendMessage(). If you want to send a message to many queues, create the message once using CreateMessage() and then use the SendMessage(IMSMQMessage*) method. You can specify a transaction for each individual Send operation. By default no transaction is used. To send a COM object to a queue, use the SendObject() method instead. The object must support IPersistStream. The following code creates a rectangle object based on user input from the keyboard for left, top, right, and bottom. The object is then sent to a queue using an internal transaction:
// create a rect object
CComPtr<IRectObj> pRect;
hr = pRect.CoCreateInstance(CLSID_RectObj);
// get the rect params so we have some known state to pass
long l =0, t = 0, r = 0, b = 0;
GetRectInput(&l, &t, &r, &b);
// set the rect object state
hr = pRect->SetRect(l,t,r,b);
// start an internal transaction
CComPtr<IMSMQTransaction> pTrans;
hr = q.BeginTransactionInternal(&pTrans);
// send the object
hr = q.SendObject(static_cast<IUnknown*>(pRect),
(BSTR)NULL, pTrans);
// send was OK, but receiver will not get it until commit
CComBSTR bstrPath;
printf("Object Sent to Queue %S\n", bstrPath );
printf("Press any key to Commit the transaction...");
// commit
hr = pTrans->Commit(&vtMissing, &vtMissing, &vtMissing);
Once sent, the object can be released. The object state and CLSID is held in the queue until a receiver reads the message.
You must release any interface pointers that you receive as [out] parameters from COtlMSMQ methods. Close the queue at the end of messaging by calling Close(). The destructor releases the current queue and queue info objects held internally.