ATLやタイプライブラリを使用せずに IDispatch を実装する
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
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