テクニカル ノート 39: MFC/OLE オートメーションの実装
更新 : 2007 年 11 月
メモ : |
---|
次のテクニカル ノートは、最初にオンライン ドキュメントの一部とされてから更新されていません。結果として、一部のプロシージャおよびトピックが最新でないか、不正になります。最新の情報について、オンライン ドキュメントのキーワードで関係のあるトピックを検索することをお勧めします。 |
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 メンバ関数のプロパティを定義しています。ほとんどのオートメーション サーバーの作成には、通常はこれらの機能で十分です。
次の DISP_PROPERTY_NOTIFY や DISP_PROPERTY_PARAM などのマクロは、ClassWizard によってサポートされるマクロが適切でないときにだけ利用してください。
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 のデストラクタが既定でこの操作を行います。