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, |
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.