テクニカル ノート 39: MFC/OLE オートメーションの実装
注意
次のテクニカル ノートは、最初にオンライン ドキュメントの一部とされてから更新されていません。 結果として、一部のプロシージャおよびトピックが最新でないか、不正になります。 最新の情報について、オンライン ドキュメントのキーワードで関係のあるトピックを検索することをお勧めします。
OLE IDispatch インターフェイスの概要
IDispatch インターフェイスは、アプリケーションのメソッドやプロパティを公開して、Visual BASIC などのほかのアプリケーションや言語から利用できるようにします。 このインターフェイスの最も重要な部分は IDispatch::Invoke 関数です。 IDispatch::Invoke 関数は、"ディスパッチ マップ" を使用して実現されています。 ディスパッチ マップは、CCmdTarget 派生クラスのレイアウトつまり "形" に関する情報を提供します。これによって、IDispatch::Invoke 要求に応じて、直接オブジェクトのプロパティを操作したり、オブジェクトのメンバー関数を呼び出すことができます。
ClassWizard と MFC (Microsoft Foundation Class) によって、OLE オートメーションの詳細のほとんどはアプリケーション プログラマから見えません。 プログラマが考慮する必要があるのは、アプリケーションで公開する機能だけです。そのための内部機構を意識する必要はありません。
しかし、場合によっては、MFC の内部的な処理についても理解する必要があります。 このテクニカル ノートでは、メンバー関数やプロパティへの DISPID の割り当てについて説明します。 オブジェクトの "タイプ ライブラリ" を作成するときなど、メンバー関数やプロパティの ID を知る必要がある場合は、DISPID の割り当てアルゴリズムを理解する必要があります。
MFC の DISPID 割り当て
オートメーションのエンド ユーザー (Visual Basic のユーザーなど) は、オートメーション対応のプロパティやメソッドの名前を直接コード中に記述しますが (obj.ShowWindow など)、IDispatch::Invoke 関数は名前を直接受け取らず、 効率を上げるため DISPID (アクセスするメソッドやプロパティを示す 32 ビットの番号) を受け取ります。 DISPID 値は、IDispatch 機構の IDispatch::GetIDsOfNames 関数によって得ることができます。 オートメーション クライアント アプリケーションは、アクセスするプロパティやメンバーごとに 1 回ずつ GetIDsOfNames 関数を呼び出し、後で IDispatch::Invoke 関数を呼び出すときに利用します。 これによって、IDispatch::Invoke 関数の呼び出しごとに処理に時間のかかる文字列の検索をする必要がなくなり、オブジェクトごとに 1 回で済むようになります。
DISPID は次のような要素から決定されます。
ディスパッチ マップの先頭からの距離。1 から数えます。
最初にディスパッチ マップの定義されたクラスからの派生回数。0 から数えます。
DISPID は、2 つの部分から構成されます。 DISPID の LOWORD 部分は最初の要素 (ディスパッチ マップの先頭からの距離) を表します。 HIWORD は、最初にディスパッチ マップの定義されたクラスからの派生回数を表します。 次に例を示します
class CDispPoint : public CCmdTarget
{
public:
short m_x, m_y;
...
DECLARE_DISPATCH_MAP()
...
};
class CDisp3DPoint : public CDispPoint
{
public:
short m_z;
...
DECLARE_DISPATCH_MAP()
...
};
BEGIN_DISPATCH_MAP(CDispPoint, CCmdTarget)
DISP_PROPERTY(CDispPoint, "x", m_x, VT_I2)
DISP_PROPERTY(CDispPoint, "y", m_y, VT_I2)
END_DISPATCH_MAP()
BEGIN_DISPATCH_MAP(CDisp3DPoint, CDispPoint)
DISP_PROPERTY(CDisp3DPoint, "z", m_z, VT_I2)
END_DISPATCH_MAP()
これらの 2 つのクラスはいずれも OLE オートメーション インターフェイスを公開しています。 一方のクラスをもう一方のクラスから派生させているので、OLE オートメーション部分 (この例では "x" と "y") を含むいくつかの機能を共有しています。
MFC によって作成される CDispPoint クラス用の DISPID は次のようになります。
property X (DISPID)0x00000001
property Y (DISPID)0x00000002
これらのプロパティは基本クラスにはないので、DISPID の HIWORD は 0 となります (CDispPoint クラスの最派生クラスまでの派生の深さが 0 であるため)。
MFC によって作成される CDisp3DPoint クラス用の DISPID は次のようになります。
property Z (DISPID)0x00000001
property X (DISPID)0x00010001
property Y (DISPID)0x00010002
Z プロパティの DISPID の HIWORD は 0 になります。これは、プロパティを公開しているクラス CDisp3DPoint で、そのプロパティの定義が行われているからです。 X プロパティと Y プロパティは基本クラスで定義されているので、DISPID の HIWORD は 1 となります。これらのプロパティが定義されているクラスから最派生クラスまでの派生の深さが 1 だからです。
注意
LOWORD の内容は常にマップ上の位置で決まります。マップ上に明示的な DISPID を持つエントリがある場合も同じです。_ID を使った DISP_PROPERTY マクロや DISP_FUNCTION マクロについては、次のセクションを参照してください。
高度なディスパッチ マップ機能
Visual C++ のこのリリースの ClassWizard では、ディスパッチ マップの多くの機能がサポートされていません。 サポートされている機能は DISP_FUNCTION、DISP_PROPERTY、DISP_PROPERTY_EX だけであり、それぞれメソッド、メンバー変数、get/set メンバー関数のプロパティを定義しています。 ほとんどのオートメーション サーバーの作成には、通常はこれらの機能で十分です。
ClassWizard のサポートされているマクロが適切でない場合、次の追加のマクロを使用できます。DISP_PROPERTY_NOTIFY、および DISP_PROPERTY_PARAM
DISP_PROPERTY_NOTIFY - マクロの説明
DISP_PROPERTY_NOTIFY(
theClass,
pszName,
memberName,
pfnAfterSet,
vtPropType
)
解説
パラメーター
theClass
クラス名。pszName
プロパティの外部名。memberName
プロパティが格納されているメンバー変数の名前。pfnAfterSet
プロパティに変更があったときに呼び出されるメンバー関数の名前。vtPropType
プロパティの型を指定する値。
解説
このマクロは DISP_PROPERTY マクロに似ていますが、引数の数が多い点が異なります。 引数 pfnAfterSet, には、戻り値やパラメーターを持たない "void OnPropertyNotify()" 形式のメンバー関数を指定します。 このメンバー関数は、メンバー変数が変更された後に呼び出されます。
DISP_PROPERTY_PARAM - マクロの説明
DISP_PROPERTY_PARAM(
theClass,
pszName,
pfnGet,
pfnSet,
vtPropType,
vtsParams
)
解説
パラメーター
theClass
クラス名。pszName
プロパティの外部名。memberGet
プロパティを取得するために使用するメンバー関数名。memberSet
プロパティを設定するために使用するメンバー関数名。vtPropType
プロパティの型を指定する値。vtsParams
各パラメーターに対応する、空白で区切った VTS_ の並び。
解説
DISP_PROPERTY_EX マクロに似ていますが、このマクロは異なる Get メンバー関数と Set メンバー関数でアクセスするプロパティを定義します。 このマクロではプロパティのパラメーター リストを指定できます。 このマクロは、インデックス処理されたプロパティやパラメーター化されたプロパティを実装する場合に便利です。 パラメーターは必ず最初に置き、その後にプロパティの新しい値を続けます。 次に例を示します
DISP_PROPERTY_PARAM(CMyObject, "item", GetItem, SetItem, VT_DISPATCH, VTS_I2 VTS_I2)
この例は、次の get メンバー関数、set メンバー関数に対応します。
LPDISPATCH CMyObject::GetItem(short row, short col)
void CMyObject::SetItem(short row, short col, LPDISPATCH newValue)
DISP_XXXX_ID - マクロの説明
DISP_FUNCTION_ID(
theClass,
pszName,
dispid,
pfnMember,
vtRetVal,
vtsParams
)
DISP_PROPERTY_ID(
theClass,
pszName,
dispid,
memberName,
vtPropType
)
DISP_PROPERTY_NOTIFY_ID(
theClass,
pszName,
dispid,
memberName,
pfnAfterSet,
vtPropType
)
DISP_PROPERTY_EX_ID(
theClass,
pszName,
dispid,
pfnGet,
pfnSet,
vtPropType
)
DISP_PROPERTY_PARAM_ID(
theClass,
pszName,
dispid,
pfnGet,
pfnSet,
vtPropType,
vtsParams
)
解説
パラメーター
theClass
クラス名。pszName
プロパティの外部名。dispid
プロパティやメソッドに付ける DISPID。pfnGet
プロパティを取得するために使用するメンバー関数名。pfnSet
プロパティを設定するために使用するメンバー関数名。memberName
プロパティとのマップに使われるメンバー関数名。vtPropType
プロパティの型を指定する値。vtsParams
各パラメーターに対応する、空白で区切った VTS_ の並び。
解説
これらのマクロを使用すると、MFC によって自動的に割り当てられる DISPID の代わりに、指定した DISPID を使用できます。 これらの上級マクロの名前は、元のマクロの名前に ID を追加したものです ( DISP_PROPERTY_ID など)。また、その ID はパラメーター pszName の直後のパラメーターで指定します。 これらのマクロについては、AFXDISP.H を参照してください。 _ID エントリは、ディスパッチ マップの最後に置きます。 このエントリは、DISPID の自動生成に反映されます。つまり、非 _ID マクロと同じように行われます (DISPID はディスパッチ マップ中の位置で決まります)。 次に例を示します
BEGIN_DISPATCH_MAP(CDisp3DPoint, CCmdTarget)
DISP_PROPERTY(CDisp3DPoint, "y", m_y, VT_I2)
DISP_PROPERTY(CDisp3DPoint, "z", m_z, VT_I2)
DISP_PROPERTY_ID(CDisp3DPoint, "x", 0x00020003, m_x, VT_I2)
END_DISPATCH_MAP()
MFC によって作成される CDisp3DPoint クラス用の DISPID は次のようになります。
property X (DISPID)0x00020003
property Y (DISPID)0x00000002
property Z (DISPID)0x00000001
固定的な DISPID を指定すると、既存のディスパッチ マップ インターフェイスとの下位互換性を維持できます。また、システム定義のメソッドやプロパティを実装するときにも便利です (DISPID_NEWENUM コレクションなどのように、通常負の DISPID となります)。
COleClientItem 用の IDispatch インターフェイスの取得
多くのサーバーでは、OLE サーバー機能だけでなく、サーバー側のドキュメント オブジェクトに対するオートメーションもサポートされています。 このようなオートメーション インターフェイスにアクセスするには、COleClientItem::m_lpObject メンバー変数に直接アクセスする必要があります。 次のコードは、COleClientItem の派生クラスのオブジェクトの IDispatch インターフェイスを取得します。 必要なときは次のコードをアプリケーションに追加してください。
LPDISPATCH CMyClientItem::GetIDispatch()
{
ASSERT_VALID(this);
ASSERT(m_lpObject != NULL);
LPUNKNOWN lpUnk = m_lpObject;
Run(); // must be running
LPOLELINK lpOleLink = NULL;
if (m_lpObject->QueryInterface(IID_IOleLink,
(LPVOID FAR*)&lpOleLink) == NOERROR)
{
ASSERT(lpOleLink != NULL);
lpUnk = NULL;
if (lpOleLink->GetBoundSource(&lpUnk) != NOERROR)
{
TRACE0("Warning: Link is not connected!\n");
lpOleLink->Release();
return NULL;
}
ASSERT(lpUnk != NULL);
}
LPDISPATCH lpDispatch = NULL;
if (lpUnk->QueryInterface(IID_IDispatch, &lpDispatch)
!= NOERROR)
{
TRACE0("Warning: does not support IDispatch!\n");
return NULL;
}
ASSERT(lpDispatch != NULL);
return lpDispatch;
}
返されたディスパッチ インターフェイスは直接使用できますが、タイプ セーフなアクセスを行うために COleDispatchDriver に付加して使用することもできます。 直接ディスパッチ インターフェイス使用するときに、ポインターを使用する場合は、必ず Release メンバー関数を呼ぶようにしてください。COleDispatchDriver のデストラクターが既定でこの操作を行います。