Export (0) Print
Expand All

Writing MS Transaction Server Resource Dispensers

Archived content. No warranty is made as to technical accuracy. Content may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist.

Walter Oliver

Microsoft Corporation

On This Page

1. What is a resource dispenser?
2. Development Environment
3. Defining the resource dispenser class
4. Initializing the resource dispenser
5. Writing a COM interface or API for your RM Proxy.
6. Talking to DispMan through IHolder
7. Implementing the IDispenserDriver.
8. Releasing the resource dispenser
9. Implement the DLL EntryPoint and Exports
Appendix A: Threading and Marshalling Issues.
Appendix B: Registry Entries

1. What is a resource dispenser?

A resource dispenser (RD) provides the following services:

  • The medium through which application components can obtain resources for executing transacted operations. These resources are non-durable and reside in the resource dispenser's memory, that is, they never persist on disk. For example, a connection to a resource manager is a common resource.

  • A resource manager (RM) proxy or RM client-side interface through which applications can access a particular RM.

  • Resource pooling for Microsoft® Transaction Server (MTS) applications so that several resources can be created and kept in an inventory for use among multiple clients.

  • Transactions that can propagate across processes or distributed systems.

1.1. When to create a resource dispenser.

Build an RD if your requirements include:

  • Pooling and recycling resources. An RD allows the MTS Dispenser Manager (DispMan) to maintain an inventory of resources that can be reused by applications at run-time.

  • Propagating transactions between process boundaries or remote systems on a network. Propagated transactions originate by the client and are executed on the server.

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.

1.2. 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:

  • Three MTS Components: Dispenser Manager, Holder, and Microsoft Distributed Transaction Coordinator (MS DTC).

  • One or more application components.

  • One resource manager.

These relationships are:

  • RD and DispMan: RD calls the IDispenserManager interface to register resource dispenser.

  • RD and Holder: RD calls the IHolder interface and Holder calls the IDispenserDriver interface.

  • RD and Application Component: Component calls the interface provided by RD

  • RD and Resource Manager:RD calls the interface provided by the Resource Manager.

  • RD and MS DTC: RD calls the ITransaction, ITarnasactionDispenser, IGetDispenser, and ITransactionExport interfaces to propagate transactions to the Resource Manager.

  • RD and OLE Libraries: RD calls the CoCreateInstance, and CoCreateFreeThreadedMarshaler functions and the IGlobalInterfaceTable interface.

1.3. 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).

2. 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:

  • Microsoft® Visual C++® 5.x programming language, which contains all the development tools such as compilers, linker, and debugger.

  • Active Template Library, which is ideal for building lightweight COM components. It contains the base classes and templates for the resource dispenser class: CComObjectRootEx, CComCoClass, and IDispatchImpl.

  • ATL Application Wizard, which comes with Visual C++ 5.x. This wizard is an easy way to get your project files started since it generates the DLL's entry point and export functions as well as the .def, and .rc files.

  • MTS 2.x. provides all the MTS components needed at run time.

  • MTS Software Development Kit (SDK) 2.x, which provides the documentation, sample code, header files, and libraries needed to write and compile your resource dispense, such as the header file for the IDispenserDriver interface.

  • Microsoft® Windows NT® 4.0 SDK Service Pack 3 or later, which contains the header file for the IGlobalInterfaceTable interface.

3. 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);
    STDMETHOD(DestroyResource)(/*[in]*/  const RESID ResId);   // numeric resource id
    STDMETHOD(DestroyResourceS)(/*[in]*/  constSRESID ResId);  // string resource id
};

4. 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 titled Talking to DispMan through IHolder). For information on IDispenserDriver see the Implementing the IDispenserDriver section.

    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 the Registry Entries section in Appendix B), 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.

5. 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 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 non-intuitive 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:

  • Allow client access to the RM services. This is the most important goal. As stated above, your resource dispenser does not have to run under MTS to work.

  • Interface with MTS to allocate and free resources. This is an optional, yet highly recommended, goal. It is accomplished by interfacing with DispMan through the assigned IHolder interface pointer. Notice that by using IHolder, the RD will be able to automatically participate in MTS declarative transactions. Otherwise, you will have to provide particular methods for your clients to explicitly enlist resources in transactions.

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

6.1. 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 IHodel::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.

6.2. 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 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 IDispenserDriver" has more detail on this topic.

Code Sample:

    // 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 declare 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. 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.

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

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

Code Sample:

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

7. 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 above.)

7.1. 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 "4.5. Use a Global Interface Table (GIT)" above. Code Sample:

    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. Code Sample:

    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." Code Sample:

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

    Code Sample:

    //
     //    set a 120 second time out
     //
     *pSecsFreeBeforeDestroy = 120; 
    

    Release the RM interface pointer. Code Sample:

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

7.2. Implement IDispenserDriver::RateResource.

As part of the process of allocating a resource, the Holder object call 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 above).

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.

7.3. 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:

  • If the calling application component object is running within a transaction, then Holder calls the resource dispenser and asks it to enlist the resource in the transaction by calling the IDispenserDriver::EnlistResource method.

  • If DispMan requires the particular resource to be unlisted in a transaction, the Holder object calls this method to have the RD certify the case. To indicate this query, Holder passes a value of 0 in the TRANSID input parameter. This case could occur when a transacted object performed the previous use of the resource, and now the current use is by a non-transacted object. To be useful the resource must now be unlisted.

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 a 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*).

    Code Sample:

        ITransaction * pTransaction = (ITransaction *)TransId;
    
  3. Get a pointer to the Resource Manager interface from the Global Interface Table (see Step 2 in "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)

    Code Sample:

        pExport = m_mapExport[ResId];
         if (pExport == NULL)
         {
             hr = GetExportObject(ResId, pTransaction, &pExport);     // Create an Export object
             if (FAILED(hr))
             {
                 pRm->Release();
                 return hr;
             }        
             m_mapExport[ResId] = pExport;    // Create a map entry between pExport and ResId.
         }
    
  5. Call ITransactionExport::Export to export the transaction object and get the size of the Transaction Cookie.

    Code Sample:

        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.

    Code Sample:

        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.

    Code Sample:

        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.

    Code Sample:

        hr = pRm->ExportTx (ResId, cbUsed, rgbTransactionCookie);
    
  9. Finally, free the allocated Cookie buffer and release the pointer to the Resource Manager.

    Code Sample:

        CoTaskMemFree (rgbTransactionCookie);    
         pRm->Release();
    

7.4. 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 "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.

    Code Sample:

        hr = pRm->GetWhereabouts (ResId, &rgbWhereabouts, &cbWhereabouts);
    
  3. Call the Transaction object's IUknown::QueryInterface to obtain an IGetDispenser interface pointer.

    Code Sample:

        hr = pTransaction->QueryInterface (IID_IGetDispenser, (LPVOID *) &pIDispenser);
    
  4. Call the IGetDispenser::GetDispenser method to obtain an ITransactionExportFactory interface pointer.

    Code Sample:

        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.

    Code Sample:

        hr = pTxExpFac->Create (cbWhereabouts, rgbWhereabouts, ppExport);
    
  7. Release the ITransactionExportFactory* you obtained in Step 4.

  8. Free the Whereabouts buffer by calling CoTaskMemFree.

    Code Sample:

        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.

7.5. 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 "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.

7.6. 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 "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.

    Code Sample:

        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.

    Code Sample:

        ITransactionExport    *pExport;
         pExport = m_mapExport[ResId];
         int nElements = m_mapExport.erase(ResId);
    

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

    Code Sample:

            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.

9. 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 VC++ 5.x.

The following code is generated by the ATL App Wizard:

// 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 Entry Point
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;    // ok
}
/////////////////////////////////////////////////////////////////////////////
// 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;
}

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

Appendix B: Registry Entries

The following 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 planing to have data stored in the registry, this would be the place to make the initial entries with their default values.

Code Sample:

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'        
            }
        }
    }
}
Was this page helpful?
(1500 characters remaining)
Thank you for your feedback
Show:
© 2014 Microsoft