January 18th, 2024

Implementing two-phase initialization with ATL

In an attempt to solve problems with exceptions thrown out of constructors that hand out COM references in your class implement with ATL, you might notice this nice extension point called Final­Construct() and use it for the second phase of two-phase construction.

// ATL - this code is wrong
class MyPage : public CComObjectRootEx<CComMultiThreadModel>,
    public CComCoClass<MyPage>,
    public IPage
{
public:
    DECLARE_PROTECT_FINAL_CONSTRUCT()

    HRESULT FinalConstruct() try
    {
        Application::LoadComponent(this, blah, blah);
        something_that_might_throw();
        return S_OK;
    }
    CATCH_RETURN();

    ⟦ ... ⟧
};

You thought you were clever and remembered that ATL runs the constructor with a reference count of zero, so you deferred the operations that use COM references to the Final­Construct(), and you used DECLARE_PROTECT_FINAL_CONSTRUCT() to ensure that Final­Construct() runs with a nonzero reference count.

However, if you look at how CComCoClass::CreateInstance uses Final­Construct(), you’ll see that it doesn’t really work for two-phase construction:

template<class Base>
/* static */
HRESULT WINAPI CComObject<Base>::CreateInstance(
    CComObject<Base>** pp) throw()
{
    ATLASSERT(*pp == NULL);
    if (pp == NULL)
        return E_POINTER;
    *pp = NULL;

    HRESULT hRes = E_OUTOFMEMORY;
    CComObject<Base>* p = NULL;
    ATLTRY(p = _ATL_NEW CComObject<Base>())
    if (p != NULL)
    {
        p->SetVoid(NULL);
        p->InternalFinalConstructAddRef();
        hRes = p->FinalConstruct();
        p->InternalFinalConstructRelease();
        if (hRes != S_OK) {
            delete p;
            p = NULL;
        }
    }
    *pp = p;
    return hRes;
}

Observe that if FinalConstruct() fails, the object is outright deleted; any AddRef that occurred during Final­Construct() won’t prevent the object’s destruction.

You will have to implement the two-phase construction manually.

class MyPage : public CComObjectRootEx<CComMultiThreadModel>,
    public CComCoClass<MyPage>,
    public IPage
{
public:
    HRESULT InitializeComponent() noexcept try
    {
        Application::LoadComponent(this, blah, blah);
        something_that_might_throw();
        return S_OK;
    }
    CATCH_RETURN();

    ⟦ ... ⟧
};

HRESULT CreateMyPage(CComObject<MyPage>** result)
{
    *result = NULL;

    CComObject<MyPage>* page;

    HRESULT hr = CComObject<MyPage>::CreateInstance(&page);
    if (FAILED(hr)) return hr;

    CComPtr<CComObject<MyPage>> pageRef(page);

    hr = page->InitializeComponent());
    if (FAILED(hr)) return hr;

    *result = pageRef.Detach();
    return S_OK;
}

The important things to note are

  • We bump the reference count from 0 to 1 (by putting it in a CComPtr) before calling Initialize­Component(), so that the COM references we hand out have a nonzero reference count.
  • We use a CComPtr so that the reference will be released automatically if Initialize­Component() throws an exception or returns a COM failure.
  • The CComPtr destructor does a Release() rather than a delete, so any extra references created by Initialize­Component() are honored.
Topics
Code

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

0 comments

Discussion are closed.