カタバミさんのプログラミングノート

日曜プログラマーがプログラミング関係のメモを記録するブログです。

IUnknownの実装例とATL CComPtr&CComQIPtrの動作確認

IUnknownを自前で実装する方法とATLメモリ管理クラスであるCComPtrクラスおよびCComQIPtrクラスの動作確認を兼ねたサンプルコードです。 main関数内部のコメントは主にOutputDebugStringで出力される内容です。

IUnknownインターフェイスではなくIClassFactoryインターフェイスを実装した理由はQueryInterfaceメソッドでそれっぽさを出したかったこととIClassFactoryインターフェイスのメソッド数が少なかったことだけです。

サンプルコード

#define STRICT
#include <Windows.h>

#include <atlbase.h>
#include <atlcom.h>

class CClassFactory : IClassFactory
{
private:
    ULONG m_cRef;
public:
    CClassFactory()
        : m_cRef(0)
    {
        OutputDebugString(TEXT("CClassFactory::CClassFactory()\r\n"));
    }

    ~CClassFactory()
    {
        OutputDebugString(TEXT("CClassFactory::~CClassFactory()\r\n"));
    }

    HRESULT STDMETHODCALLTYPE QueryInterface(REFIID riid, void **ppvObject)
    {
        OutputDebugString(TEXT("CClassFactory::QueryInterface()\r\n"));
        if (ppvObject == nullptr)
        {
            return E_POINTER;
        }
        if (IsEqualIID(riid, IID_IUnknown))
        {
            m_cRef++;
            OutputDebugString(TEXT(" IID_IUnknown\r\n"));
            *ppvObject = reinterpret_cast<IUnknown*>(this);
            return S_OK;
        }
        else if (IsEqualIID(riid, IID_IClassFactory))
        {
            m_cRef++;
            OutputDebugString(TEXT(" IID_IClassFactory\r\n"));
            *ppvObject = reinterpret_cast<IClassFactory*>(this);
            return S_OK;
        }
        return E_FAIL;
    }

    ULONG STDMETHODCALLTYPE AddRef()
    {
        OutputDebugString(TEXT("CClassFactory::AddRef()\r\n"));
        return ++m_cRef;
    }

    ULONG STDMETHODCALLTYPE Release()
    {
        if (--m_cRef == 0)
        {
            OutputDebugString(TEXT("CClassFactory::Release() <final>\r\n"));
            delete this;
            return 0;
        }
        else
        {
            OutputDebugString(TEXT("CClassFactory::Release()\r\n"));
            return m_cRef;
        }
    }

    HRESULT STDMETHODCALLTYPE CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObject)
    {
        OutputDebugString(TEXT("CClassFactory::CreateInstance()\r\n"));
        return E_NOTIMPL;
    }

    HRESULT STDMETHODCALLTYPE LockServer(BOOL fLock)
    {
        OutputDebugString(TEXT("CClassFactory::LockServer()\r\n"));
        return E_NOTIMPL;
    }
};

int main()
{
    CComPtr<IUnknown> pUnknown((IUnknown*)new CClassFactory());
    // CClassFactory::CClassFactory()
    // CClassFactory::AddRef()
    CComQIPtr<IClassFactory> pClassFactory(pUnknown);
    // CClassFactory::QueryInterface()
    //  IID_IClassFactory

    auto hr = pClassFactory->LockServer(TRUE); // E_NOTIMPL
    // CClassFactory::LockServer()

    return hr;
    // CClassFactory::Release()
    // CClassFactory::Release() <final>
    // CClassFactory::~CClassFactory()
}

備考

ATLメモリ管理クラスと自前のIUnknown

ATLメモリ管理クラスは一般にCOMインターフェイスの運用のために使用しますが、自前でIUnknown及びその派生インターフェイスを実装したクラスでも使用することができます。

IUnknown::QueryInterfaceと参照カウンタ

IUnknownインターフェイスを実装する場合、QueryInterfaceメソッドの成功時は参照カウンタをインクリメントする必要があります。これを忘れた場合、ATL::CComQIPtrのデストラクタでIUnknown::Releaseメソッド、CComPtrのデストラクタ等でIUnknown::Releaseメソッドが呼び出されて不正なメモリ操作(解放)が発生します。

参照カウンタの初期値

今回はクラスのメモリ管理を完全にATLに委ねたため、コンストラクタで指定する参照カウンタの値(初期値)は0としています。newで作成したポインタにもReleaseメソッドを実行する場合、参照カウンタを1としないと不正なメモリアクセスが発生します。

参考

docs.microsoft.com

docs.microsoft.com