Search code examples
c#c++cominterop

IUnknown_SetSite reference counter behavior


As I am currently in need to use Windows API's IUnknown_SetSite() from within a C# application, I am trying to understand this function's internals. I have found this reference which in turn forwarded me to this page which states the following:

The object should hold onto this pointer, calling IUnknown::AddRef in doing so. If the object already has a site, it should call that existing site's IUnknown::Release, save the new site pointer, and call the new site's IUnknown::AddRef.

Now, please consider the following code (assuming that I have correctly declared Windows API's prototypes, interfaces, GUIDs and so on elsewhere, as well as the variables I am using):

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

My question is actually simple:

After the first line, the ComClass_1 object's site obviously is the ComClass_1 object itself. Thus, if I take the reference literally, that object will call Release() on its own IUnknown interface during execution of the last line. Consequently, I must not release that interface later.

This does not make sense IMHO, and according to some tests I did, it is not true (if my test methods were correct, ComClass_1 object's IUnknown reference counter did not decrease during the execution of the last line).

But since correctly releasing or not releasing COM interfaces is crucial, I would like to know for sure what is going on there. The question boils down to what the documentation does mean when it says "... has a site ...".

Personally, I think that it must be "... has another site than the object itself is ...", but I strongly would like to know what the COM / interop experts out there think about it.


Solution

  • TL;DR: Your assumption is incorrect, the object is not created with itself as the site. Just call SetSite and let the object deal with the lifetime issues related to a existing old site it might have. You are only responsible for objects you create/AddRef/QueryInterface.


    A object generally never uses itself as a site. A objects site usually starts out as NULL. A site is used to connect two objects together, the connection is one way and lets the object interact with its host/owner. You are usually implementing the site or the object that needs a site yourself.

    IUnknown_SetSite is just a helper function, all it does is:

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

    everything it does is normal COM lifetime management and it tries a couple of interfaces, not just IObjectWithSite.

    If the object implements one of the interfaces that has a SetSite method, its implementation of it should look something like this:

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

    ...and if non-null, this->m_pSite is released when the object is destroyed. this->m_pSite starts out as NULL because the object is not connected to a site. If the object is thread safe it would use InterlockedExchangePointer when assigning the new pointer to this->m_pSite. IUnknown_Set can do all of this for simple implementations of SetSite. MSDN does say that the implementation should Release first and then AddRef but the order does not matter if everyone follows the COM rules. The caller already has a reference to the site they pass in so it cannot be destroyed by the Release even if the old site and the new site is the same object instance.

    The answer is actually quite simple. You don't have to worry about anything, the SetSite implementation will AddRef the new site and release it when it no longer needs a site. It is safe to pass NULL and any interface pointer as the new site except the object itself (foo->SetSite(foo);) because then the object would never be released. Calling SetSite with the same pointer multiple times is also safe.