The Stub

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.

© 1998 by Wrox Press. All rights reserved.