古のATLのWindows8以降?版のWRLを使ってみる。

IXMLHTTPRequest2を使うサンプルコードをベースにWRL化してみる。

ComPtr 何はともあれComPtrを取り入れる。 Before

#include <Msxml6.h>
#pragma comment(lib, "msxml6.lib")

#define SAFERELEASE(p){ if(p){p->Release(); p=nullptr;}}

int main(int, char **)
{
    CoInitializeEx(NULL, COINITBASE_MULTITHREADED);

    IXMLHTTPRequest2 *pXHR=nullptr;
    auto hr = CoCreateInstance(CLSID_FreeThreadedXMLHTTP60,
            NULL,
            CLSCTX_INPROC_SERVER,
            IID_PPV_ARGS(&pXHR));
    if (FAILED(hr)) {
        goto EXIT;
    }

EXIT:
    SAFERELEASE(pXHR);
    CoUninitialize();

    if (FAILED(hr)) {
        return 1;
    }
    return 0;
}

SAFERELEASEとgoto

ComPtrを取り入れてSAFERELEASEとgotoを除去しよう。 After RAIIを取り入れて積極的にEarly Outできる(後始末が自動になったので)。

#include <Msxml6.h>
#pragma comment(lib, "msxml6.lib")
#include <wrl/client.h>


class ComInitializer
{
public:
    ComInitializer()
    {
        CoInitializeEx(NULL, COINITBASE_MULTITHREADED);
    }
    ~ComInitializer()
    {
        CoUninitialize();
    }
};

int main(int, char **)
{
    ComInitializer co;

    Microsoft::WRL::ComPtr<IXMLHTTPRequest2> pXHR;
    auto hr = CoCreateInstance(CLSID_FreeThreadedXMLHTTP60,
            NULL,
            CLSCTX_INPROC_SERVER,
            IID_PPV_ARGS(&pXHR));
    if (FAILED(hr)) {
        return 1;
    }

    return 0;
}

IUnknown実装とComPtr初期化

Callbackの定義等で自らComオブジェクトを定義する場合がある。 IXMLHTTPRequest2Callbackを実装する例。 Before

class CCallback :public IXMLHTTPRequest2Callback
{
    ULONG m_cRef=1;

public:
    CCallback()
    {
    }

    ~CCallback()
    {
    }

    // IUnknown
    STDMETHODIMP_(ULONG) AddRef()override
    {
        InterlockedIncrement(&m_cRef);
        return m_cRef;
    }

    STDMETHODIMP_(ULONG) Release()override
    {
        ULONG ulRefCount = InterlockedDecrement(&m_cRef);
        if (0 == m_cRef)
        {
            delete this;
        }
        return ulRefCount;
    }

    STDMETHODIMP QueryInterface (REFIID riid, void **ppvObj)override
    {
        // Always set out parameter to NULL, validating it first.
        if (!ppvObj) return E_INVALIDARG;

        *ppvObj = NULL;
        if (riid == IID_IUnknown 
                || riid == IID_IXMLHTTPRequest2Callback 
           )
        {
            // Increment the reference count and return the pointer.
            *ppvObj = (LPVOID)this;
            AddRef();
            return NOERROR;
        }

        return E_NOINTERFACE;
    }

    // IXMLHTTPRequest2Callback
    STDMETHODIMP OnRedirect(
        __RPC__in_opt IXMLHTTPRequest2 *pXHR,
        __RPC__in_string const WCHAR *pwszRedirectUrl)override
    {
        return E_NOTIMPL;
    }

    STDMETHODIMP
        OnHeadersAvailable(
            __RPC__in_opt IXMLHTTPRequest2 *pXHR,
            DWORD dwStatus,
            __RPC__in_string const WCHAR *pwszStatus
        )override
    {
        return E_NOTIMPL;
    }

    STDMETHODIMP
        OnDataAvailable(
            __RPC__in_opt IXMLHTTPRequest2 *pXHR,
            __RPC__in_opt ISequentialStream *pResponseStream
        )override
    {
        return E_NOTIMPL;
    }

    STDMETHODIMP
        OnResponseReceived(
            __RPC__in_opt IXMLHTTPRequest2 *pXHR,
            __RPC__in_opt ISequentialStream *pResponseStream
        )override
    {
        return E_NOTIMPL;
    }

    STDMETHODIMP
        OnError(
            __RPC__in_opt IXMLHTTPRequest2 *pXHR,
            HRESULT hrError
        )override
    {
        return E_NOTIMPL;
    }
};

IUnknownの実装(AddRef, Release, QueryInterface)が定型コードである

newしたときにリファレンスカウントが1であること、AddRef, Releaseを正しく実装する QueryInterfaceを正しく実装する(あとでインタフェースを増減させたときに更新を忘れたりする)

ComPtrの初期化が不穏

Microsoft::WRL::ComPtr<CCallback> pCallback;
// RefCount=1のインスタンスを内部ポインタ(&演算子)に渡す
*((CCallback**)&pCallback)=new CCallback(); 

または、

Microsoft::WRL::ComPtr<CCallback> pCallback(new CCallback); // 1+1はRefCount=2
pCallback.Get()->Release(); // 1に減らす

のようなあからさまに不穏なコードを書かなければならない。 間違いの元である。 After

#include <wrl/implements.h>
class CCallback :
    public Microsoft::WRL::RuntimeClass<
    Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>, IXMLHTTPRequest2Callback>
{
public: 
    CCallback()
    {
    }

    ~CCallback()
    {
    }

    // IXMLHTTPRequest2Callback
    // 省略
};

とすることでIUnknownの実装をWRL::RuntimeClassに任せることができる。 また、newによる初期化を禁止されるので、newではなくWRL::Makeを使う。

error C2248: 'Microsoft::WRL::Details::DontUseNewUseMake::operator new': private メンバー (クラス 'Microsoft::WRL::Details::DontUseNewUseMake' で宣言されている) にアクセスできません。
Microsoft::WRL::ComPtr<CCallback> pCallback=Microsoft::WRL::Make<CCallback>();

MakeAndInitialize 初期化メソッド

Makeよりこっちの方がCom風。 RuntimeClassInitializeという名前のメンバ関数で初期化する。失敗した場合はS_OK以外を返す。

class CCallback :
    public Microsoft::WRL::RuntimeClass<
    Microsoft::WRL::RuntimeClassFlags<Microsoft::WRL::ClassicCom>, IXMLHTTPRequest2Callback>
{
public: 

    STDMETHODIMP RuntimeClassInitialize()
    {
        return S_OK;
    }

};

Microsoft::WRL::ComPtr<CCallback> pCallback;
hr=Microsoft::WRL::MakeAndInitialize<CCallback>(&pCallback);
if (FAILED(hr)) {
    return 2;
}

MakeAndInitialize 初期化メソッド(引数) 9つまでいける。

    STDMETHODIMP RuntimeClassInitialize(DWORD value)
    {
        return S_OK;
    }

    DWORD value = 255;
    Microsoft::WRL::ComPtr<CCallback> pCallback;
    hr=Microsoft::WRL::MakeAndInitialize<CCallback>(&pCallback, value);
    if (FAILED(hr)) {
        return 2;
    }