IUnknown_SetSite引用计数器行为

时间:2017-02-13 14:05:15

标签: c# c++ com interop

由于我目前需要在C#应用程序中使用Windows API IUnknown_SetSite(),因此我试图了解此功能的内部功能。我找到了this reference,后者又将我转发给this page,其中说明了以下内容:

  

对象应该保留在this指针上,调用IUnknown :: AddRef   这样做。如果对象已经有一个站点,它应该调用它   现有网站的IUnknown :: Release,保存新网站指针,然后调用   新网站的IUnknown :: AddRef。

现在,请考虑以下代码(假设我已在其他地方正确声明了Windows API' s原型,接口,GUID等,以及我正在使用的变量):

/* Create COM Object of ComClass_1 and get reference to its IUnknown */
CoCreateInstance(ref ComClass_1_id,
                 IntPtr.Zero,
                 (CLSCTX.CLSCTX_INPROC_SERVER | CLSCTX.CLSCTX_LOCAL_SERVER),
                 ref IID_IUNKNOWN,
                 out intptr_ComClass_1_IUnknown);

/* This actually is not necessary to understand the question itself, but
   keeps additional complexity from us (without the following line, the
   ComClass_1 object would destroy itself if the reference counter for its
   IUnknown interface would reach zero, and for this question, I would like
   to keep this aspect from being discussed) */
CoCreateInstance(ref ComClass_1_id,
                 IntPtr.Zero,
                 (CLSCTX.CLSCTX_INPROC_SERVER | CLSCTX.CLSCTX_LOCAL_SERVER),
                 ref IID_I_SOME_OTHER_INTERFACE,
                 out intptr_ComClass_1_ISomeOtherInterface);

/* Create COM Object of ComClass_2 and get reference to its IUnknown */
CoCreateInstance(ref ComClass_2_id,
                 IntPtr.Zero,
                 (CLSCTX.CLSCTX_INPROC_SERVER | CLSCTX.CLSCTX_LOCAL_SERVER),
                 ref IID_IUNKNOWN,
                 out intptr_ComClass_2_IUnknown);

/* Now set ComClass_1 object's site to ComClass_2 object */
IUnknown_SetSite(intptr_ComClass_1_IUnknown, intptr_ComClass_2_IUnknown);

我的问题其实很简单:

在第一行之后,ComClass_1对象的网站显然是ComClass_1对象本身。因此,如果我从字面上理解引用,那么在执行最后一行期间,该对象将在其自己的Release()接口上调用IUnknown。因此,我不能在以后发布该界面。

这没有意义恕我直言,根据我所做的一些测试,这不是真的(如果我的测试方法是正确的,ComClass_1对象' IUnknown参考计数器在执行最后一行期间不减少)。

但是,因为正确释放或不释放COM接口是至关重要的,所以我想知道那里发生了什么。问题可以归结为文档在表示" ...有网站时的含义......"。

就我个人而言,我认为它必须是#34; ...还有另一个网站,而不是对象本身......",但我非常想知道那里的COM /互操作专家是怎么想的关于它。

1 个答案:

答案 0 :(得分:1)

TL; DR:您的假设不正确,该对象不会自己创建为网站。只需调用SetSite,让对象处理与可能拥有的现有旧网站相关的生命周期问题。您只负责创建/ AddRef / QueryInterface。

的对象

对象通常不会将自己用作网站。对象站点通常以NULL开头。站点用于将两个对象连接在一起,连接是一种方式,并允许对象与其主机/所有者进行交互。您通常是自己实现网站需要网站的对象。

IUnknown_SetSite只是一个辅助函数,它所做的只是:

hr = ptr->QueryInterface(IObjectWithSite, &i1);
if (SUCCEEDED(hr))
{
  hr = i1->SetSite(unkSite);
  i1->Release();
  if (SUCCEEDED(hr)) return hr;
}
hr = ptr->QueryInterface(ISomeOtherInterfaceThatHasASetSiteMethod, &i2);
if (SUCCEEDED(hr)) ...
...

它所做的一切都是正常的COM生命周期管理,它尝试了几个接口,而不仅仅是IObjectWithSite。

如果对象实现了一个具有SetSite方法的接口,那么它的实现应该如下所示:

IUnknown *pOld = this->m_pSite;
if (pUnkSite) pUnkSite->AddRef();
this->m_pSite = pUnkSite;
if (pOld) pOld->Release();

...如果非null,则在销毁对象时释放this->m_pSitethis->m_pSite以NULL开头,因为该对象未连接到站点。如果对象是线程安全的,则在将新指针分配给InterlockedExchangePointer时将使用this->m_pSiteIUnknown_Set可以为SetSite的简单实现完成所有这些工作。 MSDN确实说实现应该首先发布然后发布AddRef,但是如果每个人都遵循COM规则,则顺序无关紧要。调用者已经拥有对他们传入的站点的引用,因此即使旧站点和新站点是同一个对象实例,它也不会被版本销毁。

答案其实很简单。您不必担心任何事情,SetSite实现将AddRef新站点并在不再需要站点时释放它。传递NULL和任何接口指针作为新站点除了对象本身(foo->SetSite(foo);)是安全的,因为这样对象永远不会被释放。多次使用相同指针调用SetSite也是安全的。