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"
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;
public:
STDMETHOD(RegisterResourceDispenser)( BSTR name,
LPUNKNOWN pIDispDiver,
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
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);
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");
}
}
public void CreateResource(uint restypid, out uint resid,
out uint timeinsecs)
{
resid = 0;
timeinsecs = 180;
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;
}
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)
{
}
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");
uint conn = MSMQResDisp.QPool.Connect(@".\qtest1");
MessageQueue mq = MSMQResDisp.QPool[conn];
mq.Send(new Message("This is a test message"));
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.