Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Using COM+ Resource Dispenser in the .Net Application.

0.00/5 (No votes)
17 Sep 2001 1  
This article describes how to incorporate the resource dispenser into the .Net application using the C# language to build a poolable resource.

Introduction

The resource dispensers are a part of the unmanaged COM+ programming model to handle a non-durable shared state of the resources within a process. In the simple way they can be use independently from the COM+ application, providing only resource pooling capabilities. This article describes how to incorporate the resource dispenser into the .Net application using the C# language. I am using a MessageQueue object as a simple poolable resource managed by the resource dispenser. Practically, it can be used to manage access to any type of resources such as serial ports, files, sockets, etc. Of course, the COM+ model offers the object pooling capabilities, however to build your custom features it is easy to use the resource dispenser design pattern.

Interfaces

The resource dispenser is an individual extension of the Dispenser Manager located in the COM+ services. Their contract is based on the interfaces such as

  • IDispenserManager for registration of the resource dispenser component into the manager process
  • IHolder to allocate or release a resource from the Dispenser Manager inventory
  • IDispenserDriver as a callback interface to perform a resource specific work such as create, reset, destroy, etc.
The interaction between each layer is shown in the following picture:

Based on the interface signatures, defined in the comsvcs.h file, I recreated their abstract definitions for the resource dispenser assembly. Note that the tlbimp.exe utility can not be used to generate this metadata.

#region Interfaces of the COM+ Dispenser Manager 
    public class mtxdm 
    {
        [DllImport ("mtxdm.dll")]
        public static extern void 
        GetDispenserManager([MarshalAs(UnmanagedType.IUnknown)] out object o);
    }
    
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
        Guid("5cb31e10-2b5f-11cf-be10-00aa00a2fa25")]
    public interface IDispenserManager
    {
        [PreserveSig]
        void RegisterDispenser(
            [In, MarshalAs(UnmanagedType.IUnknown)] object objDispenserDriver, 
            [In, MarshalAs(UnmanagedType.BStr)] string szDispenserName, 
            [Out, MarshalAs(UnmanagedType.IUnknown)] out object objHolder);

        [PreserveSig]
        void GetContext(
            [Out, MarshalAs(UnmanagedType.U4)] out uint instid,
            [Out, MarshalAs(UnmanagedType.U4)] out uint transid);
    }

    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
                   Guid("bf6a1850-2b45-11cf-be10-00aa00a2fa25")]
    public interface IHolder
    {
        [PreserveSig]    
        void AllocResource(
            [In, MarshalAs(UnmanagedType.U4)] uint   restypid,
            [Out, MarshalAs(UnmanagedType.U4)] out uint resid);
        [PreserveSig]
        void FreeResource(
            [In, MarshalAs(UnmanagedType.U4)] uint resid);
        [PreserveSig]
        void TrackResource(
            [In, MarshalAs(UnmanagedType.U4)] uint resid);
        [PreserveSig]
        void TrackResourceS(
            [In, MarshalAs(UnmanagedType.BStr)] string constSRESID);
        [PreserveSig]
        void UntrackResource(
            [In, MarshalAs(UnmanagedType.U4)] uint resid,
            [In, MarshalAs(UnmanagedType.Bool)] bool flag);
        [PreserveSig]
        void UntrackResourceS(
            [In, MarshalAs(UnmanagedType.BStr)] string constSRESID,
            [In, MarshalAs(UnmanagedType.Bool)] bool flag);
        [PreserveSig]
        void Close();
        [PreserveSig]
        void RequestDestroyResource(
            [In, MarshalAs(UnmanagedType.U4)] uint resid);
    }

    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
     Guid("208b3651-2b48-11cf-be10-00aa00a2fa25")]
    public interface IDispenserDriver
    {
        [PreserveSig]
        void CreateResource(
            [In, MarshalAs(UnmanagedType.U4)]  uint restypid,
            [Out, MarshalAs(UnmanagedType.U4)] out uint resid,
            [Out, MarshalAs(UnmanagedType.U4)] out uint timeinsecs);
        [PreserveSig]
        void RateResource(
            [In, MarshalAs(UnmanagedType.U4)]   uint restypid,
            [In, MarshalAs(UnmanagedType.U4)]   uint resid,
            [In, MarshalAs(UnmanagedType.Bool)] bool fRequiresTxEnlistment,
            [Out, MarshalAs(UnmanagedType.U4)]  out uint rating);
        [PreserveSig]
        int EnlistResource(
            [In, MarshalAs(UnmanagedType.U4)] uint resid,
            [In, MarshalAs(UnmanagedType.U4)] uint transid);
        [PreserveSig]
        void ResetResource(
            [In, MarshalAs(UnmanagedType.U4)] uint resid);
        void DestroyResource(
            [In, MarshalAs(UnmanagedType.U4)] uint resid);
        void DestroyResourceS(
            [In, MarshalAs(UnmanagedType.BStr)] string constSRESID);
    }
#endregion

Resource dispenser proxy

In the about design the interoperability between managed and unmanaged code is supported by lightweight COM component � RDProxy.dll. The proxy has the following responsibilities:

  • register a managed resource dispenser (.Net class) in the Dispenser Manager and returning back its IHolder interface
  • delegate the callback interface IDispenserDriver to the managed resource dispenser
The proxy is working full transparently between the different worlds, which it has been achieved aggregating an IUnknown pointer of the managed resource dispenser class into the proxy interface com map. In this scenario, the managed class (resource dispenser) represents an inner object of this proxy. The other proxy�s advantage is its loosely coupled design pattern between the managed and unmanaged codes. There is no requirement to import any managed metadata. Note that proxy has been built with a VC++ 6.0.
////////////////////////////////////////////////////////////////////////////

#ifndef __RDPROXY_H_
#define __RDPROXY_H_

#include "resource.h"       // main symbols


/////////////////////////////////////////////////////////////////////////////

// CRDProxy

class ATL_NO_VTABLE CRDProxy : 
    public CComObjectRootEx<CComMultiThreadModel>,
    public CComCoClass<CRDProxy, &CLSID_RDProxy>,
    public IRDProxy
{
public: 
    CRDProxy() {}

DECLARE_REGISTRY_RESOURCEID(IDR_RDPROXY)
DECLARE_GET_CONTROLLING_UNKNOWN()

DECLARE_PROTECT_FINAL_CONSTRUCT()

BEGIN_COM_MAP(CRDProxy)
    COM_INTERFACE_ENTRY(IRDProxy)
    COM_INTERFACE_ENTRY_FUNC(IID_IDispenserDriver, 0, Delegator)
END_COM_MAP()

    HRESULT FinalConstruct()
    {
        return S_OK; 
    }
    void FinalRelease()
    {
        m_pDispMan.Release();
        m_pIHolder.Release();
        m_punkCallback.Release();
    }
    static HRESULT WINAPI Delegator(void* pv, REFIID riid, 
          LPVOID* ppv, DWORD dw)
    {
        *ppv = (LPVOID)(((CRDProxy*)pv)->m_punkCallback.p);
        return S_OK;
    }

private:
    CComQIPtr<IDispenserDriver, &IID_IDispenserDriver>    m_punkCallback;
    CComPtr<IHolder>              m_pIHolder;
    CComPtr<IDispenserManager>    m_pDispMan;

// IRDProxy

public:
    STDMETHOD(RegisterResourceDispenser)(/*[in]*/  BSTR name, 
                                         /*[in]*/  LPUNKNOWN pIDispDiver, 
                                         /*[out]*/ LPUNKNOWN* pIHolder)
    {
        m_punkCallback = pIDispDiver;
        CComPtr<IDispenserDriver> pDriver;
        GetUnknown()->QueryInterface(IID_IDispenserDriver, 
                                reinterpret_cast<void **>(&pDriver));
        //

        GetDispenserManager(&m_pDispMan);
        m_pDispMan->RegisterDispenser(pDriver, name, &m_pIHolder);
        *pIHolder = m_pIHolder.p;
        //        

        return S_OK;
    }
};
#endif //__RDPROXY_H_

Resource dispenser

The resource dispenser is a singleton class relays between the application component and your resource. In my simple example the resource is a reference to the MessageQueue object initiated with the resource type id (full path of the queue location). Every active/deactivate resource is recognized by its unique id (resid), which is a �cookie� between the three layers such as application, resource dispenser and dispenser manager. Their �handshaking� is very simple and straightforward:

  • The application component is asking a connection to the resource specified by its resource type id.
  • This request is passed to the Dispenser Manager (AllocResource) to look at an inventory in the holder component. Based on this result, the dispenser manager will ask the resource dispenser either to create one or rating the deactivated resource to reuse it.
  • In the case of creating a new resource, the resource dispenser initiates the resource and its reference is inserted into the resource pool under the unique key, which in this design is represented by its resource id. This value is returned back to the application component as a connection cookie.
  • Now, the application component can use this connection to access the resource any time until its deactivation.
  • The application component can request the resource dispenser to disconnect a specified resource, which it will perform its deactivation in the inventory of holder component and resetting its state. The callback method ResetResource indicates, that this resource can be reuse again.
The resource lifetime after its deactivation is limited by setup a value timeinsecs in the CreateResource method. If this time expired, the dispenser manager will ask a resource dispenser to destroy this resource and taking out from the resource pool. In the other hand, reconnecting of the resource it will raise to reuse one of available resource in the resource pool (the rating process) and setup its state in the inventory holder component. This is a primary advantage of the resource dispenser (poolable resource connection). Note that the RDProxy.dll has to be added to the references of the assembly project.
using System;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.InteropServices;
using System.EnterpriseServices;
using System.Threading;
using System.Collections;
using System.Messaging;
//

using RESDISPPROXYLib;


namespace RKiss.ResourceDispenser 
{
    #region Interfaces of the MTX Dispenser Manager 
    // ...    

    #endregion

    
    [Guid("385125E6-F0EC-40aa-8768-734957C5C969")]
    [ComVisible(true)]
    public class MSMQResDisp :  IDispenserDriver, IDisposable 
    {
        private Hashtable MQPool = Hashtable.Synchronized(new Hashtable());
        public  IHolder holder = null;
        object  rdproxy = null;
        static private MSMQResDisp rd = null;

        private  MSMQResDisp()
        {    
            object objHolder;
            rdproxy = new RDProxy();
            (rdproxy as IRDProxy).RegisterResourceDispenser("MSMQ Pool", 
                                                       this, out objHolder);
            holder = objHolder as IHolder;
            Trace.WriteLine(string.Format("MSMQResDisp.ctor = {0}", 
                                          GetHashCode()));    
        }
        public void Dispose()
        {
            holder.Close();
            Marshal.ReleaseComObject(rdproxy);
            // be sure that all resources have been closed

            foreach(uint key in MQPool.Keys)
                this[key].Close();
            MQPool.Clear();
            Trace.WriteLine(string.Format("MSMQResDisp.Dispose done = {0}", 
                            GetHashCode()));    
        }
        public static MSMQResDisp QPool 
        { 
            get 
            { 
                if(rd == null) 
                    rd = new MSMQResDisp();
                return rd; 
            } 
        }
        public static void Release()
        {
            if(rd != null) 
            {    
                rd.Dispose();
                rd = null;
                Trace.WriteLine("MSMQResDisp.Release done");    
            }
        }
        ////////////////////////////////////////////////////////////////////

        //IDispenserDriver - callback from the COM+ Resource Dispenser 

        // Manager (DispMan)

        public void CreateResource(uint restypid, out uint resid, 
                                   out uint timeinsecs)
        {
            resid = 0;
            timeinsecs = 180;            // idle time = 3 minutes 

            string srestypid = null;

            try 
            {    
                srestypid = Marshal.PtrToStringAuto((System.IntPtr)restypid);
                MessageQueue mq = new MessageQueue(srestypid);
                resid = Convert.ToUInt32(mq.GetHashCode());
                MQPool.Add(resid, mq);
            }
            catch(Exception ex) 
            {
                Trace.WriteLine(string.Format("CreateResource, 
                       restypid={0} exception={1}", srestypid, ex.Message));
            }

            Trace.WriteLine(string.Format("CreateResource, resid={0}, 
                                           restypid={1}", resid, srestypid));
        }
        public void RateResource(uint restypid, uint resid, 
                                 bool fRequiresTxEnlistment, out uint rating)
        {
            string srestypid = Marshal.PtrToStringAuto((System.IntPtr)restypid);
            rating = (MQPool.ContainsKey(resid) == true) ? (uint)100 : 0;
            Trace.WriteLine(string.Format("RateResource, resid={0}, " + 
                       "restypid={1}, rating={2}", resid, srestypid, rating));
        }
        public int EnlistResource(uint resid, uint transid)
        {
            Trace.WriteLine(string.Format("EnlistResource, resid={0}, " +
                                          "transid={1}", resid, transid));
            return 1;   //S_FALSE, we don't handle a transactional resource

        }
        public void ResetResource(uint resid)
        {
            Trace.WriteLine(string.Format("ResetResource, resid={0}", resid));
        }
        public void DestroyResource(uint resid)
        {
            this[resid].Close();
            MQPool.Remove(resid);
            Trace.WriteLine(string.Format("DestroyResource, resid={0}", 
                                          resid));
        }
        public void DestroyResourceS(string constSRESID)
        {
        }
        /////////////////////////////////////////////////////////////

        // application layer

        public uint Connect(string srestypid)
        {    
            uint resid = 0;
            uint restypid
                      = (uint)Marshal.StringToHGlobalAuto(srestypid);
            holder.AllocResource(restypid, out resid);
            return resid;
        }
        public void Disconnect(uint connection)
        {
            holder.FreeResource(connection);
        }
        public MessageQueue this[uint connection]
        {
            get { return MQPool[connection] as MessageQueue; } 
        }
    }
}

Testing

The behavior of the resource dispenser and dispenser manager can be tested using the TestConsole.exe program and DebugView for Windows utility (http://www.sysinternals.com/). Note that resources (Message Queue) such as �.\qtest1� and �.\qtest2� have to be created in prior this test and also the RDProxy.dll COM component has to be registered. The test is simple, divided into three steps which each of them using the resource dispenser differently. If you will hold the step 1 for more than 180 seconds (resource lifetime), then you can see a resource destroying notification.
using System;
using System.Threading;
using System.Messaging;
//

using RKiss.ResourceDispenser;

namespace TestConsole
{
    class TestConsole
    {
        static void Main(string[] args)
        {
            try 
            {
                Console.WriteLine("Test 1");
                // connect to the resource (queue)

                uint conn = MSMQResDisp.QPool.Connect(@".\qtest1");
                // get an access to the resource (queue)

                MessageQueue mq = MSMQResDisp.QPool[conn];
                // send message

                mq.Send(new Message("This is a test message"));
                // disconnect resource

                MSMQResDisp.QPool.Disconnect(conn);
                //

                Console.ReadLine();
                Console.WriteLine("Test 2");
                uint conn1 = MSMQResDisp.QPool.Connect(@".\qtest1");
                uint conn2 = MSMQResDisp.QPool.Connect(@".\qtest2");
                MSMQResDisp.QPool[conn1].Send(
                                    new Message("This is a test message 1"));
                MSMQResDisp.QPool[conn2].Send(
                                    new Message("This is a test message 2"));
                MSMQResDisp.QPool.Disconnect(conn1);
                MSMQResDisp.QPool.Disconnect(conn2);
                //

                Console.ReadLine();
                Console.WriteLine("Test 3");
                uint conn3 = MSMQResDisp.QPool.Connect(@".\qtest1");
                MSMQResDisp.QPool[conn3].Send(
                                     new Message("This is a test message 3"));
                MSMQResDisp.QPool.Disconnect(conn3);
                uint conn4 = MSMQResDisp.QPool.Connect(@".\qtest1");
                MSMQResDisp.QPool[conn4].Send(
                                     new Message("This is a test message 4"));
                MSMQResDisp.QPool.Disconnect(conn4);
            }
            catch(Exception ex)
            {
                Console.WriteLine("Exception catch, error = {0}", ex.Message);
            }
            finally
            {
                MSMQResDisp.Release();
                Console.WriteLine("End of test");
                Console.ReadLine();
            }
        }
    }
}

The Trace output:

Conslusion

In this simple example, the resource dispenser has been used to manage poolable resource - reference to the MessageQueue object. However, its capability is increasing using in the COM+ transactional application. In this case its resource can be a part of the distributed transaction, which it will allow to handle all resources in the ACID manner.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here