So what sort of thing is our stub process? Well, it’s going to spend most of its time sitting waiting for messages to come through. But wait one moment – up to now, we’ve polled our queues for messages. Do we really want our stub process thrashing around in a permanent loop? I don’t think so. You can use MSMQ events to get notified whenever something happens but that entails revisiting connection
points once more. If we go way back to Chapter 1, you might recall that connection points are pretty cool in VB, so-so in ATL, but pretty terrible in MFC so I think we'll duck out of MFC and use ATL to develop our stub instead
We’ll create our stub as an executable ATL project. We need to define two objects: OrderStub
(sole interface IOrderStub
) and StubSink
(sole interface IStubSink
). The first of these is going to do all the set-up work, and the second is going to be hooked up to the MSMQ event connection point. The second one will be the one that handles all the incoming messages. How do we get started, then? We don’t really want to have another client that only exists to kick the whole thing off, so let’s make a small addition to our WinMain
function to create an instance of our OrderStub
object without client intervention:
if (bRun)
{
hRes = _Module.RegisterClassObjects(CLSCTX_LOCAL_SERVER,
REGCLS_MULTIPLEUSE);
_ASSERTE(SUCCEEDED(hRes));
CComObject<COrderStub>* pOrderStub;
CComObject<COrderStub>::CreateInstance(&pOrderStub);
hRes = pOrderStub->Initialize();
_ASSERTE(SUCCEEDED(hRes));
MSG msg;
while (GetMessage(&msg, 0, 0, 0))
DispatchMessage(&msg);
pOrderStub->Uninitialize();
pOrderStub->Release();
_Module.RevokeClassObjects();
}
OK, let’s take a look at the definition of our IOrderStub
interface. We’ve got three methods, two of which we’ve just seen in use:
Method | Parameters | Description |
Initialize | None | Initialize stub; hook up to connection point of local queue |
Uninitialize | None | Uninitialize stub; unhook from connection point |
Enable | None | Enable events on queue |
Here’s the code that implements this interface:
// OrderStub.cpp : Implementation of COrderStub
#include "stdafx.h"
#include "AsyncOrderStub.h"
#include "OrderStub.h"
#include "StubSink.h"
/////////////////////////////////////////////////////////////////////////////
// COrderStub
STDMETHODIMP COrderStub::Initialize()
{
// Create queue info object
CComPtr<IMSMQQueueInfo> pInfo;
HRESULT hr = CoCreateInstance(CLSID_MSMQQueueInfo,
NULL,
CLSCTX_INPROC_SERVER,
IID_IMSMQQueueInfo,
reinterpret_cast<void**>(&pInfo));
if (FAILED(hr))
return hr;
// Work out name of local queue, and populate info object
TCHAR szMachine[MAX_COMPUTERNAME_LENGTH + 1];
ULONG length = MAX_COMPUTERNAME_LENGTH;
GetComputerName(szMachine, &length);
_tcslwr(szMachine);
CComBSTR bstrPath = szMachine;
bstrPath.Append("\\async");
pInfo->put_PathName(bstrPath);
// Open up queue
hr = pInfo->Open(MQ_RECEIVE_ACCESS, MQ_DENY_NONE, &m_pQueue);
if (FAILED(hr))
return hr;
// Create event object
hr = CoCreateInstance(CLSID_MSMQEvent,
NULL,
CLSCTX_INPROC_SERVER,
IID_IMSMQEvent,
reinterpret_cast<void**>(&m_pEvent));
if (FAILED(hr))
return hr;
// Create sink object
CComObject<CStubSink>* pSink = NULL;
hr = CComObject<CStubSink>::CreateInstance(&pSink);
if (FAILED(hr))
return hr;
pSink->put_OrderStub(this);
hr = pSink->QueryInterface(IID_IDispatch,
reinterpret_cast<void**>(&m_pDispatch));
if (FAILED(hr))
return hr;
// Attach to connection point of event object
hr = AtlAdvise(m_pEvent, m_pDispatch, DIID__DMSMQEventEvents,
&m_dwCookie);
if (FAILED(hr))
return hr;
return Enable();
}
STDMETHODIMP COrderStub::Uninitialize()
{
AtlUnadvise(m_pDispatch, DIID__DMSMQEventEvents, m_dwCookie);
return S_OK;
}
STDMETHODIMP COrderStub::Enable()
{
// Enable notification for queue using event object
CComVariant vtCursor(MQMSG_CURRENT, VT_I4);
CComVariant vtTimeout(100000L);
HRESULT hr = m_pQueue->EnableNotification(m_pEvent, &vtCursor,
&vtTimeout);
if (hr == MQ_INFORMATION_OPERATION_PENDING)
hr = S_OK;
return hr;
}
The code for opening the MSMQ queue should look familiar to you from earlier examples in this chapter. The connection point stuff should be familiar from Chapter 1. Notice that we pass a pointer to ourselves into the connection point; that’s so it can get access to the Enable()
method. This is because every time an event fires, the event object has to be re-enabled. Notice also that this is all very much in the usual MSMQ style: create two objects, one for the queue and one for the event, and then invoke a method on one of them to tie them together.
In this code, we’ve referenced one or two member variables, so we need to make some small changes to OrderStub.h
:
class ATL_NO_VTABLE COrderStub :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<COrderStub, &CLSID_OrderStub>,
public IDispatchImpl<IOrderStub, &IID_IOrderStub,
&LIBID_ASYNCORDERSTUBLib>
{
private:
CComPtr<IMSMQQueue> m_pQueue;
CComPtr<IMSMQEvent> m_pEvent;
CComPtr<IDispatch> m_pDispatch;
DWORD m_dwCookie;
public:
COrderStub()
{
}
DECLARE_REGISTRY_RESOURCEID(IDR_ORDERSTUB)
BEGIN_COM_MAP(COrderStub)
COM_INTERFACE_ENTRY(IOrderStub)
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
// IOrderStub
public:
STDMETHOD(Initialize)();
STDMETHOD(Uninitialize)();
STDMETHOD(Enable)();
};
OK, that’s our main stub object completed. But what about our sink? We need to implement an interface that looks pretty much identical as the outgoing one from the MSMQ event object’s connection point, and then do a little fiddle with the COM map to re-direct incoming methods on the connection point’s interface onto our implementation.
This is the interface that we’re going to have to implement:
Method | Parameters | Description |
Arrived | [in] IDispatch* Queue, [in] long Cursor | There is a message in the queue |
ArrivedError | [in] IDispatch* Queue, [in] long ErrorCode, [in] long Cursor |
An error has occurred on the queue |
This – I hope – all makes perfect sense. It’s the kind of thing that you’d expect to see in an MSMQ event interface. I’ll also add one property:
Property | Type | Description |
OrderStub | IOrderStub* | Pointer back to main object |
Let’s take a look at what our StubSink.h
header file is going to look like:
// StubSink.h : Declaration of the CStubSink
#ifndef __STUBSINK_H_
#define __STUBSINK_H_
#include "resource.h" // main symbols
#include "mqoai.h"
/////////////////////////////////////////////////////////////////////////////
// CStubSink
class ATL_NO_VTABLE CStubSink :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<CStubSink, &CLSID_StubSink>,
public IDispatchImpl<IStubSink, &IID_IStubSink, &LIBID_ASYNCORDERSTUBLib>
{
private:
CComPtr<IOrderStub> m_pOrderStub;
public:
CStubSink()
{
}
DECLARE_REGISTRY_RESOURCEID(IDR_STUBSINK)
BEGIN_COM_MAP(CStubSink)
COM_INTERFACE_ENTRY_IID(DIID__DMSMQEventEvents,IStubSink)
COM_INTERFACE_ENTRY(IStubSink)
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
// IStubSink
public:
STDMETHOD(put_OrderStub)(/*[in]*/ IOrderStub* newVal);
STDMETHOD(Arrived)(IDispatch* Queue, long Cursor);
STDMETHOD(ArrivedError)(IDispatch* Queue, long ErrorCode, long Cursor);
};
#endif //__STUBSINK_H_
I’ve added one uncontroversial member variable, m_pOrderStub
, which holds our local copy of the OrderStub
property. I’ve also added that little fiddle to the COM map. Now, there’s an important change to the IDL that we’re going to have to make in order for this to work:
In order for COM_INTERFACE_ENTRY_IID to map one interface to another, you must make sure that the DISPIDs in both interfaces are identical with each other
The reason why I’m making a song and dance about this is that the default DISPIDs provided by the ATL Object Wizard (which start from 1) do not match those used by the MSMQ event connection point (which start from 0). As you may have guessed, I’ve been stung here myself! So let’s make that change to our IDL:
interface IStubSink : IDispatch
{
[id(0), helpstring("method Arrived")]
HRESULT Arrived([in] IDispatch* Queue, [in] long Cursor);
[id(1), helpstring("method ArrivedError")]
HRESULT ArrivedError([in] IDispatch* Queue,
[in] long ErrorCode, [in] long Cursor);
[propput, id(2), helpstring("property OrderStub")]
HRESULT OrderStub([in] IOrderStub* newVal);
};
OK, we’re ready to implement IStubSink
. Here we go:
// StubSink.cpp : Implementation of CStubSink
#include "stdafx.h"
#include "mqoai.h"
#include "AsyncOrderStub.h"
#include "StubSink.h"
#include "..\AsyncOrder\AsyncOrder.h"
#include "..\AsyncOrder\AsyncOrder_i.c"
/////////////////////////////////////////////////////////////////////////////
// CStubSink
STDMETHODIMP CStubSink::Arrived(IDispatch* Queue, long Cursor)
{
// Get hold of pointer to queue object
CComPtr<IMSMQQueue> pQueue;
HRESULT hr = Queue->QueryInterface(IID_IMSMQQueue,
reinterpret_cast<void**>(&pQueue));
if (FAILED(hr))
return hr;
CComVariant vTimeout(100L);
CComVariant vFalse(false);
CComVariant vTrue(true);
// Get message from queue
CComPtr<IMSMQMessage> pMessage;
hr = pQueue->ReceiveCurrent(&vFalse, &vFalse, &vTrue, &vTimeout,
&pMessage);
if (FAILED(hr))
return hr;
// Extract object from message
CComVariant vData(static_cast<IUnknown*>(NULL));
hr = pMessage->get_Body(&vData);
if (FAILED(hr))
return hr;
// Get IOrder interface on new object
CComPtr<IOrder> pOrder;
hr = (vData.punkVal)->QueryInterface(IID_IOrder,
reinterpret_cast<void**>(&pOrder));
if (FAILED(hr))
return hr;
// Submit the order
hr = pOrder->Submit();
// Re-enable events
if (m_pOrderStub)
return m_pOrderStub->Enable();
return hr;
}
STDMETHODIMP CStubSink::ArrivedError(IDispatch* Queue, long ErrorCode,
long Cursor)
{
if (m_pOrderStub)
return m_pOrderStub->Enable();
return S_OK;
}
STDMETHODIMP CStubSink::put_OrderStub(IOrderStub* newVal)
{
m_pOrderStub = newVal;
return S_OK;
}
Now I don’t know about you, but I think that’s rather beautiful. Did you check out the blink-if-you-missed-it way in which we took the object off the queue? Just one call to get_Body()
, and there we have one instantiated, fully populated order object that we can go ahead and submit.
And that, ladies and gentlemen, is how to use MSMQ to implement one-way method calls.