65.9K
CodeProject 正在变化。 阅读更多。
Home

在 .Net 应用程序中使用 COM+ 资源分配器。

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.50/5 (2投票s)

2001 年 9 月 18 日

4分钟阅读

viewsIcon

74262

downloadIcon

612

本文介绍如何使用 C# 语言将资源分配器集成到 .Net 应用程序中,以构建可池化的资源。

引言

资源分配器是 COM+ 非托管编程模型的一部分,用于处理进程内资源的非持久性共享状态。简单来说,它们可以独立于 COM+ 应用程序使用,只提供资源池功能。本文介绍如何使用 C# 语言将资源分配器集成到 .Net 应用程序中。我使用一个 MessageQueue 对象作为资源分配器管理的简单可池化资源。实际上,它可以用于管理对任何类型资源的访问,如串行端口、文件、套接字等。当然,COM+ 模型提供了对象池功能,但要构建自定义功能,使用资源分配器设计模式会更简单。

接口

资源分配器是 COM+ 服务中分配器管理器的一个独立扩展。它们的契约基于以下接口:

  • IDispenserManager 用于将资源分配器组件注册到管理器进程
  • IHolder 用于从分配器管理器库存中分配或释放资源
  • IDispenserDriver 作为回调接口,用于执行资源特定的操作,如创建、重置、销毁等。
各层之间的交互通过下图显示:

根据 comsvcs.h 文件中定义的接口签名,我为资源分配器程序集重新创建了它们的抽象定义。请注意,tlbimp.exe 工具不能用于生成此元数据。

#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

资源分配器代理

在设计方面,托管代码和非托管代码之间的互操作性由轻量级 COM 组件 RDProxy.dll 支持。该代理具有以下职责:

  • 在分配器管理器中注册托管资源分配器(.Net 类),并返回其 IHolder 接口
  • 将回调接口 IDispenserDriver 委派给托管资源分配器
代理在不同世界之间完全透明地工作,这是通过将托管资源分配器类的 IUnknown 指针聚合到代理接口 com map 来实现的。在此场景中,托管类(资源分配器)代表该代理的内部对象。代理的另一个优点是其托管代码和非托管代码之间松散耦合的设计模式。不需要导入任何托管元数据。请注意,代理是使用 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_

资源分配器

资源分配器是一个单例类,充当应用程序组件和资源之间的中继。在我简单的例子中,资源是对 MessageQueue 对象的引用,该对象使用资源类型 ID(队列位置的完整路径)进行初始化。每个活动/非活动资源都通过其唯一的 ID(resid)来识别,该 ID 是应用程序、资源分配器和分配器管理器这三层之间的“Cookie”。它们的“握手”非常简单明了。

  • 应用程序组件请求与由其资源类型 ID 指定的资源建立连接。
  • 此请求被传递给分配器管理器 (AllocResource),以在持有者组件中查找库存。根据此结果,分配器管理器将要求资源分配器创建新资源或评分(rating)已停用的资源以重复使用它。
  • 在创建新资源的情况下,资源分配器会初始化资源,并将其引用插入到资源池中,其键是唯一的,在本设计中由其资源 ID 表示。此值作为连接 Cookie 返回给应用程序组件。
  • 现在,应用程序组件可以使用此连接随时访问该资源,直到其停用。
  • 应用程序组件可以请求资源分配器断开指定资源的连接,它将在持有者组件的库存中执行其停用并重置其状态。回调方法 ResetResource 表明该资源可以再次使用。
资源在其停用后的生命周期受 CreateResource 方法中设置的 timeinsecs 值的限制。如果此时间已过,分配器管理器将要求资源分配器销毁该资源并将其从资源池中移除。另一方面,重新连接资源将导致重用资源池中的可用资源(评分过程),并设置其在持有者组件中的库存状态。这是资源分配器(可池化资源连接)的主要优点。请注意,RDProxy.dll 必须添加到程序集项目的引用中。
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; } 
        }
    }
}

测试

可以使用 TestConsole.exe 程序和 Windows DebugView 工具 (http://www.sysinternals.com/) 来测试资源分配器和分配器管理器的行为。请注意,“.\qtest1”和“.\qtest2”等资源(消息队列)必须在此测试之前创建,并且 RDProxy.dll COM 组件也必须注册。测试很简单,分为三个步骤,每个步骤以不同的方式使用资源分配器。如果您将步骤 1 保持超过 180 秒(资源生命周期),则可以看到资源销毁通知。
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();
            }
        }
    }
}

跟踪输出

结论

在这个简单的例子中,资源分配器被用来管理可池化资源——对 MessageQueue 对象的引用。然而,其能力可以通过在 COM+ 事务性应用程序中使用来增强。在这种情况下,其资源可以成为分布式事务的一部分,这将允许以 ACID 方式处理所有资源。

© . All rights reserved.