Adding the Bank Clerk

The premise is this — the Negotiator needs to ask the BankClerk, on behalf of the base client, whether the ransom amount is in the account. The observant amongst you will realize that we're really just taking the code from the TakeCashFromAcount() method out of Negotiator and putting it into the BankClerk. Why? To demonstrate how MTS objects share context ... and to show off the ATL MTS support.

The BankClerk object has the following interface:

Method Parameters Description
WithdrawCash [in] int amountNeeded,
[in] BSTR account,
[out, retal] VARIANT_BOOL* pIsCashThere
Find the account, check its balance against amountNeeded and fill out pIsCashThere accordingly

Because the BankClerk is a trustworthy sort, his word that the cash is there is quite enough for the Negotiator to keep working.

Let's insert this as a new ATL object into the TransNegotiator project, and this time, select the MS Transaction Server object. Fill out the class name as normal, but then look at the MTX tag:

I've selected the Support IObjectControl checkbox, which is sufficient. This adds the IObjectControl interface to the BankClerk object, and includes mtx.h automatically. I've highlighted the relevant bits of the header file:

// BankClerk.h : Declaration of the CBankClerk

...

#include <mtx.h>

//////////////////////////////////////////////////////////////////////// CBankClerk
class ATL_NO_VTABLE CBankClerk : 
   public CComObjectRootEx<CComSingleThreadModel>,
   public CComCoClass<CBankClerk, &CLSID_BankClerk>,
   public IObjectControl,
   public IDispatchImpl<IBankClerk, &IID_IBankClerk, &LIBID_TRANSNEGOTIATORLib>

...

// IObjectControl
public:
   STDMETHOD(Activate)();
   STDMETHOD_(BOOL, CanBePooled)();
   STDMETHOD_(void, Deactivate)();

   CComPtr<IObjectContext> m_spObjectContext;

...

The default implementations of the three methods of the interface look like this:

/////////////////////////////////////////////////////////////////////////
// CBankClerk

HRESULT CBankClerk::Activate()
{
   HRESULT hr = GetObjectContext(&m_spObjectContext);
   if (SUCCEEDED(hr))
      return S_OK;
   return hr;
} 

BOOL CBankClerk::CanBePooled()
{
   return FALSE;
} 

void CBankClerk::Deactivate()
{
   m_spObjectContext->Release();
} 

They'll do as they are. When the BankClerk object is created, the Activate() method is called first by the MTS environment. It gets the context object.

It's here that you'd call IObjectControl's DisableCommit() method, if you wanted the same MTS object to be used by a base client over multiple method calls.

CanBePooled() isn't implemented at the moment — the benefits of recycling object instances apparently aren't obvious, especially as resource managers like ODBC pool database connections, which are the time-consuming parts of object initialization.

One thing you'll need to include in the Project | Settings... is the library mtxguid.lib for the value of IID_IObjectControl.

Because we're using the code from the TakeCashFromAccount() method in BankClerk::WithdrawCash(), we'll need copies of the OpenDatabase(), FindRecordSet() and the three Log() functions, and we'll need the ADO includes and library.

Back in the Negotiator::TakeCashFromAccount() method add the following code:

HRESULT CNegotiator::TakeCashFromAccount(int amount, VARIANT_BOOL* pbFree)
{
   // Create the BankClerk object using the Context object

   CComPtr<IBankClerk> pBankClerk;
   HRESULT hResult = m_spObjectContext->CreateInstance (CLSID_BankClerk, IID_IBankClerk, reinterpret_cast<void**>(&pBankClerk));

   if (FAILED(hResult))
   {
      Log (hResult, "Creating Bank Clerk");
      return hResult;
   }

   // Ask the bank clerk to withdraw the cash

   hResult = pBankClerk->WithdrawCash(amount, m_bstrAccount, &pbFree);

   if (FAILED(hResult))
      Log (hResult, "Bank Clerk withdrawing cash");

   return hResult;
}

We create the BankClerk object using the CreateInstance() call, so that the Negotiator object's context information is passed down the line. A failure in either of the objects will result in both being rolled back.

We need to replace the components in the TransNegotiator package, set the Negotiator object to Requires a New Transaction and BankClerk to Requires a Transaction.... All should be ready for the recompiled Visual Basic client.

© 1998 by Wrox Press. All rights reserved.