由于当前需要在C#应用程序中使用Windows API的IUnknown_SetSite(),因此我试图了解此函数的内部。我发现this reference将其转发给this page,其中指出:


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


现在,请考虑以下代码(假设我已经在其他位置正确声明了Windows API的原型,接口,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接口至关重要,因此我想确定到底发生了什么。问题归结为文档说“ ...拥有一个站点...”时的含义。

就我个人而言,我认为它一定是“ ...除了对象本身是……之外还有另一个站点”,但是我非常想知道COM / interop专家在那里对它的看法。

最佳答案

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也是安全的。

关于c# - IUnknown_SetSite引用计数器行为,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/42205981/

10-08 22:28