组件工具模子的法则
当前位置:以往代写 > C/C++ 教程 >组件工具模子的法则
2019-06-13

组件工具模子的法则

组件工具模子的法则

副标题#e#

摘要

本文的目标是为利用和实行Microsoft的组件工具模子(COM)提供迅捷的参考。读者若想更好的领略什么是COM,以及埋没在它的设计及体系中的念头,应该阅读组件工具模子的技能说明书(MSDN库,技能说明书)。

法则1:必需实现Iunknown

假如一个工具没有至少实现一个最小水平为IUnknown的接口,那它就不是Microsoft的组件工具模子(COM)。

接口设计法则

接口必需直接或间接地从IUnknown担任。

接口必需有独一的识别(IID)。

接口是稳定的。一旦分派和发布了IID,接口界说的任何因素都不能被改变。

接口的成员函数应该有HRESULT范例的返回值,使远端布局可陈诉长途进程挪用(RPC)错误的环境。

接口成员函数的字符串参数应该是Unicode。

实现 IUnknown

工具的同一性。这要求对任何特定IUnknown接口的给定工具实例的QueryInterface挪用返回沟通的物理指针变量。这导致了所谓的两个接口的QueryInterface(IID_IUnknown, …)和功效的较量,以确定它们是否为同一工具(COM工具同一性)。

静态接口的配置。任何经过QueryInterface来会见工具的接口的配置,必需是静态而不是动态的。也就是说,如果一旦QueryInterface得到了一个给定的IID,那么它老是对沟通的工具(除非有意想不到环境)挪用,如果QueryInterface不能得到一个给定的IID,那么随后对沟通IID的工具挪用肯定会失败。

工具完整性。对付可处理惩罚的接口配置,必需有反身性,对称性和过渡性。即给定代码如下:

IA * pA = (some function returning an IA*);
IB * pB = NULL;
HRESULT hr;
hr = pA->QueryInterface(IID_IB,&pB); // line 4
Symmetric: pA->QueryInterface(IID_IA, ...) must succeed (a>>a)
Reflexive: If, in line 4, pB was successfully obtained, then
pB->QueryInterface(IID_IA, ...)
must succeed (a>>b, then b>>a).
Transitive: If, in line 4, pB was successfully obtained, and we do
IC * pC = NULL;
hr = pB->QueryInterface(IID_IC, &pC); //Line 7
and pC is successfully obtained in line 7,then
pA->QueryInterface(IID_IC, ...)
must succeed (a>>b, and b>>c,then a>>c).

最小参考处事巨细。我们需要实现AddRef来维护一个处事台,它足够大以便支持给定工具的所有接口的2 31 –1有精彩的整体指示处事。一个32-位的无标记整型数满意要求。

Release并不料味着失败。如果客户想知道关于资源已被释放等环境,就必需在挪用Release之前利用一些工具接口中的较高的语义。


#p#副标题#e#

内存打点法则

接口指针的生命期打点老是通过成立在每个COM接口上的AddRef和Release要领来实现。(拜见下面的“引用计数法则”)

下面的法则合用于接口成员函数的参数,包罗不是“按值”通报的返回值。

对付参数来说,挪用措施应分派和释放内存。

出口参数必需由被挪用措施分派,由挪用措施用尺度的COM内存分派措施来释放。

进出参数首先由挪用措施分派,须要时由被挪用措施释放及重分派。至于出口参数,挪用措施有责任释放最终返回变量。此时必需利用尺度的COM内存分派措施。

如果函数返回挪用失败的代码,则凡是挪用者没步伐排除出口和入出口参数。这导致了一些附加法则:

错误返回时,出口参数必需靠得住地被配置成可排除变量,它不能对换用措施有影响。

另外,所有的出口指针参数(包罗挪用分派,被挪用委任布局)必需被明明地设为NULL。最直接的要领是在函数说明项中设成NULL。

返回错误时,所有的入出口参数必需为被挪用者所弃捐(这样保持为挪用措施初始化的值;若挪用措施没有对它初始化,则它是个出口参数,不是入出口参数),可能被明明地设为出口错误返回环境。

参考计数法则:

法则1:对付接口指针的每一个新的副本,AddRef必需被挪用;Release在接口指针的每一个粉碎时挪用,除了子法则明明答允了其他环境。

以下法则对应于法则1的非破例环境。

法则1a:函数的进口出口参数。挪用措施必需AddRef实际参数,因为当出口变量存放在它之上时,将由被调措施释放。

法则1b:获取全局变量。从全局变量的已存在的指针副本获得的接口指针的局部副本,必需被独立地引用计数。因为存在局部副本时,被调函数会粉碎全局副本。

法则1c:新指针合成所需资源不多。函数利用内涵常识合成接口指针,而不是从其他资源所得,此时必需对新指针做初始AddRef。这样的重要例子有事例生成法例,Iunknown::QueryInterface的实现,等等。

#p#分页标题#e#

法则1d:内部存储指针副本的返回。指针返回之后,被调措施不知道它的生命期和指针的内部存储副本如何接洽。所以,被调措施必需在返回前对指针副本挪用AddRef。

法则2:对付接口指针的两个或更多的副本,它们的生命期的起始和终了的干系代码的特定常识,使AddRef/Release可以被省略。

从COM客户的角度,引用计数是和接口对应的观念。客户不该认为工具的所有接口有同一引用计数。

不该依赖于Addref & Release的返回值,而应用于调试目标。

指针不变性;拜见在"Reference-Counting Rules"下的OLE辅佐文件中的子部门:"Stabilizing the this Pointer and Keeping it Valid"。

拜见Douglas Hodges写的优秀的技能文章"Managing Object Lifetimes in OLE",及Kraig Brockschmidt (MSDN Library, Books)写的Inside OLE的第三章来获取更多信息。

COM申请责任:

以客户,处事器,工具执行者之一身份利用COM的每一历程,要对三件事认真:

确定COM库是同COM函数CoBuildVersion一致的版本。

在利用其他函数之前通过挪用CoInitialize初始化COM库。

在不消CoUninitialize时打消COM库的初始化。

历程内处事器能假定载入的历程已执行了这些步调。

#p#副标题#e#

处事器法则

历程内处事器必需输出DllGetClassObject and DllCanUnloadNow。

历程内处事器必需支持COM自注册。

历程内和局部处事器应该在它们的文件版本信息中提供OLESelfReg字符串。

历程内处事器必需输出DllRegisterServer and DllUnRegisterServer。

局部处事器应支持/RegServer and /UnRegServer呼吁行开关。

生成荟萃工具

生成可合计的工具是可选的,且操纵简朴,有诸多益处。以下法则利用于建设可合计的工具(凡是称为内部工具)。

由QueryInterface, AddRef, 和 Release对IUnknown接口的内部工具执行单独节制内部接口的引用计数,且不能授权给外部未知指针。这种IUnknown执行称为隐式IUnknown。

内部工具执行接口的QueryInterface, AddRef, 和 Release成员的实行,除了IUnknown本身,都必需授权给外部未知指针。这些实施不能直接影响内部工具的参考计数。

隐式Iunknown只对内部工具实施QueryInterface操纵。

荟萃工具在占用外部未知指针参考时,不能挪用AddRef。

假如当工具建设时,需要除Iunknown外的任一接口,建设失败同E_UBKNOWN一起。

以下的代码段阐发了利用嵌套类来实现荟萃工具接口的典型:

// CSomeObject is an aggregatable object that implements
// IUnknown and ISomeInterface
class CSomeObject : public IUnknown
{
private:
DWORD m_cRef;// Object reference count
IUnknown* m_pUnkOuter; //Outer unknown, no AddRef
// Nested class to implement the ISomeInterface interface
class CImpSomeInterface: public ISomeInterface
{
friend class CSomeObject ;
private:
private:DWORD m_cRef; //Interface ref-count, for debugging
private:IUnknown*m_pUnkOuter; // Outerunknown, for delegation
private:public:
private:CImpSomeInterface() { m_cRef = 0; };
private:~ CImpSomeInterface(void) {};
private:// IUnknown members delegate to the outer unknown
private:// IUnknown members do not control lifetime of object
private:STDMETHODIMP QueryInterface(REFIID riid, void** ppv)
private:{ return m_pUnkOuter->QueryInterface(riid,ppv);};
private:STDMETHODIMP_(DWORD) AddRef(void)
private:{ return m_pUnkOuter->AddRef(); };
private:STDMETHODIMP_(DWORD) Release(void)
private:{ return m_pUnkOuter->Release();};
private:// ISomeInterface members
private:STDMETHODIMP SomeMethod(void)
private:{ return S_OK; };
private:} ;
private:CImpSomeInterface m_ImpSomeInterface ;
private:public:
private:CSomeObject(IUnknown * pUnkOuter)
{
m_cRef=0;
// No AddRef necessary if non-NULL as we're aggregated.
m_pUnkOuter=pUnkOuter;
m_ImpSomeInterface.m_pUnkOuter=pUnkOuter;
} ;
// Static member function for creating new instances (don't use
// new directly).Protects against outer objects asking for interfaces
// other than IUnknown
static HRESULT Create(IUnknown* pUnkOuter, REFIID riid, void **ppv)
{
CSomeObject* pObj;
if (pUnkOuter != NULL && riid != IID_IUnknown)
return CLASS_E_NOAGGREGATION;
pObj = new CSomeObject(pUnkOuter);
if (pObj == NULL)
return E_OUTOFMEMORY;
// Set up the right unknown for delegation (the non-aggregation
case)
if (pUnkOuter == NULL)
pObj->m_pUnkOuter = (IUnknown*)pObj ;
HRESULT hr;
if (FAILED(hr = pObj->QueryInterface(riid, (void**)ppv)))
delete pObj ;
return hr;
}
// Implicit IUnknown members, non-delegating
// Implicit QueryInterface only controls inner object
STDMETHODIMP QueryInterface(REFIID riid, void** ppv)
{
*ppv=NULL;
if (riid == IID_IUnknown)
*ppv=this;
if (riid == IID_ISomeInterface)
*ppv=&m_ImpSomeInterface;
if (NULL==*ppv)
return ResultFromScode(E_NOINTERFACE);
((IUnknown*)*ppv)->AddRef();
return NOERROR;
} ;
STDMETHODIMP_(DWORD)AddRef(void)
{ return++m_cRef; };
STDMETHODIMP_(DWORD)Release(void)
{
if (--m_cRef != 0)
return m_cRef;
delete this;
return 0;
};
};

#p#副标题#e#

荟萃工具

当在一个工具上发生另一个荟萃工具时,必需遵循以下法则:

当建设一个内部工具时,外部工具必需明晰的向Iunknown请求。

外部工具必需掩护重入时对粉碎代码的人工引用的Release实施。

假如外部工具查询任一内部工具接口,它必需挪用本身的未知Release。释放此指针时,外部工具紧随内部工具指针挪用本身的外部未知AddRef。

#p#分页标题#e#

// Obtaining inner object interface pointer
pUnkInner->QueryInterface(IID_IFoo,&pIFoo);
pUnkOuter->Release();
// Releasing inner object interface pointer
pUnkOuter->AddRef();
pIFoo->Release();

外部工具不能盲目地对内部工具的未被识别接口举办查询,除非操纵是为外部工具特定目标。

房间线程化模子

房间模子线程化的细节实际上很是简朴,可是必需小心地遵循以下法则:

每个工具存在于单线程中(在单独的房间中)。

所有对一工具的挪用必需基于本身的线程(在本身的房间中)。直接以后外线程中挪用工具是克制的。试图用空线程方法利用工具的申请,将在操纵系统的将来版本中赶上不能正确运行的问题。这条法则的寄义就是在房间之间,必需布置工具的所有指针。

为了处理惩罚从差异历程或同一历程的差异房间中的挪用,在工具中的每一房间/线程必需有一个动静行列。这就意味着线程的事情函数必需有一个GetMessage/DispatchMessage轮回。如果在线程之间有此外同步原语用来通信,那么Microsoft Win32的蕇gWaitForMultipleObjects将被用来期待动静和线程同步事件。

基于DLL或历程内的工具必需在注册表中标志为"房间识别",通过给注册数据库的InprocServer32要害字增添名为"ThreadingModel=Apartment"的变量实现。

房间识别工具应仔细填写DLL表项。对房间识别工具挪用CoCreateInstance的每一个房间将从它的线程挪用DllGetClassObject。故DllGetClassObject应能多级类工具或单线程安详工具。从任一线程挪用CoFreeUnusedLibraries,都通过主房间线程来挪用DllCanUnloadNow.。

    关键字:

在线提交作业