📑

ATLやタイプライブラリを使用せずに IDispatch を実装する

2022/05/10に公開

COMのIDispatchインターフェースを実装したクラスを作成する場合、通常はATLおよびタイプライブラリ(.tlb)を用いて実装することになると思います。

しかし、Windows APIにはそれらを使用せずにIDispatchインターフェースを実装する手段が提供されています。

この記事ではそれを使用した例を示します。

CreateStdDispatch

CreateStdDispatch関数により、void *pvThisに対するプロキシオブジェクトを取得することが出来ます。

HRESULT CreateStdDispatch(
  IUnknown  *punkOuter,
  void      *pvThis,
  ITypeInfo *ptinfo,
  IUnknown  **ppunkStdDisp
);

用意することが困難そうなのはITypeInfo *ptinfoですが、これはCreateDispTypeInfo関数で取得できます。

CreateDispTypeInfo

CreateDispTypeInfo関数に必要な入力データとしては、INTERFACEDATA構造体(およびMETHODDATA構造体とPARAMDATA構造体)になります。

HRESULT CreateDispTypeInfo(
  INTERFACEDATA *pidata,
  LCID          lcid,
  ITypeInfo     **pptinfo
);

つまり、void *pvThisに対応するメタデータをINTERFACEDATA構造体に設定すれば良い訳です。

呼び出し対象のメソッド

以降はvoid *pvThisには、以下のクラスのオブジェクトを渡すと想定します。

class TargetClass
{
...
    virtual void dummy()
    {
    }
    virtual BSTR __stdcall Hello(BSTR name)
    {
        wprintf(L"\"Hello %s\"\r\n", name);
        return SysAllocString(L"bye");
    }
};

dmuuy()は無くても問題有りません。(後述のメソッドIDの理解のために置いているだけです)

呼び出し対象のメソッドは、戻り値および引数はVARIANTで使用できるものであれば問題有りません。
呼び出し規約も、__stdcallのほかに__cdeclでも使用できます。

重要なのは呼び出し対象のメソッドが virtual関数であること です。
つまり、CreateDispTypeInfo関数はvtbl経由でメソッドを呼び出します。

クラスはIUnknownを実装している必要はありません。

メタデータ

TargetClass::Helloに対応するメタデータは下記のようになります。

class TargetClass
{
    inline static PARAMDATA s_pd_Hello[] = {
        { const_cast<LPOLESTR>(L"name"), VT_BSTR},
    };
    inline static METHODDATA s_md[] =
    {
        {
            const_cast<LPOLESTR>(L"Hello"), s_pd_Hello,
            99, /* dispid */
            1, /* iMeth */
            CC_STDCALL,
            ARRAYSIZE(s_pd_Hello),
            DISPATCH_METHOD,
            VT_BSTR /* vtReturn */
        },
    };
    inline static INTERFACEDATA s_id = { s_md, ARRAYSIZE(s_md) };
...

dispidはメソッド群の中でユニークな値であれば問題有りません。(.idlで指定する値と同様です。)

注意すべきはメソッドID(iMeth)です。これはvtbl内のインデックスになります。
TargetClassでは、vtblの要素は下記になります。
0. dummy

  1. Hello

そのため、iMethは1にを設定します。
もしIUnknownインターフェースを実装した場合、その分ずらした値にする必要があります。

メンバー関数ポインターからメソッドIDを求める方法があれば良いのですが、思いつかなかったので手動で求めています。

他は特に説明は不要かと思います。

IDispatchオブジェクトを取得する

あとは関数を順番に呼び出すだけです。

    ITypeInfo* pti{};
    hr = CreateDispTypeInfo(&s_id, LOCALE_SYSTEM_DEFAULT, &pti);

    TargetClass target{};
    IUnknown* pUnk{};
    hr = CreateStdDispatch(nullptr, &target, pti, &pUnk);
    IDispatch* pDisp{};
    hr = pUnk->QueryInterface(&pDisp);

メソッド名は大文字小文字は区別しない様です。

完全なサンプルコード

長いので折りたたんでます。
#include <stdio.h>
#include <windows.h>
#include <assert.h>

class TargetClass
{
    inline static PARAMDATA s_pd_Hello[] = {
        { const_cast<LPOLESTR>(L"name"), VT_BSTR},
    };
    inline static METHODDATA s_md[] =
    {
        {
            const_cast<LPOLESTR>(L"Hello"), s_pd_Hello,
            99, /* dispid */
            1, /* iMeth */
            CC_STDCALL,
            ARRAYSIZE(s_pd_Hello),
            DISPATCH_METHOD,
            VT_BSTR /* vtReturn */
            },
    };
    inline static INTERFACEDATA s_id = { s_md, ARRAYSIZE(s_md) };

private:
    virtual void dummy()
    {
    }
    virtual BSTR __stdcall Hello(BSTR name)
    {
        wprintf(L"\"Hello %s\"\r\n", name);
        return SysAllocString(L"bye");
    }
public:
    static HRESULT CreateTypeInfo(ITypeInfo** ppti)
    {
        return CreateDispTypeInfo(&s_id, LOCALE_SYSTEM_DEFAULT, ppti);
    }
};

int main()
{
    HRESULT hr;
    ITypeInfo* pti{};
    hr = TargetClass::CreateTypeInfo(&pti);
    assert(SUCCEEDED(hr));

    TargetClass target{};
    IUnknown* pUnk{};
    hr = CreateStdDispatch(nullptr, &target, pti, &pUnk);
    assert(SUCCEEDED(hr));
    IDispatch* pDisp{};
    hr = pUnk->QueryInterface(&pDisp);
    assert(SUCCEEDED(hr));

    OLECHAR name[] = L"Hello";
    LPOLESTR names[] = { name };
    DISPID dispId{};
    hr = pDisp->GetIDsOfNames(IID_NULL, names, ARRAYSIZE(names), LOCALE_SYSTEM_DEFAULT, &dispId);
    assert(SUCCEEDED(hr));
    wprintf(L"dispid: %d\r\n", dispId);

    VARIANTARG args[1]{};
    V_VT(&args[0]) = VT_BSTR;
    V_BSTR(&args[0]) = SysAllocString(L"Bob");
    DISPPARAMS dp{};
    dp.rgvarg = args;
    dp.cArgs = ARRAYSIZE(args);
    VARIANT result{};
    hr = pDisp->Invoke(dispId, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_METHOD, &dp, &result, nullptr, nullptr);
    assert(SUCCEEDED(hr));

    wprintf(L"retval: %s\r\n", V_BSTR(&result));

    VariantClear(&args[0]);
    VariantClear(&result);

    pDisp->Release();
    pUnk->Release();
    pti->Release();
}

実行結果

dispid: 99
"Hello Bob"
retval: bye

Discussion