Writing Microsoft Transaction Server Resource Dispensers

Walter Oliver

Contents

What Is a Resource Dispenser?
Development Environment
Defining the Resource Dispenser Class
Initializing the Resource Dispenser
Writing a COM Interface or API for Your RM Proxy
Talking to DispMan Through IHolder
Implementing the IDispenserDriver
Releasing the Resource Dispenser
Implement the DLL EntryPoint and ExportsAppendix A: Threading and Marshalling Issues
Appendix B: Registry Entries

What Is a Resource Dispenser?

A resource dispenser (RD) provides the following services:

When to create a resource dispenser

Build an RD if your requirements include:

Writing a resource dispenser to pool application component objects is not recommended. Future versions of MTS will use the IobjectControl interface, which can be implemented by application components to achieve object pooling and recycling. Moreover, in those cases where there are a few component objects that can be shared globally, setting up a shared property for each object and using them through the Shared Property Manager may provide the desired result.

Resource dispensers running under MTS

When running under MTS, the Dispenser Manager can automate transactions and resource reclamation. When operating with MTS, your RD interacts with:

These relationships are:

Resource dispenser running independently from MTS

When running independently from MTS, your RD interacts with all but two of the MTS components: Dispenser Manager and Holder. This implies that the RD will continue to use MS DTC to provide transaction propagation but will not provide MTS pooling (the RD may provide its own pooling mechanism).

Return to Contents

Development Environment

There are many ways of setting up your development environment for writing a resource dispenser. Here is a set of tools and libraries that either are required or can simplify the job:

Return to Contents

Defining the Resource Dispenser Class

Define your class simply and easily by using the ATL Object Wizard in VC ++ and choosing the "simple object" option. The following class definition serves as a model for defining your resource dispenser class:

/////////////////////////////////////////////////////////////////////////////
// CResourceDispenser
class ATL_NO_VTABLE CRDisp : 
   public IDispenserDriver, 
   public CComObjectRootEx<CComMultiThreadModel>,
   public CComCoClass<CRDisp, &CLSID_ ResourceDispenser >,
   public IDispatchImpl<IRDisp, &IID_IResourceDispenser, &LIBID_RESDISPLib>
{
private:
   //
   // Member variables needed to make a resource dispenser
   //
   IGlobalInterfaceTable *      m_pGIT;
   DWORD         m_dwRmPtrCookie;
   IHolder *         m_pHolder;
   IDispenserManager *      m_pDispMan;
   // A map of Resource handles to export objects
   map<DWORD, ITransactionExport *> m_mapExport;


public:

//
// The resource dispenser must be a singleton object so that
// there is a unique IHolder which maintains the list of 
// resources to be pooled.
//
DECLARE_CLASSFACTORY_SINGLETON(CFileRmPxy);
DECLARE_PROTECT_FINAL_CONSTRUCT();


   // Set pointers to NULL within the constructor.
   CResourceDispenser ();
   ~ CResourceDispenser ();


DECLARE_REGISTRY_RESOURCEID(IDR_RDISP)
DECLARE_GET_CONTROLLING_UNKNOWN()

BEGIN_COM_MAP(CResourceDispenser)
   COM_INTERFACE_ENTRY(IResourceDispenser)
   COM_INTERFACE_ENTRY(IDispatch)
   COM_INTERFACE_ENTRY(IDispenserDriver)   
   COM_INTERFACE_ENTRY_AGGREGATE(IID_IMarshal, m_pUnkMarshaler.p)
END_COM_MAP()

   // Do initialization work within this method.
   HRESULT FinalConstruct();

   // Do clean up work within this method.
   void FinalRelease();

   CComPtr<IUnknown> m_pUnkMarshaler;

//
// IResourceDispenser. This is the custom interface that the application
// components call.
//
// Define your custom interface here. The following methods are given as
//examples only, you can choose any names and/or signatures as you deem
// appropriate.
//
   STDMETHOD(Connect)(long *hConnection);
   STDMETHOD(Disconnect)(long hConnection);
   STDMETHOD(SomeMethod_1)(long hConnection, DWORD dwQty );
   STDMETHOD(SomeMethod_2)(long hConnection, BYTE* pData );

//
// IDispenserDriver
//
   // This interface is required to run under MTS.
   STDMETHOD(CreateResource)(   /*[in]*/  const RESTYPID ResTypId,   
               /*[out]*/ RESID* pResId, 
               /*[out]*/ TIMEINSECS* SecsFreeBeforeDestroy   );
   STDMETHOD(RateResource)(   /*[in]*/  const RESTYPID ResTypId,
               /*[in]*/  const RESID ResId,
               /*[in]*/  const BOOL fRequiresTransactionEnlistment,
               /*[out]*/ RESOURCERATING* pRating   );
   STDMETHOD(EnlistResource)(/*[in]*/  const RESID ResId,/*[in]*/  const TRANSID TransId);
   STDMETHOD(ResetResource)(/*[in]*/  const RESID ResId);
// Numeric resource ID
   STDMETHOD(DestroyResource)(/*[in]*/  const RESID ResId);
// String resource ID
STDMETHOD(DestroyResourceS)(/*[in]*/  constSRESID ResId);
};

Return to Contents

Initializing the Resource Dispenser

If you decide to use the ATL library to implement the resource dispenser, the RD class should be derived from CComObjectRootEx. This ATL template provides the FinalContruct method that you should use when initializing. Doing initialization work within this method will allow more flexibility when handling errors since they could be propagated all the way back to the user. This is because the ATL framework has fully created the object by the time it calls FinalContruct.

When initializing the resource dispenser you need to perform the following steps:

  1. Initialize key member variables

    Initialize (to NULL) all member variables that will hold the various interface pointers. This step will help you make some basic decisions down the road, such as finding out whether a pointer should be released or whether the RD object is running under MTS. Perform this task within the default constructor of your class.

  2. Call GetDispenserManager

    Call the GetDispenserManager function to get a pointer to the IDisperserManager interface. You must call this function to obtain a successful return code that indicates if the RD object is running under MTS. In addition, the GetDispenserManager function is the only way to obtain a pointer to the IDispenserManager interface. You should store the pointer received in a private member variable so that you can check it to verify if the RD is running under MTS. For example, you can use the following call to check if the RD is running under MTS:

    hr = GetDispenserManager(&m_pDispMan);
    
  3. Call IDispenserManager::RegisterDispenser

    If the previous call succeeded, then call the IDispenserManager::RegisterDispenser method to tell the DispMan that the RD object has started and wants to be connected. This function takes two input parameters and one output parameter. The input parameters are a pointer to the RD's IDispenserDriver interface and a WCHAR pointer containing the friendly name of your resource dispenser. The output parameter is a pointer to the pointer of the IHolder interface. Make sure you store this pointer in a private member variable. You will need it when implementing the resource allocation and freeing functionality (see the section "Talking to DispMan through IHolder"). For information on IDispenserDriver see the section "Implementing the IDispenserDriver".

    Note   The RegisterDispenser method does not add a reference count for the IDispenserDriver interface. This means that before you can call this method you need to obtain a pointer to the IDispenserDriver interface though the IUnknown::QueryInterface method so that the reference is properly added. This issue should be fixed in future releases of MTS.

    For example, the following code demonstrates how to obtain a pointer to the IdispenserDriver interface through the IUnknown::QueryInterface method.

    ...hr = GetDispenserManager(&m_pDispMan);
    
    ...if (SUCCEEDED(hr))
    ...{
    ......// Call QueryInterface to get the right reference count.
    ......IDispenserDriver * pDriver;
    ......hr = GetUnknown()->QueryInterface(IID_IDispenserDriver, (void **)&pDriver);
    ......_ASSERTE(hr == S_OK);
    
    ......// Register with DispMan
    ......hr = m_pDispMan -> RegisterDispenser(pDriver, L"MyDispenser", &m_pHolder);......
    ......_ASSERTE(hr == S_OK);......
    ...}
    
  4. Get a connection to the Resource Manager

    Call the particular Resource Manager interface method (or API function) to establish a connection. This is not a resource connection, but the connection your resource dispenser needs for future requests. In other words, you should get back a handle or pointer to the RM, that is, a handle to a name pipe or a pointer to a COM Interface that will enable future calls to create and free resources or to propagate transactions. If the RM provides a COM Interface, call the CoCreateInstance function and pass the RM's CLSID to get a pointer to it.

    The following code demonstrates how to make this call:

    hr = CoCreateInstance(...CLSID_CoResourceManager, 
    NULL, 
    CLSCTX_LOCAL_SERVER, 
    IID_IResourceManager , 
    (void **)&m_pRm);...
    
  5. Use a Global Interface Table (GIT)

    Once you acquire a COM interface pointer to the Resource Manager, you should register it with the Global Interface Table (GIT), which allows the RD object to use the RM pointer within the right context even when the application component resides in a different apartment from the one the RM interface was created. In other words, the GIT ensures that the RM pointer will be valid within the particular thread in which it is needed. See Appendix A for more information on the GIT.

    The following code demonstrates how to create an instance of the GIT:

    hr = CoCreateInstance(...CLSID_StdGlobalInterfaceTable,
                               ...NULL,
                                    ...CLSCTX_INPROC_SERVER,
                                   ... IID_IGlobalInterfaceTable,
                                    ...(void **)&m_pGIT);
    

    After calling CoCreateInstance, call the IGlobalInterfaceTable::RegisterInterfaceInGlobal method and pass the RM pointer you got earlier along with its GUID. This method returns an out parameter that contains a cookie. From this point on you should always use this cookie when asking for RM pointer by calling IGlobalInterfaceTable::GetInterfaceFromGlobal. You do not need to have a member variable for the RM interface pointer; instead, you must have a member variable for the cookie.

    It is strongly recommended that you create a small private method to make the call and check the return value. For example:

    IFileRm * CFileRmPxy::GetResourceManagerPointer()
    {
    ...IResourceManager * pRm =NULL;
    ...HRESULT hr;
    ...hr = m_pGIT->GetInterfaceFromGlobal(...m_dwRmPtrCookie, 
    IID_ IResourceManager,
    (void **)&pRm);...
    ..._ASSERTE(pRm);
    ...return pRm;
    }
    
  6. Call CoCreateFreeThreadedMarshaler

    As a final step to your initialization work, call CoCreateFreeThreadedMarshaler. Since your resource dispenser has a threading model value of "Both" (see Appendix B: Registry Entries), calling this function provides efficient inter-thread marshaling of the RD interface pointer within the same process. This function creates a free-threaded marshaler object and aggregates it to the RD object (refer to Appendix A for more information on marshalling issues.) The following code demonstrates how this function call is used:

    hr = CoCreateFreeThreadedMarshaler(GetUnknown(), &m_pFreeThreadedMarshaler);
    

    Notice that the first parameter corresponds to the IUknown interface pointer of the resource dispenser object.

Return to Contents

Writing a COM Interface or API for Your RM Proxy

One of the main requirements of your resource dispenser is to act as the Resource Manager's proxy or client-side interface. It is strongly recommended that you implement your RM proxy interface as a COM interface if possible. However, it is not mandatory for this interface to be COM compliant. An example of a resource dispenser that does not provide a COM interface is the Microsoft SQL Server™ ODBC driver. The interface it provides to clients is the standard ODBC API. For an example of a resource dispenser that implements a COM interface see the sample provided in the MTS 2.x SDK.

By using COM you will be able to group areas of functionality in simple interfaces as opposed to one big API library. For example, if the RM you are working with implements a nonintuitive interface, API, IPC layer, or requires several steps to accomplish one single operational unit, you may want to encapsulate all of the complexity behind one or more well though-out COM interfaces.

Independent of the interface implementation as a COM interface or API, the RD must accomplish two goals when working as the RM proxy:

Return to Contents

Talking to DispMan through IHolder

The Dispenser Manager provides each resource dispenser (or RM proxy) with a Holder object that implements the IHolder interface. This Holder object works along side the RD to create and keep track of resources. In other words, the Holder object and the RD are part of the mechanism used by MTS to maintain an inventory of the resources provided by the Resource Manager.

When implementing your RM proxy's interface, you need to use the methods provided in IHolder to allocate and free resources. You should get a pointer to IHolder during initialization of your RM Proxy object by calling IDispenserManager::RegisterDispenser method, as described above.

Use the IHolder interface

Select among the methods of your interface those that will call the various methods of the IHolder interface. Of IHolder methods you will normally use only two: IHolder::AllocResource and IHolder::FreeResource. The other two IHolder::TrackResource and IHolder::UntrackResource are there for future functionality.

Whatever RD methods you select make sure there is a balance between AllocResource and FreeResource calls. For every call to AllocResource there should be a call to FreeResource. Failing to follow this rule will cause resources to hang around and not be returned to inventory until the object using them terminates or crashes. This behavior could seriously impair the entire system.

Call IHolder::AllocResource

AllocResource should be called when the client requests a resource. In other words, the method that returns the pointer or handle that identifies the resource to the client should call this method to ask the corresponding Holder to allocate a resource.

Notice that the method that calls AllocResource is not the one that "explicitly" connects to the RM to create the resource. The section "Implementing the IDispenserDriver" has more detail on which method explicitly connects to the RM for resource creation.

The AllocResource method takes two parameters: a RESTYPID and a RESID*. You need to decide what these two parameters contain.

RESTYID is defined as a DWORD. Its purpose is to identify a type of resource not the resource itself. What you store in it is up to you. It could be a constant or a pointer to an object in the RD memory that contains a full description of the type of resource. DispMan does not care about its content. DispMan only uses RESTYPID to refer to a resource type within the resource dispenser.

RESID is also defined as a DWORD. Its purpose is to identify a particular instance of a resource. You would normally want to store in it a pointer to the resource itself. The section "Implementing the IDispenserDriver" has more detail on this topic.

   // Running under MTS?
   if (m_pDispMan)
   {
      *hConnection = NULL;
      hr = m_pHolder -> AllocResource((RESID)1, (RESID *)hConnection);
      if (FAILED(hr))
      {
         AtlTrace(_T("AllocResource failed! Error code %x\n"), hr);
      }
   }

Notice that hConnenction could be an output parameter declared as "long *hConnection" which would allow the client to get a handle to the resource. The client would then treat this handle as an opaque handle. For a more detailed example see the sample provided with the MTS 2.x SDK.

As part of the execution of the AllocResource method, DispMan does the following steps to produce a resource:

  1. Searches the pool for a free resource of this RESTYPID, which is already enlisted in the caller's current transaction. DispMan uses the value returned from IDispenserDriver::RateResource as part of the search criteria.

  2. .

  3. Searches the pool for a free unenlisted resource of this RESTYPID, and then enlists it in the caller's current transaction. Here, DispMan also uses the value returned from IDispenserDriver::RateResource.

  4. Creates the resource by calling back to the resource dispenser's IDispenserDriver::CreateResource, and then enlisting it by calling IDispenserDriver::EnlistResource.

If the caller does not have a current transaction, then the enlistment is skipped. Or if the resource dispenser rejects the enlistment (meaning the resource is not transaction capable), then the enlistment is skipped.

Call IHolder::FreeResource

The IHolder::FreeResource method is called when the client application component "frees" a resource previously allocated by AllocResource. For example, in the case of ODBC, FreeResource would be called during the execution of SQLDisconnect API function.

When calling this function you need to provide the RESID that identifies the resource. This implies that the method that calls FreeResource must have access to it. Normally you would let the client provide you with the RESID in the form of an opaque handle.

   HRESULT hr;   
   if (m_pDispMan)
   {   
      hr = m_pHolder -> FreeResource(hConnection);
if (FAILED(hr))
      {
         AtlTrace(_T("FreeResource failed! Error code %x\n"), hr);
      }
   }

Return to Contents

Implementing the IDispenserDriver

In order to let MTS (in particular DispMan/Holder) communicate with the RD, you need to implement the IDisperserDriver interface. This is required if you want the RD to run under MTS. During the initialization process, you called IDispenserManager::RegisterDispenser and passed a pointer to the RD's IDispenserDriver interface (using the first parameter). The Holder object, which DispMan assigned to the RD instance, uses this interface pointer to communicate with the RD.

This interface provides the means by which MTS can create and maintain inventory of the resources provided by the RD. The Holder object calls this interface's methods for creating, rating, transaction enlisting, resetting, and destroying resources. The RD implements this interface as any other COM interface (such as the custom interface provided to clients for the RM Proxy functionality; see the section, "Writing a COM Interface or API for Your RM Proxy").

Implement IDispenserDriver::CreateResource

When it is time to create a new resource, the Holder asks the resource dispenser to create a resource by calling the IDisperserDriver::CreateResource method. In other words, when the RD's RM Proxy custom interface calls IHolder::AllocResource and not resources are available, the Holder object calls this method.

Follow these steps to implement this method:

  1. Check the RESTYPID input parameter to see if your RD can create this type of resources.

  2. Get a pointer to the RM interface. Back in the initialization object you got this pointer and stored it in the Global Interface Table. Now you need to get it back by calling the IGlobalInterfaceTable::GetInterfaceFromGlobal method and passing the cookie stored in a member variable. You may have written a private method to do this; see GetResourceManagerPointer() in the section "Use a Global Interface Table (GIT)".
    IResourceManager * pRm = GetResourceManagerPointer();
    if (!pRm)
    {
    return E_UNEXPECTED;
    }
    
  3. Call the RM's method to create the resource. This method should have an output parameter in that the RM stores a handle or pointer to the resource.
    CComBSTR sRDName = L"SomeResourceDispenser";
    hr = pRm -> Connect(sRDName.m_str, (long *)&lHandle);
    if (FAILED(hr))
    {
    pRm-Release();
    return hr;
    }
    
  4. Take the resource pointer or handle acquired during step 3 and store it in the output parameter RESID*. This will be the value passed all the way back to the method that called IHolder::AllocResource. This way the resource pointer/handle becomes the resource ID. See the section "Call IHolder::AllocResource."
    *pResId = (RESID)lHandle;
    
  5. Set a timeout value in the output parameter TIMEINSECS*. This indicates to DispMan the number of seconds that this resource will be allowed to remain idle in the pool before DispMan destroys it.
    //
    // Set a 120-second time out.
    //
    *pSecsFreeBeforeDestroy = 120; 
    

    Release the RM interface pointer.

    if (pRm)
    {
    pRm -> Release();
    pRm = NULL;
    }
    

Implement IDispenserDriver::RateResource

As part of the process of allocating a resource, the Holder object calls the IDisperserDriver::RateResource method. The Holder object generates a list of candidates among the already created and sometimes enlisted resources. For each of these candidates, the Holder object calls the RateResource method to obtain a value rate (on a scale of 0 to 100) that will determine the "fitness" of the candidate with respect to the RESTYPID and the transaction itself.

The resource dispenser can terminate the rating loop early by assigning the candidate a resource rating of 100 (a perfect fit). A rating of 100 would normally be reserved for candidate resources that match the RESTYID and are already properly enlisted, unless the resource dispenser concludes that enlistment is an inexpensive operation. If all candidate resources (if any) are rated 0 (unuseable) then a new resource will be created by calling IDispenserDriver::CreateResource (see the section "Implement IDispenserDriver::CreateResource").

The steps to implement this method are:

  1. Check that the resource passed in the RESID (resource pointer) is a good fit for the resource type required. If the conclusion is negative, return a rate less than 100, 0 may be the appropriate value, in the output parameter RESOURCERATING*. Otherwise continue to Step 2.

  2. Check to see if the resource needs enlistment by querying the value of fRequiresTransactionEnlistment. If FALSE, the resource becomes a prime candidate. In that case return 100. If TRUE and enlistment is expensive, rate the resource lower than 100.
    ...if (fRequiresTransactionEnlistment == FALSE)
    ...{
    ......*pRating = 100;.........
    ...}
    ...else
    ...{
    ......// not enlisted
    ......*pRating = 50;
    ...}
    
  3. Return S_OK if successful.

Implement IDispenserDriver::EnlistResource

As part of the process of allocating a resource the Holder object may call the IDispenserDriver::EnlistResource method. There are two cases in which this method gets called:

To implement this method, you need to use two of the OLE Transactions objects: the Transaction and Export objects. The Transaction object represents the MS DTC transaction and the Export object represents the connection between an RM proxy (or resource dispenser) and Resource Manager. The Export object contains the name and the location of the Resource Manager's Transaction Manager and is used to propagate transactions between processes or systems.

Follow these steps to implement this method:

  1. Check the value of TRANSID. If 0 then verify that the resource indicated by RESID is not enlisted in a transaction (this is up to your particular implementation.) If the outcome is TRUE, then return S_OK.

  2. If the value of TRANSID is different than 0, cast it to an ITransaction pointer interface (ITransaction*).
    ...ITransaction * pTransaction = (ITransaction*)TransId;
    
  3. Get a pointer to the Resource Manager interface from the Global Interface Table (see Step 2 in the section "Implement IDispenserDriver::CreateResource").

  4. Check to see if you have an ITransactionExport pointer associated to the RESID. It is a good idea to keep a map of RESIDs and ITransactionExport* because it is expensive to create an Export object every time a resource needs to be enlisted. If you do not have an Export object available, create one (see the next section "Obtaining an ITransactionExport Interface Pointer" for more information).
    ...pExport = m_mapExport[ResId];
    ...if (pExport == NULL)
    ...{
    // Create an Export object
    hr = GetExportObject(ResId, pTransaction, &pExport);
    ......if (FAILED(hr))
    ......{
    .........pRm->Release();
    .........return hr;
    ......}
    // Create a map entry between pExport and ResId.
    ......m_mapExport[ResId] = pExport;
    ...}
    
  5. Call ITransactionExport::Export to export the transaction object and get the size of the Transaction Cookie.
    ...ULONG... cbTransactionCookie = 0;
    ...hr = pExport->Export (pTransaction, &cbTransactionCookie);
    
  6. Using the size of the Transaction Cookie, allocate a buffer. You can use the COM function CoTaskMemAlloc. Always check the return value from this function.
    ...rgbTransactionCookie = (BYTE *) CoTaskMemAlloc (cbTransactionCookie);
    ...if (0 == rgbTransactionCookie)
    ...{
    ......pRm->Release();
    ......return E_FAIL;
    ...}
    
  7. With the Transaction pointer, Transaction Cookie, and Cookie Buffer pointer call ITransactionExport::GetTransactionCookie to get the Transaction Cookie value. This method provides the amount of bytes used for the cookie in an output parameter. Once this method returns, you are ready to propagate the transaction and enlist the resource in it.
    ...hr = pExport->GetTransactionCookie (...pTransaction, 
    ..................bTransactionCookie,
    ..................rgbTransactionCookie,
    ..................&cbUsed...);
    
  8. To propagate the transaction all the way to the RM and enlist the resource, you need to call a method within the RM interface. This method should allow the caller to pass a RESID, the bytes used to store the Cookie, and the Transaction Cookie Buffer. This RM's method will import the transaction represented by the cookie and will call MS DTC to get the Transaction object and proceed to enlist the resource.
    ...hr = pRm->ExportTx (ResId, cbUsed, rgbTransactionCookie);
    
  9. Finally, free the allocated Cookie buffer and release the pointer to the Resource Manager.
    ...CoTaskMemFree (rgbTransactionCookie);
    ...pRm->Release();
    

Obtaining an ITransactionExport Interface Pointer

If a resource must be enlisted in a transaction and no Export object pointer (ITransactionExport*) is available for that resource, you must create an Export object.

Follow these steps to create an Export object:

  1. Get a pointer to the Resource Manager interface from the Global Interface Table (see Step 2 in the section "Implement IDispenserDriver::CreateResource").

  2. The RM interface should provide a method to get the "Whereabouts" of the RM. If your RD will be using more than one RM from different locations, you should call this method every time an Export object is needed. Otherwise, call it once and store the "Whereabouts." This method would probably take three parameters: the RESID, a BYTE**, and an ULONG*. In the BYTE** the RM's method should return a pointer to a buffer that contains the RM's "Whereabouts" and in ULONG* the size of this buffer. The RD must call such a method to obtain the RM's location.
    ...hr = pRm->GetWhereabouts (ResId, &rgbWhereabouts, &cbWhereabouts);
    
  3. Call the Transaction object's IUknown::QueryInterface to obtain an IGetDispenser interface pointer.
    ...hr = pTransaction->QueryInterface (IID_IGetDispenser, (LPVOID *) &pIDispenser);
    
  4. Call the IGetDispenser::GetDispenser method to obtain an ITransactionExportFactory interface pointer.
    ...hr = pIDispenser->GetDispenser (IID_ITransactionExportFactory, (LPVOID *)&pTxExpFac );
    
  5. Release the IGetDispenser* you obtained in Step 3.

  6. Call the ITransactionExportFactory::Create method and pass the Whereabouts buffer, its size and a pointer to an ITransationExport*. This method will create the Export object and place a pointer to its interface in the output parameter.
    ...hr = pTxExpFac->Create (cbWhereabouts, rgbWhereabouts, ppExport);
    
  7. Release the ITransactionExportFactory* you obtained in Step 4.

  8. Free the "Whereabouts" buffer by calling CoTaskMemFree.
    ...CoTaskMemFree (rgbWhereabouts);
    
  9. Finally release the RM interface pointer.

At this point the RD is ready to continue with the enlistment process as depicted in the previous section.

Implement IDispenserDriver::ResetResource

The Holder object calls this method when it is time to put the resource back into general or enlisted inventory. This method prepares the resource before it goes into inventory.

Follow these steps to implement the method:

  1. Get a pointer to the Resource Manager interface from the Global Interface Table (see Step 2 in the section "Implement IDispenserDriver::CreateResource").

  2. Call the particular method within the RM's interface to reset the resource. This method may have a single parameter containing the RESID.
    ...hr = pRm -> ResetConnection((long)ResId);
    
  3. Clean up any resource specific state data that may have been set while the resource was active.

  4. Finally, release the RM interface pointer.

Implement IDispenserDriver::DestroyResource

The Holder object calls this method when it is time to destroy the resource.

The steps to implement this method are:

  1. Get a pointer to the Resource Manager interface from the Global Interface Table (see Step 2 in the section "Implement IDispenserDriver::CreateResource").

  2. Call the particular method within the RM's interface to release the resource. This method may have a single parameter containing the RESID.
    ...hr = pRm -> Disconnect((long )ResId);
    
  3. Release the RM interface pointer.

  4. Release the ITransactionExport pointer associated to the RESID.

  5. If any map entry was made to keep the RESID and ITransactionExport* association, remove it.
   ITransactionExport   *pExport;
   pExport = m_mapExport[ResId];
   int nElements = m_mapExport.erase(ResId);

Return to Contents

Releasing the Resource Dispenser

If you are using ATL, you can use the FinalRelease method to undo the "things" did during initialization in the FinalConstruct method and throughout the execution of the resource dispenser.

To release an instance of the resource dispenser, follow these steps:

  1. Unregister the RM interface pointer from the Global Interface Table by calling the method RevokeInterfaceFromGlobal and pass the cookie you got during initialization.
    ...hr =m_pGIT->RevokeInterfaceFromGlobal(m_dwRmPtrCookie);
    
  2. Release the pointer to the GIT interface and set the member variable to NULL.

  3. Do Step 2 for the interface pointers to IDispenserManager, IHolder, Resource Manager, and Free-Threaded Marshaler.

Return to Contents

Implement the DLL EntryPoint and Exports

There is one EntryPoint function called DllMain and four DLL export functions: DllCanUnloadNow, DllGetClassObject, DllRegisterServer, and DllUnregisterServer. The resource dispenser should implement them all. Normally you want to use the implementation generated by the ATL Wizard in Visual C++ 5.x.

The ATL Application Wizard generates the following code:

// ResDisp.cpp : Implementation of DLL EntryPoint and Exports.

#include "stdafx.h"
#include "resource.h"
#include "initguid.h"
#include "ResDisp.h"
#include "dlldatax.h"

#include "ResDisp_i.c"

#ifdef _MERGE_PROXYSTUB
extern "C" HINSTANCE hProxyDll;
#endif

CComModule _Module;

BEGIN_OBJECT_MAP(ObjectMap)
END_OBJECT_MAP()

/////////////////////////////////////////////////////////////////////////////
// DLL EntryPoint

extern "C"
BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID lpReserved)
{
   lpReserved;
#ifdef _MERGE_PROXYSTUB
   if (!PrxDllMain(hInstance, dwReason, lpReserved))
      return FALSE;
#endif
   if (dwReason == DLL_PROCESS_ATTACH)
   {
      _Module.Init(ObjectMap, hInstance);
      DisableThreadLibraryCalls(hInstance);
   }
   else if (dwReason == DLL_PROCESS_DETACH)
      _Module.Term();
   return TRUE;    // Okay
}

/////////////////////////////////////////////////////////////////////////////
// Used to determine whether the DLL can be unloaded by OLE

STDAPI DllCanUnloadNow(void)
{
#ifdef _MERGE_PROXYSTUB
   if (PrxDllCanUnloadNow() != S_OK)
      return S_FALSE;
#endif
   return (_Module.GetLockCount()==0) ? S_OK : S_FALSE;
}

/////////////////////////////////////////////////////////////////////////////
// Returns a class factory to create an object of the requested type

STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
#ifdef _MERGE_PROXYSTUB
   if (PrxDllGetClassObject(rclsid, riid, ppv) == S_OK)
      return S_OK;
#endif
   return _Module.GetClassObject(rclsid, riid, ppv);
}

/////////////////////////////////////////////////////////////////////////////
// DllRegisterServer - adds entries to the system registry.

STDAPI DllRegisterServer(void)
{
#ifdef _MERGE_PROXYSTUB
   HRESULT hRes = PrxDllRegisterServer();
   if (FAILED(hRes))
      return hRes;
#endif
   // registers object, typelib and all interfaces in typelib
   return _Module.RegisterServer(TRUE);
}

/////////////////////////////////////////////////////////////////////////////
// DllUnregisterServer - removes entries from the system registry.

STDAPI DllUnregisterServer(void)
{
#ifdef _MERGE_PROXYSTUB
   PrxDllUnregisterServer();
#endif
   _Module.UnregisterServer();
   return S_OK;
}

Return to Contents

Appendix A: Threading and Marshalling Issues

The resource dispenser as a singleton object

Since the Holder object assigned to the resource dispenser object is the one that maintains the pool of resources, you must have only one instance of the resource dispenser. Otherwise, many instances of the RD would imply more than one Holder object and, consequently, more than one pool of resources. Therefore, the RD must be a singleton object. This means that all client threads will use the same RD object.

Care should be taken when implementing your RD class. The RD class has to be fully reentrant and thread-safe to be able to provide one single instance of the RD. Moreover, if you plan to provide the same Class factory instance for each calling thread, make sure that thread synchronization code is added to the implementation of the DllGetClassObject export function. Also your class factory itself needs to be carefully written so that it will return the same RD pointer for all its clients. For example, the reference count must be thread safe.

Fortunately, ATL takes care of these issues with: the CComObjectRootEx template, the DECLARE_CLASSFACTORY_SINGLETON macro, and the CComModule class provided when you run the ATL COM Application Wizard and the ATL Object Wizard.

Using the Free-threaded Marshaler

Supporting both threading models (STA and MTA) implies that by using standard marshalling to accomplish inter-thread marshalling would result in a performance hit that could be easily avoided. When marshalling the RD's interface pointer between different threads in the same process, the client thread should have access to the same address space where the pointer resides. Therefore, the client can call the interface directly. This is a gain in performance as opposed to calling the interface through a proxy, which would be the case with standard marshalling. However, when marshalling across processes, you should use standard marshalling.

To easily accomplish this switch in marshalling, your RD should call the CoCreateFreeThreadedMarsharler function. This function will aggregate a free-threaded marshaller object to your RD's object. This object will perform either marshalling depending on the context in which the call is made.

Using the Global Interface Table (GIT)

There is one problem when aggregating the free-threaded marshaller. Normally your RD object holds, in its member variables, interface pointers to other objects that reside in other processes or are not free-threaded. The problem becomes apparent when the RD makes any reference to these objects from a thread different to the one where these objects' pointers were stored. Thus, any such call will result in the error RPC_E_WRONG_THREAD or some incorrect result. Clearly, this will be a common scenario for resource dispensers since they hold pointers to at least their Resource Managers and probably other objects.

To solve this problem, you should use a Global Interface Table object. It allows any apartment (STA or MTA) in a process to get access to an interface implemented on an object in any other apartment in the process. Thus, by using both the free-threaded marshaller and the GIT, your RD will have a better performance than using standard marshalling as the inter-thread marshalling mechanism.

Return to Contents

Appendix B: Registry Entries

The following code is an example of the registry entries needed for a resource dispenser. They are no different from those of any other COM component. If you were planning to have data stored in the registry, this would be the place to make the initial entries with their default values.

HKCR
{
   ResDisp.ResDisp.1 = s 'ResDisp Class'
   {
      CLSID = s '{8A7339E4-5397-11D0-B151-00AA00BA3258}'
   }
   ResDisp. ResDisp = s ' ResDisp Class'
   {
      CurVer = s ' ResDisp. ResDisp.1'
   }
   NoRemove CLSID
   {
      ForceRemove {8A7339E4-5397-11D0-B151-00AA00BA3258} = s ' ResDisp Class'
      {
         ProgID = s ' ResDisp. ResDisp.1'
         VersionIndependentProgID = s ' ResDisp. ResDisp '
         InprocServer32 = s '%MODULE%'
         {
            val ThreadingModel = s 'Both'      
         }
      }
   }
}

Return to Contents