Figure 3   Minimum RD Interfaces

//Types
typedef DWORD RESTYPID;

typedef DWORD RESID;

typedef LPOLESTR SRESID;

typedef LPCOLESTR constSRESID;

typedef DWORD RESOURCERATING;

typedef long TIMEINSECS;

typedef DWORD INSTID;

typedef DWORD TRANSID;


//Interfaces
IDispenserManager : public IUnknown
{
    HRESULT RegisterDispenser(
        const IDispenserDriver* pDispenserDriver,
        const WCHAR* szDispenserName,
        IHolder** ppIHolder);

    HRESULT GetContext(INSTID* pInstId,TRANSID* pTransId);

interface IHolder: public IUnknown
{
    HRESULT AllocResource(RESTYPID ResTypId,RESID* pResId);

    HRESULT FreeResource(const RESID ResId);

    HRESULT TrackResource(const RESID ResId);

    HRESULT TrackResourceS(const SRESID SResId);

    HRESULT UntrackResource(const RESID ResId,const BOOL fDestroy);

    HRESULT UntrackResourceS(const SRESID SResId, const BOOL fDestroy);

    HRESULT Close();

    HRESULT RequestDestroyResource(const RESID ResId);
}


interface IDispenserDriver : public IUnknown
{
    HRESULT CreateResource(
        const RESTYPID ResTypId, 
        RESID* pResId, 
        TIMEINSECS* pSecsFreeBeforeDestroy);

    HRESULT RateResource(
        const RESTYPID ResTypId,
        const RESID ResId,
        const BOOL fRequiresTransactionEnlistment,
        RESOURCERATING* pRating);

    HRESULT EnlistResource(const RESID ResId,const TRANSID TransId);

    HRESULT ResetResource(const RESID ResId);

    HRESULT DestroyResource(const RESID ResId);
} 


Figure 4   CRDisp Class Declaration

/////////////////////////////////////////////////////////////////////////////////
//RDisp.h : Declaration of the CRDisp resource dispenser class.
//
//DESCRIPTION
// Declaration of C++ resource dispenser class. Implements IIDispenserDriver,
//IRDisp, and IRDispAdmin interfaces.
//
//
/////////////////////////////////////////////////////////////////////////////////

#ifndef __RDISP_H_
#define __RDISP_H_

// disable warning C4786: symbol greater than 255 characters
#pragma warning(disable: 4786)

#include "StdAfx.h"
#include "resource.h"       // main symbols
#include <map>
#include <fstream>
using namespace std;

#include "..\include\mtxdm.h"


//create a map of ResourceIDs and their status
typedef map<long, bool> ResourceMap;


//interface IDispenserDriver;
//interface IHolder;

//utility for releasing pointers
#define SafeRelease(pUnk) {if (pUnk){pUnk -> Release();pUnk = NULL; }}

// CRDisp
class ATL_NO_VTABLE CRDisp : 
    public IDispenserDriver,
    public IRDisp,
    public IRDispAdmin,
    public CComObjectRootEx<CComMultiThreadModel>,
    public CComCoClass<CRDisp, &CLSID_CoRDisp>
{
private:
    //data members
    IHolder * m_pHolder;
    IDispenserManager * m_pDispMan;
    IUnknown* m_pUnkMarshaler;
    ofstream m_fLog;
    long m_lResourceTimeout;

    //critical sections:
    //used to serialize access to instance data, namely ResourceMap
    CComAutoCriticalSection m_CS;

    //Set containing all resources
    ResourceMap m_ResourceMap;

public:
    const static RESTYPID m_DefaultResourceTypeID;
    const static long m_lDefaultTimeout;

//must be singleton
DECLARE_CLASSFACTORY_SINGLETON(CRDisp);
DECLARE_PROTECT_FINAL_CONSTRUCT();
DECLARE_REGISTRY_RESOURCEID(IDR_RDISP);
DECLARE_NOT_AGGREGATABLE(CRDisp);

    //const/dest
    CRDisp();
    ~CRDisp();
    HRESULT FinalConstruct();
    void FinalRelease();

//COM interface map
BEGIN_COM_MAP(CRDisp)
    COM_INTERFACE_ENTRY(IRDisp)
    COM_INTERFACE_ENTRY(IRDispAdmin)
    COM_INTERFACE_ENTRY(IDispenserDriver)
    COM_INTERFACE_ENTRY_AGGREGATE(IID_IMarshal, m_pUnkMarshaler)
END_COM_MAP()

    //IRDisp
    STDMETHODIMP Connect(BSTR strConnectInfo, long* lResourceID);
    STDMETHODIMP Disconnect(long lResourceID);
    STDMETHODIMP ScheduleJob(long lResourceID, long lJobID);
    STDMETHODIMP CancelJob(long lResourceID, long lJobID);

    //IRDispAdmin
    STDMETHODIMP DestroyInactive(void);
    STDMETHODIMP SetTimeout(long lTimeout);
    STDMETHODIMP GetInventoryStatus( long* lSize, long* lInUse);

    //IDipenserDriver
    STDMETHODIMP CreateResource(const RESTYPID ResourceTypeID,
                                RESID* pResourceID,
                                TIMEINSECS* pSecsFreeBeforeDestroy);

    STDMETHODIMP RateResource(const RESTYPID ResourceTypeID,
                              const RESID ResourceID,
                              const BOOL fRequiresTransactionEnlistment,
                              RESOURCERATING* pRating);

    STDMETHODIMP EnlistResource(const RESID ResourceID, const TRANSID TransID);
    STDMETHODIMP ResetResource(const RESID ResourceID);
    STDMETHODIMP DestroyResource(const RESID ResourceID);
    STDMETHODIMP DestroyResourceS(const SRESID sResourceID);
};

#endif //__RDISP_H_


Figure 5   CRDisp Class, IDispenserDriver Interface Implementation

/////////////////////////////////////////////////////////////////////////////////
//CRDisp::CreateResource(), implements IDispenserDriver::CreateResource()
//
//DESCRIPTION:
//Called by the dispenser manager to create brand new resource.
//
STDMETHODIMP CRDisp::CreateResource (
    /*[in]*/  const RESTYPID ResourceTypeID,    
    /*[out]*/ RESID* pResourceID, 
    /*[out]*/ TIMEINSECS* pSecsFreeBeforeDestroy)
{
    if (ResourceTypeID != m_DefaultResourceTypeID)
    {
        //invalid Resource Type requested, return E_FAIL
        return E_FAIL;
    };

    //
    //get the resource manager pointer, no-op for us since we use DLL
    //

    //create new resource
    bool bSuccess = RM_Connect("RDisp Disp Manager", (long*)pResourceID);

    //set timeout
    *pSecsFreeBeforeDestroy = (TIMEINSECS)m_lResourceTimeout;

    //lock the CS
    m_CS.Lock();

    //Add the resourceID into map and mark it as 'active'
    m_ResourceMap[*pResourceID] = true;

    ATLTRACE(_T("CRDisp::CreateResource()...creating new resource.\n"));
    m_fLog << "CRDisp::CreateResource()...resource :" << (long)*pResourceID 
        << " created."<< endl;
    
    //release lock and return
    m_CS.Unlock();
    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////////
//CRDisp::RateResource(), implements IDispenserDriver::RateResource()
//
//DESCRIPTION:
//Called by the dispenser manager to rate an already created resource.
//
STDMETHODIMP CRDisp::RateResource(    
    /*[in]*/  const RESTYPID ResourceTypeID,
    /*[in]*/  const RESID ResourceID,
    /*[in]*/  const BOOL fRequiresTransactionEnlistment,
    /*[out]*/ RESOURCERATING* pRating)
{
    ATLTRACE(_T("CRDisp::RateResource()...Rating resource %ld.\n"),  
             (long)ResourceID);

    //init
    *pRating = 0;

    //first, make sure we have the correct RESTYPID
    if(ResourceTypeID != m_DefaultResourceTypeID)
    {
        //unusable resource, Resource Type mismatch
        *pRating = 0;
        return S_OK;
    };

    if (!fRequiresTransactionEnlistment)
    {
        //if already enlisted on the right transaction, and has the same
        //IP address, assign perfect rating

        *pRating = 100;            
    }
    else
    {
        // not enlisted, but exists
        *pRating = 25;
    }

    return S_OK;
}

//////////////////////////////////////////////////////////////////////////////////
//CRDisp::EnlistResource(), implements IDispenserDriver::EnlistResource()
//
//DESCRIPTION:
//Called by the dispenser manager when attempting to enlist new resource in the 
//client's transaction. 
//The same happens when the resource from general inventory is used.
//
STDMETHODIMP CRDisp::EnlistResource(
    /*[in]*/  const RESID ResourceID,
    /*[in]*/  const TRANSID TransId)
{
    ATLTRACE(_T("CRDisp::EnlistResource()...Enlisting Resource%ld.\n"), 
             (long)ResourceID );
    m_fLog << "CRDisp::EnlistResource()...Enlisting resource: " 
       << (long)ResourceID << endl;

    //this is non-transactional ResDisp, return S_FALSE
    return S_FALSE;
}

/////////////////////////////////////////////////////////////////////////////////
//CRDisp::ResetResource(),implements IDispenserDriver::ResetResource()
//
//DESCRIPTION:
//Called by the dispenser manager to reset the resouce before returning it to the 	
//pool.
//
STDMETHODIMP CRDisp::ResetResource(/*[in]*/  const RESID ResourceID)
{
    ATLTRACE(_T("CRDisp::ResetResource()...Resetting resource: %ld.\n"),  ResourceID );
    m_fLog << "CRDisp::ResetResource()...Resetting resource: " 
        << (long)ResourceID << endl;

    //
    //get the resource manager pointer, no-op for us since we use DLL
    //

    //reset the connection
    bool bSuccess = RM_ResetConnection(ResourceID);

    if (bSuccess)
    {
        m_CS.Lock();

        m_ResourceMap[ResourceID] = false;

        m_CS.Unlock();
        return S_OK;
    }
    else
    {
        return S_FALSE;
    };
}

/////////////////////////////////////////////////////////////////////////////////
//CRDisp::DestroyResource(), implements IDispenserDriver::DestroyResource().
//
//DESCRIPTION:
//Called by the dispenser manager when destroying the resource.
//
STDMETHODIMP CRDisp::DestroyResource(/*[in]*/  const RESID ResourceID)
{
    HRESULT hr;

    ATLTRACE(_T("CRDisp::DestroyResource()...Destroying resource: %ld.\n"),  (long)ResourceID);
    m_fLog << "CRDisp::DestroyResource()...Destroying resource:" 
        << (long)ResourceID << endl;

    //get the RM pointer, no-op for us since we use DLL

    //instruct RM to close the connection:
    if (RM_Disconnect((long)ResourceID) )
    {
        //make sure that no other thread is accessing instance data
        m_CS.Lock();

        //remove resourceID from the set of resources
        m_ResourceMap.erase(ResourceID);

        //unlock and release
        m_CS.Unlock();

        hr = S_OK;
    }
    else
    {
        hr = S_FALSE;
    };

    return hr;
}

//////////////////////////////////////////////////////////////////////////////////
//CRDisp::DestroyResourceS(), implements IDispenserDriver::DestroyResourceS().
//
//DESCRIPTION:
//This method behaves exactly the same as DestroyResource() except it uses string 
//representation of SRESID. Since we do not use SRESIDs, return E_NOTIMPL.
//
STDMETHODIMP CRDisp::DestroyResourceS(/*[in]*/  const SRESID sResourceID)
{
    ATLTRACE(_T("CRDisp::DestroyResourceS()\n"));
    m_fLog << "CRDisp::DestroyResourceS()" << endl;

    return E_NOTIMPL;
}


Figure 6   CRDisp Class, IRDisp Interface Implementation

/////////////////////////////////////////////////////////////////////////////////
//CRDisp::Connect(), implements IRDisp::Connect()
//
//DESCRIPTION:
//Called by clients to create new resource: connection to the scheduling system.
//
//
STDMETHODIMP CRDisp::Connect(BSTR strConnectInfo, long* lResourceID)
{
    HRESULT hr;

    //get the lock
    m_CS.Lock();

    ATLTRACE(_T("CRDisp::Connect()\n"));
    m_fLog << "CRDisp::Connect()" << endl;

    //init
    *lResourceID = 0;

    //call Holder to create resource:
    if (m_pDispMan)
    {
        if (!m_pHolder)
        {
            //no holder - return error
            ATLTRACE(_T("CRDisp::Connect(), no holder present.\n"));

            //release lock and return
            m_CS.Unlock();
            return E_INVALIDARG;
        };

        *lResourceID = NULL;
        hr = m_pHolder->AllocResource(m_DefaultResourceTypeID, 
                                      (RESID *)lResourceID);
        if(FAILED(hr))
        {
            ATLTRACE(_T("AllocResource failed! Error code %x\n"), hr);
        };
    }
    else
    {
        //No DispMan, hence no MTS, create the resource directly
        m_fLog << "CRDisp::Connect()...No DispMan" << endl;

        TIMEINSECS timeout = 0;
        hr = CreateResource(m_DefaultResourceTypeID,
                            (RESID*)lResourceID,&timeout);
    };

    //release lock and return
    m_CS.Unlock();
    return hr;
}

/////////////////////////////////////////////////////////////////////////////////
//CRDisp::Disconnect(), implements IRDisp::Disconnect()
//
//DESCRIPTION:
//
//
STDMETHODIMP CRDisp::Disconnect(long lResourceID)
{
    ATLTRACE(_T("CRDisp::Disonnect()\n"));
    m_fLog << "CRDisp::Disonnect()...resource ID: " << lResourceID << endl;

    HRESULT hr;    

    //aquire a lock
    m_CS.Lock();

    if (m_pDispMan)
    {
        if (!m_pHolder)
        {
            ATLTRACE(_T("CRDisp::Disconnect(), no holder present.\n"));
            m_fLog << "CRDisp::no holder present." << endl;

            //release lock
            m_CS.Unlock();
            return E_INVALIDARG;
        };
        
        if (m_pDispMan)
        {
            hr = m_pHolder->FreeResource(lResourceID);

            if (!SUCCEEDED(hr))
            {
                ATLTRACE(_T("FreeResource failed with error %x\n"), hr);
                m_fLog << "FreeResource failed. " << hr << endl;
            };
        }
    }
    else
    {
        //No DispMan, i.e. no MTS, therefore destroy resource directly
        m_fLog << "CRDisp::no DispMan present." << endl;

        hr = DestroyResource(lResourceID);
    };

    //release lock
    m_CS.Unlock();    
    return hr;
}

//////////////////////////////////////////////////////////////////////////////////
//CRDisp::ScheduleJob(), implements IRDisp::ScheduleJob()
//
//DESCRIPTION:
//Performs actual task of adding a new job from the scheduling subsystem.
//
//
STDMETHODIMP CRDisp::ScheduleJob(/*[in]*/ long lResourceID, /*[in]*/ long lJobID)
{
    //
    //retrieve the RM pointer, no-op since we use RM as DLL
    //

    bool bSuccess = RM_ScheduleJob(lResourceID, lJobID);

    if (bSuccess)
    {
        return S_OK;
    }
    else
    {
        return S_FALSE;
    };
}

//////////////////////////////////////////////////////////////////////////////////
//CRDisp::CancelJob(), implements IRDisp::CancelJob()
//
//DESCRIPTION:
//Performs actual task of canceling a job from the scheduling subsystem.
//
//
STDMETHODIMP CRDisp::CancelJob(/*[in]*/ long lResourceID, /*[in]*/ long lJobID)
{
    //
    //retrieve the RM pointer, no-op since we use RM as DLL
    //

    bool bSuccess = RM_CancelJob(lResourceID, lJobID);

    if (bSuccess)
    {
        return S_OK;
    }
    else
    {
        return S_FALSE;
    };
}


Figure 7   RD::Connect Pseudocode

RD::Connect(/*IN*/ BSTR strConnectionString, /*out*/ long lResourceID)
{
    HRESULT hr;

    //Lock critical section to protect the instance data
    m_CS.Lock();

ATLTRACE(_T("in CRDisp::Connect()\n"));

    //reset resource ID
    *lResourceID = 0;

RESTYPID myResourceType;

//decide what resource type this connection represents and set myResourceType 	
//value accordingly

    //If there is a dispenser manager present (i.e. we are running under MTS)
    //must use holder object to allocate the inventory. 
    if (m_pDispMan)
    {
//If no holder object, refuse to allocate resource
//because this happens only when the RD is getting ready to shut down.
        if (!m_pHolder)
        {
            //release lock and return
            m_CS.Unlock();
            return E_INVALIDARG;
        }
        else
{
    //call Holder
        hr = m_pHolder->AllocResource(myResourceType, (RESID*)lResourceID);
        if (!SUCCEEDED(hr))
        {
            //error
ATLTRACE(_T("Failed to allocate resource, error: %x\n"), hr);
        };
    };
    }
    else
    {
        //not running under MTS: create the resource directly:
        //set timeout
        TIMEINSECS timeout;
        //call CreateResource() method
        hr = CreateResource(myResourceType,(RESID*)lResourceID, &Timeout);
    };

    //release lock and return
    m_CS.Unlock();
    return hr;
}


Figure 8   CRDisp Class, IRDispAdmin Interface Implementation

/////////////////////////////////////////////////////////////////////////////////
//CRDisp::GetInventoryStatus(), implements IRDispAdmin::GetInventoryStatus().
//
//DESCRIPTION:
//Walks through the map of all allocated resources and returns the map size,
//and number of active resources, i.e.:
//lSize = lInUse + (general and enlisted inventory)
//
STDMETHODIMP CRDisp::GetInventoryStatus(/*[out]*/ long* lSize, 
                                        /*[out]*/ long* lInUse)
{
    ATLTRACE(_T("CRDisp::GetInventoryStatus()\n"));
    m_fLog << "CRDisp::GetInventoryStatus()" << endl;

    //initialize
    *lSize = 0;
    *lInUse = 0;

    //get the CS lock so that no other thread starts inserting, or 
    //removing elements while we're using the map.
    m_CS.Lock();

    ResourceMap::iterator it;

    for (it = m_ResourceMap.begin(); it != m_ResourceMap.end(); ++it)
    {
        (*lSize)++; 
        if (it->second) (*lInUse)++;
    };

    //release lock and return
    m_CS.Unlock();

    return S_OK;
}

/////////////////////////////////////////////////////////////////////////////////
//CRDisp::DestroyInactive(...), implements IRDispAdmin::DestroyInactive().
//
//DESCRIPTION:
//Releases holders general and enlisted inventory.
//
STDMETHODIMP CRDisp::DestroyInactive(void)
{
    HRESULT hr;

    if(m_pDispMan)
    {
        if(!m_pHolder)
        {
            //no holder, therefore no pool
            ATLTRACE(_T("CRDisp::DestroyInactive(), no holder present.\n"));
            return E_FAIL;
        };
    }
    else
    {
        //no dispenser manager, therefore no holder and no pool
ATLTRACE(_T("CRDisp::DestroyInactive(), no DispMan present.\n"));

        //release lock and return
        return E_FAIL;
    };
    
    //lock cs
    m_CS.Lock();

    ATLTRACE(_T("CRDisp::DestroyInactive()\n"));
    m_fLog << "CRDisp::DestroyInactive()" << endl;

    //Walk through the resource map and identify the resource to be deleted.
    //Note that we need to do this in two steps since the RequestDestroyResource()
    //will call CRDisp::DestroyResource(), which immediately removes the resource id
    //from the map.

    //define the set
    set <long> InactiveSet;

    ResourceMap::iterator it1;

    for (it1 = m_ResourceMap.begin(); it1 != m_ResourceMap.end(); ++it1)
    {
        if (it1->second == false)
        {
            //inactive resource, add it to the rest of inactive resources
            InactiveSet.insert(it1->first);
        };
    };

    //now actually delete the resources
    set<long>::iterator it2;

    for (it2 = InactiveSet.begin(); it2 != InactiveSet.end(); ++it2)
    {
        hr = m_pHolder->RequestDestroyResource((RESID)*it2);
        if (!SUCCEEDED(hr))
        {
            m_fLog << "Cound not destroy resource: " << *it2 << endl;
        };
    };

    //release lock and return
    m_CS.Unlock();
    return hr;
}

//////////////////////////////////////////////////////////////////////////////////
//CRDisp::SetTimeout(...)
//
//DESCRIPTION:
//Sets timeout for how long all new resources can 'hang out' in general and 
//enlisted inventory before being destroyed.
//
STDMETHODIMP CRDisp::SetTimeout(long lTimeout)
{
    ATLTRACE(_T("CRDisp::SetTimeout()...Setting resource timeout to: %ld.\n"), lTimeout);
    m_fLog << "CRDisp::SetTimeout()......Setting resource timeout to: "
        << lTimeout << endl;

    if ( lTimeout < 0)
    {
        return E_INVALIDARG;
    }
    else
    {
        m_lResourceTimeout = lTimeout;
        return S_OK;
    };
}


Figure 9   CRDisp Class Implementation: Constructors, Destructors

#include "stdafx.h"
#include <initguid.h>
#include <cstdlib>
#include <ctime>
#include <set>

#include "ResourceDisp.h"
#include "RDisp.h"

//Resource Manager
#include "..\include\RM.h"

//include holder, and dispenser driver definitions
#include "..\include\mtxdm_i.c" //shipped with MTS SDK


//Set default Resource Type ID
const RESTYPID CRDisp::m_DefaultResourceTypeID = (RESTYPID)123;

//Set default resource timeout to 30 sec.
const long CRDisp::m_lDefaultTimeout = 30;

//constructors/destructors
CRDisp::CRDisp()
{
    //reset all instance variables
    m_pDispMan = NULL;
    m_pHolder = NULL;
    m_pUnkMarshaler = NULL;
}

CRDisp::~CRDisp(){}

/////////////////////////////////////////////////////////////////////////////////
//HRESULT CRDisp::FinalConstruct(...)
//
//DESCRIPTION:
//
HRESULT CRDisp::FinalConstruct()
{
    ATLTRACE(_T("CRDisp::FinalConstruct()\n"));

    HRESULT hr;

    //obtain  pointer to dispenser manager (DispMan)
    hr = GetDispenserManager(&m_pDispMan);
    if (SUCCEEDED(hr))
    {
        //we're running under MTS, register resource and receive holder
        IDispenserDriver * pDriver;
        hr = GetUnknown()->QueryInterface(IID_IDispenserDriver, 
                                          (void **)&pDriver);
        if (SUCCEEDED(hr))
        {
            //Register and receive holder
            hr = m_pDispMan->RegisterDispenser(pDriver, L"RDisp Dispenser", &m_pHolder);
            if (FAILED(hr))
            {
                ATLTRACE(_T("Could not register with DispMan.\n"));
            };
        }
        else
        {
            ATLTRACE(_T("CQI for IDispenserDriver failed.\n"));
        };
    }
    else
    {
        //no DispMan, we are running outside of MTS
    };

    //
    // get a connection to RM, since we use DLL, no-op
    //

    //initialize instance variables
    m_ResourceMap.clear();
    m_lResourceTimeout = m_lDefaultTimeout;
    m_fLog.open("ResourceDisp.log");

    //aggregate the freethreaded marshaler
    hr = CoCreateFreeThreadedMarshaler(GetUnknown(), &m_pUnkMarshaler);

    return hr;
}

//////////////////////////////////////////////////////////////////////////////////
//void CRDisp::FinalRelease()
//
//DESCRIPTION:
//Clean-up before exiting.
//Note that we have to manually free all resource handles that were not released 	
//properly by calling .DestroyResource(). The DispMan will not do this
//automatically.
//
void CRDisp::FinalRelease()
{
    ATLTRACE(_T("CRDisp::FinalRelease()\n"));
    m_fLog << "CRDisp::FinalRelease()" << endl;

    HRESULT hr;

    //instruct holder to release remaining inventory:    
    while( !m_ResourceMap.empty() )
    {
        //note: don't use IHolder to remove unreleased resources, holder can 	        
//release only general, or enlisted inventory, and NOT
        //active resources.
        //Use direct DestroyResource() call instead
        hr = DestroyResource((RESID)(*m_ResourceMap.begin()).first);
    };

    //empty the set
    m_ResourceMap.clear();

    //now, release pointers
    SafeRelease(m_pHolder);
    SafeRelease(m_pDispMan);
    SafeRelease(m_pUnkMarshaler);
}