TN039: MFC/OLE オートメーションの実装
Note
次のテクニカル ノートは、最初にオンライン ドキュメントの一部とされてから更新されていません。 結果として、一部のプロシージャおよびトピックが最新でないか、不正になります。 最新の情報について、オンライン ドキュメントのキーワードで関係のあるトピックを検索することをお勧めします。
OLE IDispatch インターフェイスの概要
IDispatch
インターフェイスは、他のアプリケーション (Visual BASIC など) や他の言語でアプリケーションの機能を利用できるようにアプリケーションがメソッドとプロパティを公開する手段です。 このインターフェイスの最も重要な部分は、IDispatch::Invoke
関数です。 MFC では、"ディスパッチ マップ" を使用して IDispatch::Invoke
を実装します。 このディスパッチ マップでは、オブジェクトのプロパティを直接操作したり、オブジェクト内のメンバー関数を呼び出して CCmdTarget
要求を満たしたりできるよう、IDispatch::Invoke
派生クラスのレイアウト(つまり "シェイプ") に関する MFC 実装情報を提供します。
ほとんどの部分では、ClassWizard と MFC が連携して、OLE オートメーションの詳細の大半がアプリケーション プログラマに見えないようにしています。 プログラマは、アプリケーションで公開する実際の機能に専念し、基になるしくみについて心配する必要はありません。
ただし、MFC がバックグラウンドで行っている処理を理解する必要がある場合もあります。 このノートでは、フレームワークがメンバー関数とプロパティに DISPID を割り当てるしくみについて説明します。 MFC が DISPID の割り当てに使用するアルゴリズムに関する知識は、アプリケーションのオブジェクトに "タイプ ライブラリ" を作成する場合など、ID を把握する必要がある場合にのみ必要です。
MFC の DISPID の割り当て
オートメーションのエンドユーザー (Visual Basic ユーザーなど) には、コード内でオートメーションが有効になっているプロパティとメソッドの実際の名前 (obj.ShowWindow など) が表示されますが、IDispatch::Invoke
の実装では実際の名前が表示されません。 最適化の理由から、アクセスするメソッドまたはプロパティを記述する 32 ビットの "マジック Cookie" である DISPID を受け取ります。 これらの DISPID 値は、IDispatch::GetIDsOfNames
と呼ばれる別のメソッドを介して IDispatch
実装から返されます。 オートメーション クライアント アプリケーションは、アクセスするメンバーまたはプロパティごとに 1 回 GetIDsOfNames
を呼び出し、後で IDispatch::Invoke
を呼び出すためにキャッシュします。 このように、負荷のかかる文字列検索は、IDispatch::Invoke
の呼び出しごとに 1 回ではなく、オブジェクトの使用ごとに 1 回だけ実行されます。
MFC は、次の 2 つの点に基づいて、各メソッドおよびプロパティの 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 プロパティは、プロパティを公開する CDisp3DPoint クラスで定義されているため、HIWORD が 0 の DISPID が指定されます。 X プロパティと Y プロパティは基底クラスで定義されているため、DISPID の HIWORD は 1 になります。これは、これらのプロパティが定義されているクラスが、最派生クラスから派生 1 つ分の距離にあるためです。
Note
LOWORD は、明示的な DISPID が設定されたエントリがマップ内に存在する場合でも、常にマップ内の位置によって決まります (DISP_PROPERTY
および DISP_FUNCTION
マクロの _ID バージョンについては、次のセクションを参照してください)。
MFC ディスパッチ マップの高度な機能
今回リリースの Visual C++ では、ClassWizard でサポートされない追加機能が多数あります。 ClassWizard では、それぞれメソッド、メンバー変数プロパティ、get/set メンバー関数プロパティを定義する DISP_FUNCTION
、DISP_PROPERTY
、DISP_PROPERTY_EX
をサポートしています。 通常、これらの機能があれば、ほとんどのオートメーション サーバーを作成するには十分です。
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_ の文字列。
解説
これらのマクロを使用すると、DISPID を MFC で自動的に割り当てるのではなく、指定できます。 これらの高度なマクロには、ID がマクロ名に追加される点 (例: DISP_PROPERTY_ID) を除き、同じ名前が付けられます。この ID は pszName パラメーターの直後に指定されたパラメーターによって決まります。 これらのマクロの詳細については、AFXDISP.H を参照してください。 _ID エントリは、ディスパッチ マップの末尾に配置する必要があります。 これらは、マクロの _ID 以外のバージョンと同じように、DISPID の自動生成に影響します (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
デストラクターは既定でこれを実行します)。