编写 Microsoft 事务服务器资源分配器

发布日期: 5/20/2004 | 更新日期: 5/20/2004

Walter Oliver

*
本页内容
什么是资源分配器?什么是资源分配器?
开发环境开发环境
定义资源分配器类定义资源分配器类
初始化资源分配器初始化资源分配器
为您的 RM 代理编写 COM 接口或 API为您的 RM 代理编写 COM 接口或 API
通过 Iholder 与 DispMan 交谈通过 Iholder 与 DispMan 交谈
实现 IDispenserDriver实现 IDispenserDriver
释放资源分配器释放资源分配器
实现 DLL 入口点函数和导出函数实现 DLL 入口点函数和导出函数
附录 A:线程和封送处理问题附录 A:线程和封送处理问题
附录 B:注册表项附录 B:注册表项

什么是资源分配器?

资源分配器 (RD) 可提供以下服务:

应用程序组件借以获取资源以执行事务操作的媒介。这些资源不是持久性资源,它们驻留在资源分配器的内存中,也就是说,它们不会持久存储在磁盘上。例如,与一个资源管理器的连接就是一个公共资源。

应用程序借以访问特定资源管理器 (RM) 的 RM 代理或 RM 客户端接口。 Microsoft 事务服务器 (MTS) 应用程序的资源池,这样可以创建多个资源并将其保存在一个库中,以在多个客户端之间使用。

可在进程或分布式系统之间传播的事务。

何时创建资源分配器

如果您有以下需求,请创建一个 RD:

共享和重复利用资源。RD 允许 MTS 分配器管理器 (DispMan) 维护一个应用程序在运行时可以重用的资源库。

在网络上的进程边界或远程系统之间传播事务。被传播的事务由客户端产生并在服务器上执行。

不建议您编写一个资源分配器来共享应用程序组件对象。未来版本的 MTS 将使用 IObjectControl 接口,应用程序组件可以实现这个接口,以实现对象共享和重复利用。此外,在几个组件对象都可以全局共享的情况下,为每个对象设置共享属性并通过共享属性管理器使用它们,可以获得期望的结果。

MTS 下运行的资源分配器

在 MTS 下运行时,分配器管理器可自动化事务和资源回收。使用 MTS 时,您的 RD 将与以下对象进行交互:

三个 MTS 组件:分配器管理器、Holder 和 Microsoft 分布式事务协调器 (MS DTC)。

一个或多个应用程序组件。

一个资源管理器。

这些关系如下:

RD DispMan。RD 调用 IDispenserManager 接口以注册资源分配器。

RD Holder。RD 调用 IHolder 接口,Holder 调用 IdispenserDriver 接口。

RD 和应用程序组件。应用程序组件调用 RD 提供的接口。

RD 和资源管理器。RD 调用资源管理器提供的接口。

RD MS DTC。RD 调用 ITransactionITarnasactionDispenserIGetDispenserITransactionExport 接口,以向资源管理器传播事务。

RD OLE 。RD 调用 CoCreateInstanceCoCreateFreeThreadedMarshaler 函数以及 IGlobalInterfaceTable 接口。

独立于 MTS 运行的资源分配器

独立于 MTS 运行时,您的 RD 将与以下两个 MTS 组件之外的所有组件进行交互:分配器管理器和 Holder。这意味着 RD 将继续使用 MS DTC 进行事务传播,但不会提供 MTS 池(RD 可能会提供自己的池机制)。

返回页首返回页首

开发环境

为编写资源分配器而设置开发环境的方法有多个。以下是必需的或可以简化此工作的一组工具和库:

Microsoft Visual C++ 5.x 编程语言,它包含了编译器、链接器和调试器等所有开发工具。

活动模板库 (ATL),它对于建立轻量级组件对象模型 (COM) 组件十分理想。它包含资源分配器类的基类和模板:CComObjectRootEx、CComCoClass 和 IDispatchImpl

ATL Application Wizard,它是 Visual C++ 5.x 附带的工具。此向导是建立项目文件的一个简单方法,因为它可以产生 DLL 的入口点函数和导出函数以及 .def 和 .rc 文件。

Microsoft 事务服务器 2.x,它提供运行时所需的所有 MTS 组件。

Microsoft 事务服务器软件开发工具包 (SDK) 2.x,它提供编写和编译资源分配器所需的文档、示例代码、头文件和库,例如 IDispenserDriver 接口的头文件。

Microsoft Windows NT 4.0 SDK Service Pack 3 或更高版本,它包含 IGlobalInterfaceTable 接口的头文件。

返回页首返回页首

定义资源分配器类

使用 VC ++ 中的 ATL Object Wizard 并选择“简单对象”选项可以简单而轻松地定义您的类。以下类定义可用作您定义资源分配器类的模型:

/////////////////////////////////////////////////////////////////////////////
// CResourceDispenser
class ATL_NO_VTABLE CRDisp : 
public IDispenserDriver, 
public CComObjectRootEx,
public CComCoClass,
public IDispatchImpl
{
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 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 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);
// Numeric resource ID
STDMETHOD(DestroyResource)(/*[in]*/  const RESID ResId);
// String resource ID
STDMETHOD(DestroyResourceS)(/*[in]*/  constSRESID ResId);
};
返回页首返回页首

初始化资源分配器

如果您决定使用 ATL 库来实现资源分配器,则 RD 类应从 CcomObjectRootEx 中派生。这个 ATL 模板提供了您在初始化时应使用的 FinalContruct 方法。在此方法中执行初始化工作可在处理错误时获得更大的灵活性,因为这些错误可以回传给用户。这是因为 ATL 框架在调用 FinalContruct 之前已完整创建了这个对象。

初始化资源分配器时,您需要执行以下步骤:

初始化主要成员变量

将所有要保存各种接口指针的成员变量初始化(为 NULL)。该步骤有助于您顺便作一些基本决定,例如查明是否应释放某个指针或这个 RD 对象是否在 MTS 下运行。在您所创建类的默认构造函数中执行这项任务。

调用 GetDispenserManager

调用 GetDispenserManager 函数以获取指向 IDisperserManager 接口的指针。您必须调用这个函数以获取一个成功的返回代码,此代码指出 RD 对象是否在 MTS 下运行。此外,GetDispenserManager 函数是获取指向 IDispenserManager 接口的指针的唯一途径。您应将获得的指针存储在一个私有成员变量中,以便您可以检查该变量以检验 RD 是否在 MTS 下运行。例如,您可以使用以下调用来检查 RD 是否在 MTS 下运行:

hr = GetDispenserManager(&m_pDispMan);

调用 IDispenserManager::RegisterDispenser

如果前面的调用成功,则调用 IDispenserManager::RegisterDispenser 方法来告诉 DispMan:RD 对象已启动且希望获得连接。此函数包含两个输入参数和一个输出参数。输入参数是两个指针,一个是指向 RD IDispenserDriver 接口的指针,一个是包含您资源分配器友好名称的 WCHAR 指针。输出参数是一个指向 IHolder 接口指针的指针。请确保将这个指针存储在一个私有成员变量中。在实现资源分配和释放功能时,您将需要该指针(请参阅“通过 IHolder 与 DispMan 交谈”一节)。有关 IDispenserDriver 的信息,请参阅“实现 IDispenserDriver”一节。

RegisterDispenser 方法不会为 IDispenserDriver 接口添加引用计数。这意味着,在调用此方法之前您需要先通过 IUnknown::QueryInterface 方法获取一个指向 IDispenserDriver 接口的指针,这样才能正确添加引用。此问题应在未来版本的 MTS 中加以修正。

例如,以下代码演示了如何通过 IUnknown::QueryInterface 方法获取指向 IdispenserDriver 接口的指针。

...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);......
...}

获取与资源管理器的连接

调用特定的资源管理器接口方法(或 API 函数)以建立一个连接。这不是一个资源连接,而是您的资源分配器为满足未来请求所需的连接。换言之,您应取回指向 RM 的一个句柄或指针,也就是说,一个指向命名管道的句柄或一个指向 COM 接口的指针,以便未来的调用可以创建和释放资源或传播事务。 如果 RM 提供了 COM 接口,则调用 CoCreateInstance 函数并传递 RM 的 CLSID 以获取一个指向该接口的指针。

以下代码演示了如何执行此调用:

hr = CoCreateInstance(...CLSID_CoResourceManager, 
NULL, 
CLSCTX_LOCAL_SERVER, 
IID_IResourceManager , 
(void **)&m_pRm);...

使用全局接口表 (GIT)

在您获得指向资源管理器的 COM 接口指针后,应使用全局接口表 (GIT) 注册这个指针,从而允许 RD 对象在正确的上下文中使用这个 RM 指针,即使应用程序组件驻留在与创建 RM 接口的单元不同的单元中。换言之,GIT 可确保 RM 指针在需要它的特定线程中有效。有关 GIT 的更多信息,请参阅附录 A

以下代码演示了如何创建一个 GIT 实例:

hr = CoCreateInstance(...CLSID_StdGlobalInterfaceTable,
...NULL,
...CLSCTX_INPROC_SERVER,
... IID_IGlobalInterfaceTable,
...(void **)&m_pGIT);

调用 CoCreateInstance 之后,调用 IGlobalInterfaceTable::RegisterInterfaceInGlobal 方法并将您先前得到的 RM 指针连同其 GUID 一起传送。此方法会返回一个包含 cookie 的输出参数。从这点出发,通过调用 IGlobalInterfaceTable::GetInterfaceFromGlobal 来请求 RM 指针时,您应一直使用这个 cookie。您不需要为这个 RM 接口指针设置一个成员变量;相反,您必须为 cookie 设置一个成员变量。

强烈建议您创建一个小型私有方法来执行调用并检查返回值。例如:

IFileRm * CFileRmPxy::GetResourceManagerPointer()
{
...IResourceManager * pRm =NULL;
...HRESULT hr;
...hr = m_pGIT->GetInterfaceFromGlobal(...m_dwRmPtrCookie, 
IID_ IResourceManager,
(void **)&pRm);...
..._ASSERTE(pRm);
...return pRm;
}

调用 CoCreateFreeThreadedMarshaler

初始化工作的最后一个步骤是调用 CoCreateFreeThreadedMarshaler。由于您的资源分配器的线程模型值为 “Both”(请参阅附录 B:注册表项),因此调用此函数会提供同一进程内 RD 接口指针的高效线程间封送处理。此函数创建了一个自由线程封送拆收器对象,并将其聚合到 RD 对象(有关封送处理问题的更多信息,请参阅附录 A)。以下代码演示了如何使用这个函数调用:

hr = CoCreateFreeThreadedMarshaler(GetUnknown(), &m_pFreeThreadedMarshaler);

请注意,第一个参数对应资源分配器对象的 IUknown 接口指针。

返回页首返回页首

为您的 RM 代理编写 COM 接口或 API

您资源分配器的主要需求之一就是作为资源管理器的代理或客户端接口使用。强烈建议,如果可能,请将您的 RM 代理接口实现为一个 COM 接口。但是,并不强制要求这个接口符合 COM。Microsoft SQL Server ODBC 驱动程序就是一个不提供 COM 接口的资源分配器的示例。它提供给客户端的接口是标准的 ODBC API。有关实现 COM 接口的资源分配器示例,请参阅 MTS 2.x SDK 中提供的示例。

通过使用 COM,您可以将功能区域集中到简单的接口中而不是一个大型 API 库中。例如,如果您使用的 RM 实现了一个不太直观的接口、API、IPC 层,或者需要几个步骤才能完成单个操作单元,您可能希望将所有的复杂性封装到一个或多个精心设计的 COM 接口之后。

若要独立于 COM 接口或 API 的接口实现,RD 在作为 RM 代理工作时必须实现两个目标:

允许客户端访问 RM 服务。这是最重要的目标。如上所述,您的资源分配器不一定要在 MTS 下工作。

与 MTS 连接以分配和释放资源。这是一个可选目标,但强烈建议您实现它。通过分配的 IHolder 接口指针与 DispMan 连接,可以实现这个目标。请注意,通过使用 IHolder,RD 将能够自动参与 MTS 声明性事务。否则,您将必须为客户端提供特定的方法,以显式登记事务中的资源。

返回页首返回页首

通过 Iholder 与 DispMan 交谈

分配器管理器为每个资源分配器(或 RM 代理)都提供了一个实现 IHolder 接口的 Holder 对象。这个 Holder 对象与 RD 一同工作,以创建和跟踪资源。换言之,Holder 对象和 RD 是 MTS 用来维护资源管理器所提供资源库的机制的一部分。

实现您 RM 代理的接口时,您需要使用 IHolder 中提供的方法来分配和释放资源。通过调用 IDispenserManager::RegisterDispenser 方法来初始化您的 RM 代理对象的过程中,您应获得一个指向 IHolder 的指针,如上所述。

使用 IHolder 接口

在您接口的方法中选择那些要调用 IHolder 接口各种方法的方法。在 IHolder 方法中,您通常只使用两个方法:IHolder::AllocResourceIHolder::FreeResource。其他两个方法(IHolder::TrackResourceIHolder::UntrackResource)是为未来功能而准备的。

无论您选择哪个 RD 方法,均需确保在 AllocResourceFreeResource 调用之间保持平衡。每次调用 AllocResource 时,均应调用一次 FreeResource。不遵循这个规则将导致资源闲置且无法返回到库中,直到使用它们的对象终止或崩溃。这种行为会严重降低整个系统的性能。

调用 IHolder::AllocResource

客户端请求资源时应调用 AllocResource。换言之,将标识资源的指针或句柄返回到客户端的方法应调用此方法,以请求相应的 Holder 分配资源。

请注意,调用 AllocResource 的方法并不是“显式”连接到 RM 以创建资源的那个方法。有关哪个方法可显式连接到 RM 以创建资源,“实现 IDispenserDriver”一节中有更详细的信息。

AllocResource 方法包含两个参数:RESTYPID 和 RESID*。您需要确定这两个参数包含的内容。

RESTYID 被定义为 DWORD。其目的是标识资源的类型而不是资源本身。您存储在其中的内容由您决定。它可以是一个常量或一个指向 RD 内存中某个对象的指针,其中包含资源类型的完整说明。DispMan 并不关心其内容。DispMan 只使用 RESTYPID 来引用资源分配器中的资源类型。

RESID 也被定义为 DWORD。其目的是标识某个资源的特定实例。通常您希望在其中存储一个指向资源本身的指针。有关这个主题,“实现 IDispenserDriver”一节中有更详细的说明。

   // 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);
      }
   }

请注意,hConnenction 可以是一个声明为“long *hConnection”的输出参数,这可让客户端获得该资源的一个句柄。然后,客户端会将这个句柄视为一个不透明句柄。有关更详细的示例,请参阅 MTS 2.x SDK 中提供的示例。

作为 AllocResource 方法执行的一部分,DispMan 可执行以下步骤来产生一个资源:

在池中搜索此 RESTYPID 的空闲资源,此资源已登记到调用者的当前事务中。DispMan 将 IDispenserDriver::RateResource 返回的值用作搜索标准的一部分。

在池中搜索此 RESTYPID 中未登记的空闲资源,然后将其登记到调用者的当前事务中。在这里,DispMan 也使用 IDispenserDriver::RateResource 返回的值。

通过回调资源分配器的 IDispenserDriver::CreateResource 来创建资源,然后通过调用 IDispenserDriver::EnlistResource 来登记这个资源。

如果调用者没有当前事务,则跳过登记。或者,如果资源分配器拒绝登记(意思是该资源不能使用事务),则跳过登记。

调用 IHolder::FreeResource

当客户端应用程序组件“释放”由 AllocResource 先前分配的资源时,会调用 IHolder::FreeResource 方法。例如,就 ODBC 来说,执行 SQLDisconnect API 函数期间会调用 FreeResource

调用这个方法时,您需要提供标识这个资源的 RESID。这意味着,调用 FreeResource 的方法必须可以访问这个资源。通常,您会让客户端以不透明句柄的形式为您提供 RESID。

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

实现 IDispenserDriver

为了让 MTS(特别是 DispMan/Holder)与 RD 通信,您需要实现 IDisperserDriver 接口。如果您希望 RD 在 MTS 下运行,这是必需的。在初始化过程中,您已调用 IDispenserManager::RegisterDispenser 并传递了一个指向 RD 的 IDispenserDriver 接口的指针(使用第一个参数)。DispMan 分配给 RD 实例的 Holder 对象将使用这个接口指针与 RD 通信。

这个接口提供了 MTS 可用来创建和维护 RD 所提供的资源库的方法。Holder 对象可调用这个接口的方法来创建、分级、事务登记、重置以及销毁资源。RD 实现此接口的方法与实现其他任何 COM 接口(例如为了 RM 代理功能而向客户端提供的自定义接口;请参阅“为您的 RM 代理编写 COM 接口或 API”一节)的方法一样。

实现 IDispenserDriver::CreateResource

可以创建新资源时,Holder 通过调用 IDisperserDriver::CreateResource 方法来请求资源分配器创建一个资源。换言之,当 RD 的 RM 代理自定义接口调用 IHolder::AllocResource 且没有可用资源时,Holder 对象将调用这个方法。

请按照以下步骤来实现这个方法:

检查 RESTYPID 输入参数,以查看您的 RD 是否可以创建这种类型的资源。

获得一个指向 RM 接口的指针。回到您获取该指针并将它存储到全局接口表的那个初始化对象。现在您需要调用 IGlobalInterfaceTable::GetInterfaceFromGlobal 方法并传递某个成员变量中存储的 cookie,来取回该指针。您可能已编写了一个私有方法来完成此操作;请参阅“使用全局接口表 (GIT)”一节中的 GetResourceManagerPointer()

IResourceManager * pRm = GetResourceManagerPointer();
if (!pRm)
{
return E_UNEXPECTED;
}

调用 RM 的方法来创建资源。由于 RM 存储了一个指向该资源的句柄或指针,因此该方法应有一个输出参数。

CComBSTR sRDName = L"SomeResourceDispenser";
hr = pRm -> Connect(sRDName.m_str, (long *)&lHandle);
if (FAILED(hr))
{
pRm-Release();
return hr;
}

接收步骤 3 中获得的资源指针或句柄,并将其存储在输出参数 RESID* 中。这将是回传给调用 IHolder::AllocResource 的方法的值。这样,这个资源指针/句柄将成为资源 ID。请参阅“调用 IHolder::AllocResource”一节。

*pResId = (RESID)lHandle;

在输出参数 TIMEINSECS* 中设置超时值。这为 DispMan 指明在 DispMan 销毁该资源之前允许该资源在池中保持闲置的秒数。

//
// Set a 120-second time out.
//
*pSecsFreeBeforeDestroy = 120; 

释放 RM 接口指针。

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

实现 IDispenserDriver::RateResource

作为资源分配过程的一部分,Holder 对象要调用 IDisperserDriver::RateResource 方法。Holder 对象可在已创建并不时登记的资源中生成一个候选资源列表。对其中的每个候选资源,Holder 对象都会调用 RateResource 方法以获取一个数值级别(从 0 到 100),它将就 RESTYPID 和事务本身确定候选资源的“适宜程度”。

资源分配器可通过分配资源级别为 100(完全适合)的候选资源,来提前终止分级循环。级别 100 通常会预留给匹配 RESTYID 且已正确登记的候选资源,除非资源分配器认为此登记是廉价操作。如果所有候选资源(如果有)的级别都是 0(不可用),则将调用 IDispenserDriver::CreateResource 来创建一个新资源(请参阅“实现 IDispenserDriver::CreateResource”一节)。

实现此方法的步骤如下:

检查 RESID(资源指针)中传递的资源是否适合所需的资源类型。如果结果是否定的,则在输出参数 RESOURCERATING* 中返回一个小于 100 的等级,0 可能是个合适的值。否则,请继续执行步骤 2。

通过查询 fRequiresTransactionEnlistment 的值来查看资源是否需要登记。如果值为 FALSE,则该资源将成为主要候选资源,在这种情况下,返回 100。如果值为 TRUE 且登记昂贵,则资源等级小于 100。

...if (fRequiresTransactionEnlistment == FALSE)
...{
......*pRating = 100;.........
...}
...else
...{
......// not enlisted
......*pRating = 50;
...}

如果成功,则返回 S_OK。

实现 IDispenserDriver::EnlistResource

作为资源分配过程的一部分,Holder 对象可能会调用 IDispenserDriver::EnlistResource 方法。调用这个方法的情况有两种:

如果调用应用程序组件对象运行在一个事务中,则 Holder 将调用资源分配器,并通过调用 IDispenserDriver::EnlistResource 方法来请求资源分配器将资源登记在该事务中。

如果 DispMan 要求注销事务中的特定资源,则 Holder 对象将调用此方法,以便让 RD 证实这种情况。为了指明这个查询,Holder 会将一个 0 值传递到 TRANSID 输入参数中。若一个事务对象执行了资源的前一种用法,而当前用法由一个非事务对象执行,就会发生这种情况。要使用该资源,就必须马上注销它。

为了实现这个方法,您需要使用两个 OLE 事务对象:Transaction 对象和 Export 对象。Transaction 对象表示 MS DTC 事务,Export 对象表示 RM 代理(或资源分配器)与资源管理器之间的连接。Export 对象包含资源管理器的事务管理器的名称和位置,用于在进程或系统之间传播事务。

请按照以下步骤来实现这个方法:

检查 TRANSID 的值。如果值为 0,则验证 RESID 所表示的资源未登记在事务中(这由您的特定实现决定)。如果结果为 TRUE,则返回 S_OK。

如果 TRANSID 的值不是 0,则将其强制转换为 ITransaction 指针接口 (ITransaction*)。

...ITransaction * pTransaction = (ITransaction*)TransId;

从全局接口表中获取一个指向资源管理器接口的指针(请参阅“实现 IDispenserDriver::CreateResource”一节中的步骤 2)。

查看您是否具有一个与 RESID 关联的 ITransactionExport 指针。保存 RESID 与 ITransactionExport* 的映射是个不错的主意,因为每次需要登记资源时都创建一个 Export 对象会非常昂贵。如果您没有可用的 Export 对象,请创建一个(更多相关信息,请参阅下一节“获取 ITransactionExport 接口指针”)。

...pExport = m_mapExport[ResId];
...if (pExport == NULL)
...{
// Create an Export object
hr = GetExportObject(ResId, pTransaction, &pExport);
......if (FAILED(hr))
......{
.........pRm->Release();
.........return hr;
......}
// Create a map entry between pExport and ResId.
......m_mapExport[ResId] = pExport;
...}

调用 ITransactionExport::Export 以导出该事务对象并得到事务 Cookie 的大小。

...ULONG... cbTransactionCookie = 0;
...hr = pExport->Export (pTransaction, &cbTransactionCookie);

根据事务 Cookie 的大小分配一个缓冲区。您可以使用 COM 函数 CoTaskMemAlloc。请经常检查这个函数的返回值。

...rgbTransactionCookie = (BYTE *) CoTaskMemAlloc (cbTransactionCookie);
...if (0 == rgbTransactionCookie)
...{
......pRm->Release();
......return E_FAIL;
...}

使用事务指针、事务 Cookie 和 Cookie 缓冲区指针调用 ITransactionExport::GetTransactionCookie,来获得事务 Cookie 的值。这个方法可提供输出参数中用于 cookie 的字节数。一旦这个方法返回,您便可以传播事务并在事务中登记资源。

...hr = pExport->GetTransactionCookie (...pTransaction, 
..................bTransactionCookie,
..................rgbTransactionCookie,
..................&cbUsed...);

为了保证将事务传播给 RM 并登记资源,您需要调用 RM 接口中的一个方法。这个方法应允许调用者传递一个 RESID、用于存储 Cookie 的字节数以及事务 Cookie 缓冲区。这个 RM 的方法将导入此 cookie 表示的事务,并调用 MS DTC 以获得 Transaction 对象并登记资源。

...hr = pRm->ExportTx (ResId, cbUsed, rgbTransactionCookie);

最后,释放分配的 Cookie 缓冲区,并释放指向该资源管理器的指针。

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

获取 ITransactionExport 接口指针

如果一个资源必须要登记到事务中,并且该资源没有可用的 Export 对象指针 (ITransactionExport*),则您必须创建一个 Export 对象。

请按照以下步骤来创建一个 Export 对象:

从全局接口表中获取一个指向资源管理器接口的指针(请参阅“实现 IDispenserDriver::CreateResource”一节中的步骤 2)。

RM 接口应提供一个获取 RM“下落”的方法。如果您的 RD 要从不同的地方使用多个 RM,则您在每次需要 Export 对象时都应调用这个方法。或者,调用这个方法一次并将其“下落”存储起来。此方法可能包含三个参数:RESID、BYTE** 和 ULONG*。在 BYTE** 中,RM 的方法应返回一个指向包含 RM“下落”的缓冲区的指针,在 ULONG* 中,应返回该缓冲区的大小。RD 必须调用这样一个方法来获取 RM 的位置。

...hr = pRm->GetWhereabouts (ResId, &rgbWhereabouts, &cbWhereabouts);

调用 Transaction 对象的 IUknown::QueryInterface,以获取一个 IGetDispenser 接口指针。

...hr = pTransaction->QueryInterface (IID_IGetDispenser, (LPVOID *) &pIDispenser);

调用 IGetDispenser::GetDispenser 方法,以获取一个 ITransactionExportFactory 接口指针。

...hr = pIDispenser->GetDispenser (IID_ITransactionExportFactory, (LPVOID *)&pTxExpFac );

释放您在步骤 3 中得到的 IGetDispenser*

调用 ITransactionExportFactory::Create 方法并传递“下落”缓冲区、该缓冲区的大小以及一个指向 ITransationExport* 的指针。这个方法将创建 Export 对象,并在输出参数中放置一个指向其接口的指针。

...hr = pTxExpFac->Create (cbWhereabouts, rgbWhereabouts, ppExport);

释放您在步骤 4 中得到的 ITransactionExportFactory*

通过调用 CoTaskMemFree′释放“下落”缓冲区。

...CoTaskMemFree (rgbWhereabouts);

最后,释放 RM 接口指针。

此时,RD 便可以继续执行前一节中描述的登记过程。

实现 IDispenserDriver::ResetResource

要将资源放回一般库或登记库时,Holder 对象将调用这个方法。这个方法可在资源进入库之前准备好资源。

请按照以下步骤来实现这个方法:

从全局接口表中获取一个指向资源管理器接口的指针(请参阅“实现 IDispenserDriver::CreateResource”一节中的步骤 2)。

调用 RM 接口中的特定方法来重置这个资源。这个方法可能有一个包含 RESID 的参数。

...hr = pRm -> ResetConnection((long)ResId);

清除可能在资源处于活动状态时设置的所有资源特定的状态数据。

最后,释放 RM 接口指针。

实现 IDispenserDriver::DestroyResource

要销毁资源时,Holder 对象将调用这个方法。

实现这个方法的步骤如下:

从全局接口表中获取一个指向资源管理器接口的指针(请参阅“实现 IDispenserDriver::CreateResource”一节中的步骤 2)。

调用 RM 接口中的特定方法来释放这个资源。这个方法可能有一个包含 RESID 的参数。

...hr = pRm -> Disconnect((long )ResId);

释放 RM 接口指针。

释放与 RESID 关联的 ITransactionExport 指针。

如果生成了用于保持 RESID 和ITransactionExport* 关联的映射项,请删除它。

   ITransactionExport   *pExport;
pExport = m_mapExport[ResId];
int nElements = m_mapExport.erase(ResId);
返回页首返回页首

释放资源分配器

如果您使用 ATL,则可以使用 FinalRelease 方法撤销在 FinalConstruct 方法的初始化过程中以及整个资源分配器的执行过程中所执行的“操作”。

要释放资源分配器的实例,请执行以下步骤:

通过调用方法 RevokeInterfaceFromGlobal 并传递您在初始化过程中得到的 cookie,从全局接口表中注销 RM 接口指针。

...hr =m_pGIT->RevokeInterfaceFromGlobal(m_dwRmPtrCookie);

释放指向 GIT 接口的指针并将这个成员变量设为 NULL。

针对指向 IDispenserManagerIHolder、资源管理器和自由线程封送拆收器的接口指针执行步骤 2。

返回页首返回页首

实现 DLL 入口点函数和导出函数

DLL 入口点函数有一个,名为 DllMain,导出函数有四个:DllCanUnloadNowDllGetClassObjectDllRegisterServerDllUnregisterServer。资源分配器应实现所有这些函数。通常,您希望使用借助 Visual C++ 5.x 中的 ATL Wizard 生成的实现。

ATL Application 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 EntryPoint
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;    // Okay
}
/////////////////////////////////////////////////////////////////////////////
// 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;
}
返回页首返回页首

附录 A:线程和封送处理问题

资源分配器作为单一对象

由于分配给资源分配器对象的 Holder 对象是一个维护资源池的对象,因此您只能拥有一个资源分配器实例。否则,多个 RD 实例就意味着多个 Holder 对象,也就是多个资源池。因此,RD 必须是单一对象。这意味着,所有客户端线程将使用同一个 RD 对象。

实现 RD 类时应十分谨慎。RD 类必须是完全可重输入且线程安全的,以便能够提供单一 RD 实例。此外,如果您计划为每个调用线程提供相同的类工厂实例,请确保将线程同步代码添加到 DllGetClassObject 导出函数的实现中。您的类工厂本身也需要认真编写,以便它为其所有客户端返回相同的 RD 指针。例如,引用计数必须是线程安全的。

幸运的是,ATL 可利用您在运行 ATL COM Application Wizard 和 ATL Object Wizard 时提供的 CComObjectRootEx 模板、DECLARE_CLASSFACTORY_SINGLETON 宏和 CComModule 类来处理这些问题。

使用自由线程的封送拆收器

支持这两种线程模式(STA 和 MTA)意味着,使用标准封送处理完成线程间的封送处理将导致性能损失,而这种损失是可以轻松避免的。在同一进程的不同线程间封送处理 RD 的接口指针时,客户端线程应能够访问该指针驻留的同一地址空间,这样,客户端就可以直接调用这个接口。与通过代理调用接口(在标准封送处理的情况下)相比,这在性能上有所提高。但是,在进程间执行封送处理时,您应使用标准封送处理。

为了轻松实现封送处理的切换,您的 RD 应调用 CoCreateFreeThreadedMarsharler 函数。此函数会将一个自由线程的封送拆收器对象聚合到您 RD 的对象中。此对象将根据执行调用的上下文来执行任一种封送处理。

使用全局接口表 (GIT)

聚合自由线程的封送拆收器时有一个问题。通常,您的 RD 对象在其成员变量中保存了指向其他对象(驻留在其他进程中或不是自由线程)的接口指针。当 RD 从不同于存储这些对象指针的线程中引用这些对象时,这个问题就变得显而易见了。因此,此类的任一调用都将导致产生错误 RPC_E_WRONG_THREAD 或某种不正确的结果。显然,这对于资源分配器而言是一种常见的情形,因为它们至少保存了指向其资源管理器的指针,可能还保存了指向其他对象的指针。

要解决这个问题,您应使用全局接口表对象。它允许进程中的任一单元(STA 或 MTA)访问在此进程其他单元的对象上实现的接口。因此,使用自由线程的封送拆收器和 GIT,您 RD 的性能将比使用标准封送处理作为线程间封送处理机制的性能更佳。

返回页首返回页首

附录 B:注册表项

以下代码是资源分配器所需的注册表项的一个示例。它们与任何其他 COM 组件没有区别。如果您计划将数据存储在注册表中,这里将是设置起始项默认值的地方。

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'      
         }
      }
   }
}

转到原英文页面


返回页首返回页首