Our first problem is that we haven’t any way of telling our object which server to connect to. If we were using straight DCOM, we’d set DCOMcnfg
accordingly. However, we can’t do that, so for ease of implementation, we’ll just add a second interface to our object (to avoid compromising the original IOrder
interface definition). We’ll call this interface IAsync
, and give it a single put_
property, Destination
. We’ll hold this in a CComBSTR
member variable, m_bstrDestination
, and while we’re at it, we’ll construct the queue pathname for this host as well, and hold this in m_bstrDestPath
. We’ll also add member variables for the local host name and queue pathname.
Here’s the state of Order.h
following our first round of changes:
class ATL_NO_VTABLE COrder :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass<COrder, &CLSID_Order>,
public IDispatchImpl<IOrder, &IID_IOrder, &LIBID_ASYNCORDERLib>,
public IDispatchImpl<IAsync, &IID_IAsync, &LIBID_ASYNCORDERLib>,
public IPersistStreamInitImpl<COrder>,
public CComControl<COrder>
{
private:
HRESULT MandatoryAlphabetic(/*[in]*/ BSTR bstrValue);
HRESULT OptionalAlphabetic(/*[in]*/ BSTR bstrValue);
HRESULT MandatoryAlphanumeric(/*[in]*/ BSTR bstrValue);
HRESULT OptionalAlphanumeric(/*[in]*/ BSTR bstrValue);
HRESULT MandatoryPrice(/*[in]*/ BSTR bstrValue);
void Write(/*[in]*/ IStorage* pStorage, /*[in]*/ LPOLESTR lpszField,
/*[in]*/ CComBSTR& bstr);
CComBSTR m_bstrCustomerID;
CComBSTR m_bstrArtist;
CComBSTR m_bstrTitle;
CComBSTR m_bstrLabel;
CComBSTR m_bstrPrice;
// Data members for source and destination machine and queue names
CComBSTR m_bstrSource;
CComBSTR m_bstrSourcePath;
CComBSTR m_bstrDestination;
CComBSTR m_bstrDestPath;
// SendOrder() sends the Order to a queue
HRESULT SendOrder();
public:
COrder()
{
TCHAR szMachine[MAX_COMPUTERNAME_LENGTH + 1] = {0};
ULONG length = MAX_COMPUTERNAME_LENGTH;
GetComputerName (szMachine, &length);
_tcslwr(szMachine);
m_bstrSource = szMachine;
m_bstrSourcePath = m_bstrSource;
m_bstrSourcePath.Append ("\\async");
}
BEGIN_MSG_MAP(COrder)
END_MSG_MAP()
DECLARE_REGISTRY_RESOURCEID(IDR_ORDER)
BEGIN_COM_MAP(COrder)
COM_INTERFACE_ENTRY(IOrder)
COM_INTERFACE_ENTRY(IAsync)
COM_INTERFACE_ENTRY2(IDispatch, IOrder)
COM_INTERFACE_ENTRY_IMPL_IID(IID_IPersistStream, IPersistStreamInit)
END_COM_MAP()
BEGIN_PROPERTY_MAP(COrder)
PROP_ENTRY("CustomerID", 1, CLSID_NULL)
PROP_ENTRY("Artist", 2, CLSID_NULL)
PROP_ENTRY("Title", 3, CLSID_NULL)
PROP_ENTRY("Label", 4, CLSID_NULL)
PROP_ENTRY("Price", 5, CLSID_NULL)
END_PROPERTY_MAP()
// IOrder
public:
STDMETHOD(put_CustomerID)(/*[in]*/ BSTR newVal);
STDMETHOD(get_CustomerID)(/*[out, retval]*/ BSTR *pVal);
STDMETHOD(put_Artist)(/*[in]*/ BSTR newVal);
STDMETHOD(get_Artist)(/*[out, retval]*/ BSTR *pVal);
STDMETHOD(put_Title)(/*[in]*/ BSTR newVal);
STDMETHOD(get_Title)(/*[out, retval]*/ BSTR *pVal);
STDMETHOD(put_Label)(/*[in]*/ BSTR newVal);
STDMETHOD(get_Label)(/*[out, retval]*/ BSTR *pVal);
STDMETHOD(put_Price)(/*[in]*/ BSTR newVal);
STDMETHOD(get_Price)(/*[out, retval]*/ BSTR *pVal);
STDMETHOD(Submit)();
// IAsync
public:
STDMETHOD(put_Destination)(/*[in]*/ BSTR newVal);
// IPersistStream
public:
STDMETHOD(GetSizeMax)(ULARGE_INTEGER* pcbSize)
{
// Return size of data to be written to stream in bytes
// ATL Version
pcbSize->QuadPart = sizeof(DWORD);
// User Data
pcbSize->QuadPart +=
SizeOfBSTR(m_bstrCustomerID) +
SizeOfBSTR(m_bstrArtist) +
SizeOfBSTR(m_bstrTitle) +
SizeOfBSTR(m_bstrLabel) +
SizeOfBSTR(m_bstrPrice);
return S_OK;
}
};
Here’s the new method to handle the Destination
property:
STDMETHODIMP COrder::put_Destination(BSTR newVal)
{
m_bstrDestPath = m_bstrDestination = newVal;
m_bstrDestPath.Append("\\async");
return S_OK;
}
Now, we’re going to change our old Submit()
method, so that – depending on whether it’s being invoked on the client or the stub – it either sends an MSMQ message, or writes away the order as it used to. This is what the new version looks like:
STDMETHODIMP COrder::Submit()
{
// If we have a destination machine,
// and it's different from the the machine on which the Order
// object is running,
// then we call SendOrder() to put the Order object in the queue
if (m_bstrDestination && wcsicmp(m_bstrDestination, m_bstrSource))
return SendOrder();
// Otherwise, save the order to file as before
CComPtr<IStorage> pStorage;
HRESULT hr = StgCreateDocfile(L"C:\\Order.dat",
STGM_SIMPLE| STGM_CREATE | STGM_READWRITE |
STGM_SHARE_EXCLUSIVE, 0, &pStorage);
if (FAILED(hr))
return hr;
Write(pStorage, L"CustomerID", m_bstrCustomerID);
Write(pStorage, L"Artist", m_bstrArtist);
Write(pStorage, L"Title", m_bstrTitle);
Write(pStorage, L"Label", m_bstrLabel);
Write(pStorage, L"Price", m_bstrPrice);
return S_OK;
}
And here’s the new SendOrder()
member function:
HRESULT COrder::SendOrder()
{
CComVariant vData(GetUnknown());
CComPtr<IMSMQQueueInfo> pInfo;
HRESULT hr = CoCreateInstance(CLSID_MSMQQueueInfo,
NULL,
CLSCTX_SERVER,
IID_IMSMQQueueInfo,
reinterpret_cast<void**>(&pInfo));
if (FAILED(hr))
return hr;
pInfo->put_PathName(m_bstrDestPath);
CComPtr<IMSMQQueue> pQueue;
hr = pInfo->Open(MQ_SEND_ACCESS, NULL, &pQueue);
if (FAILED(hr))
return hr;
CComPtr<IMSMQMessage> pMessage;
hr = CoCreateInstance(CLSID_MSMQMessage,
NULL,
CLSCTX_SERVER,
IID_IMSMQMessage,
reinterpret_cast<void**>(&pMessage));
if (FAILED(hr))
return hr;
WCHAR wszLabel[16];
swprintf(wszLabel, L"%d", m_sequence++);
CComBSTR bstrLabel(wszLabel);
pMessage->put_Label(bstrLabel);
pMessage->put_Body(vData);
// To set a time limit on reaching the destination queue
// uncomment the following line
// pMessage->put_MaxTimeToReachQueue(30);
// To set a time limit on message being received,
// uncomment the following line
// pMessage->put_MaxTimeToReceive(30);
pInfo->put_PathName(m_bstrSourcePath);
pInfo->Refresh();
pMessage->putref_ResponseQueueInfo(pInfo);
hr = pMessage->Send(pQueue, &CComVariant());
return hr;
}
Now this is really cool. All the MSMQ stuff should look pretty familiar, except for one thing. Take a good look at what we’re actually sending in the message. It’s an IUnknown
pointer to ourselves. Yes, MSMQ messages can actually send objects over the wire! But hang on a minute – what does that mean? If you think back to what we were doing in Chapter 3, you might have guessed by now that it’s actually the persistent state of the object that we’re talking about here. So how does MSMQ get hold of that? The answer is our old friend IPersistStream
, which is the reason that we based this code on the SerialOrder
code from Chapter 3.
Does this really work? Let’s try it out. Obviously we won’t have an application to receive the messages (because we haven’t built that part yet), but we can take a look at the message using MSMQ Explorer. We’ll need a client though, so we’ll make yet another clone of our VB OrderEntry
project from Chapter 3 — this time called AsyncOrderEntry
. The only change we need to make, apart from changing the reference to use the AsyncOrder
type library, is this:
Private Sub cmdConfirm_Click()
...
Dim async As IAsync
Set async = m_order
async.Destination = "dipsy"
m_order.Submit
Unload Me
End Sub
We just use the new IAsync
interface to set up our destination host before calling Submit()
. OK, let’s fire it up and see what MSMQ Explorer tells us. You should see something like this:
This looks pretty much what I entered in the order fields, prefaced by something that looks suspiciously like the Class ID for the Order
object. Let’s hope we can persuade the other end to understand this.