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






4.50/5 (2投票s)
2001 年 9 月 18 日
4分钟阅读

74262

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
委派给托管资源分配器
////////////////////////////////////////////////////////////////////////////
#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 方式处理所有资源。