次の方法で共有


テクニカル ノート 65: OLE オートメーション サーバー用デュアル インターフェイス サポート

更新 : 2007 年 11 月

4h56szat.alert_note(ja-jp,VS.90).gifメモ :

次のテクニカル ノートは、最初にオンライン ドキュメントの一部とされてから更新されていません。結果として、一部のプロシージャおよびトピックが最新でないか、不正になります。最新の情報について、オンライン ドキュメントのキーワードで関係のあるトピックを検索することをお勧めします。

ここでは、MFC (Microsoft Foundation Class) ベースの OLE オートメーション サーバーのアプリケーションへのデュアル インターフェイス サポートの追加方法を説明します。ACDUAL のサンプルでは、デュアル インターフェイス サポートを例示しており、このコード例は ACDUAL からとられています。このテクニカル ノートで説明する DECLARE_DUAL_ERRORINFO、DUAL_ERRORINFO_PART、IMPLEMENT_DUAL_ERRORINFO などのマクロは、ACDUAL のサンプルに含まれており、MFCDUAL.H ファイルに記述されています。

デュアル インターフェイス

OLE オートメーションを使用して IDispatch インターフェイス、VTBL インターフェイス、または (その両方を包含する) デュアル インターフェイスを実装できますが、Microsoft では、すべての公開された OLE オートメーション オブジェクトに対してデュアル インターフェイスを実装することを強くお勧めします。デュアル インターフェイスには、IDispatchのみのインターフェイスや VTBL のみのインターフェイスに比べて、次のような利点があります。

  • VTBL インターフェイスを介してコンパイル時に、または IDispatch インターフェイスを介して実行時にバインディングできます。

  • VTBL インターフェイスを使用可能な OLE オートメーション コントローラはパフォーマンス向上の恩恵を受けます。

  • IDispatch インターフェイスを使う既存の OLE オートメーション コントローラも動作します。

  • VTBL インターフェイスは C++ からより容易に呼び出すことができます。

  • Visual Basic オブジェクト サポート機能との互換性を確保するには、デュアル インターフェイスが必要です。

CCmdTarget に基いたクラスへのデュアル インターフェイス サポートの追加

デュアル インターフェイスは、実際には IDispatch から派生した単なるカスタム インターフェイスです。CCmdTarget に基づいたクラスでデュアル インターフェイス サポートを実装する最も簡単な方法は、まず MFC と ClassWizard を使用してユーザー定義クラスに通常のディスパッチ インターフェイスを実装してから、後でカスタム インターフェイスを追加する方法です。ほとんどの場合、ユーザー定義のカスタム インターフェイスの実装は、単に MFC の IDispatch 実装に処理を任せるだけです。

まず、サーバー用の ODL ファイルを変更して、オブジェクト用のデュアル インターフェイスを定義します。デュアル インターフェイスを定義するには、Visual C++ ウィザードで生成される DISPINTERFACE ステートメントではなく、インターフェイス ステートメントを使用する必要があります。既存の DISPINTERFACE ステートメントを削除するのではなく、インターフェイス ステートメントを新規に追加します。DISPINTERFACE フォームを残しておくことで、引き続き ClassWizard を使用してオブジェクトにプロパティやメソッドを追加できますが、追加したインターフェイス ステートメントにも同様のプロパティとメソッドを追加する必要があります。

デュアル インターフェイス用のインターフェイス ステートメントには、OLEAUTOMATION 属性と DUAL 属性が必要であり、インターフェイスは IDispatch から派生する必要があります。GUIDGEN のサンプルを使用して、次のようにデュアル インターフェイス用のインターフェイス ID (IID: Interface Identifier) を作成できます。

[ uuid(0BDD0E81-0DD7-11cf-BBA8-444553540000), // IID_IDualAClick
   oleautomation,
   dual
]
interface IDualAClick : IDispatch
  {
  };

インターフェイス ステートメントを記述したら、メソッドとプロパティのエントリを追加します。デュアル インターフェイスについては、パラメータの並びを再編成して、デュアル インターフェイス内のメソッドとプロパティ アクセス関数が HRESULT を返し、戻り値を属性 [retval,out] を持つパラメータとして渡すようにする必要があります。プロパティについては、同じ ID を持つ読み出し用アクセス関数 (propget) と書き込み用アクセス関数 (propput) の両方が必要です。次に例を示します。

[propput, id(1)] HRESULT text([in] BSTR newText);
[propget, id(1)] HRESULT text([out, retval] BSTR* retval);

メソッドとプロパティを定義したら、coclass ステートメントの中にインターフェイス ステートメントへの参照を追加する必要があります。次に例を示します。

[ uuid(4B115281-32F0-11cf-AC85-444553540000) ]
coclass Document
{
   dispinterface IAClick;
   [default] interface IDualAClick;
};

ODL ファイルを更新したら、MFC のインターフェイス マップ機構を使用してユーザー定義オブジェクトのクラスのデュアル インターフェイス用実装クラスを定義し、MFC の QueryInterface 機構内の対応するエントリを作成します。ディスパッチ インターフェイスのエントリに加え、ODL のインターフェイス ステートメント内のエントリ 1 つにつき INTERFACE_PART ブロック内に 1 つのエントリが必要です。propput 属性を持つ ODL エントリには、それぞれに put_propertyname という名前の関数が必要です。propget 属性を持つ ODL エントリには、それぞれに get_propertyname という名前の関数が必要です。

デュアル インターフェイス用実装クラスを定義するには、オブジェクトのクラス定義に DUAL_INTERFACE_PART ブロックを追加します。次に例を示します。

BEGIN_DUAL_INTERFACE_PART(DualAClick, IDualAClick)
  STDMETHOD(put_text)(THIS_ BSTR newText);
  STDMETHOD(get_text)(THIS_ BSTR FAR* retval);
  STDMETHOD(put_x)(THIS_ short newX);
  STDMETHOD(get_x)(THIS_ short FAR* retval);
  STDMETHOD(put_y)(THIS_ short newY);
  STDMETHOD(get_y)(THIS_ short FAR* retval);
  STDMETHOD(put_Position)(THIS_ IDualAutoClickPoint FAR* newPosition);
  STDMETHOD(get_Position)(THIS_ IDualAutoClickPoint FAR* FAR* retval);
  STDMETHOD(RefreshWindow)(THIS);
  STDMETHOD(SetAllProps)(THIS_ short x, short y, BSTR text);
  STDMETHOD(ShowWindow)(THIS);
END_DUAL_INTERFACE_PART(DualAClick)

デュアル インターフェイスを MFC の QueryInterface 機構に結び付けるには、以下のようにインターフェイス マップに INTERFACE_PART エントリを追加します。

BEGIN_INTERFACE_MAP(CAutoClickDoc, CDocument)
  INTERFACE_PART(CAutoClickDoc, DIID_IAClick, Dispatch)
  INTERFACE_PART(CAutoClickDoc, IID_IDualAClick, DualAClick)
END_INTERFACE_MAP()

次に、このインターフェイスを実装する必要があります。一般に、既存の MFC の IDispatch 実装に渡すことができます。次に例を示します。

STDMETHODIMP_(ULONG) CAutoClickDoc::XDualAClick::AddRef()
{
   METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
   return pThis->ExternalAddRef();
}
STDMETHODIMP_(ULONG) CAutoClickDoc::XDualAClick::Release()
{
   METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
   return pThis->ExternalRelease();
}
STDMETHODIMP CAutoClickDoc::XDualAClick::QueryInterface(
             REFIID iid, LPVOID* ppvObj)
{
   METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
   return pThis->ExternalQueryInterface(&iid, ppvObj);
}
STDMETHODIMP CAutoClickDoc::XDualAClick::GetTypeInfoCount(
            UINT FAR* pctinfo)
{
   METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
   LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
   ASSERT(lpDispatch != NULL);
   return lpDispatch->GetTypeInfoCount(pctinfo);
}
STDMETHODIMP CAutoClickDoc::XDualAClick::GetTypeInfo(
          UINT itinfo, LCID lcid, ITypeInfo FAR* FAR* pptinfo)
{
   METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
   LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
   ASSERT(lpDispatch != NULL);
   return lpDispatch->GetTypeInfo(itinfo, lcid, pptinfo);
}
STDMETHODIMP CAutoClickDoc::XDualAClick::GetIDsOfNames(
       REFIID riid, OLECHAR FAR* FAR* rgszNames, UINT cNames,
       LCID lcid, DISPID FAR* rgdispid) 
{
   METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
   LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
   ASSERT(lpDispatch != NULL);
   return lpDispatch->GetIDsOfNames(riid, rgszNames, cNames, 
                                    lcid, rgdispid);
}
STDMETHODIMP CAutoClickDoc::XDualAClick::Invoke(
    DISPID dispidMember, REFIID riid, LCID lcid, WORD wFlags,
    DISPPARAMS FAR* pdispparams, VARIANT FAR* pvarResult,
    EXCEPINFO FAR* pexcepinfo, UINT FAR* puArgErr)
{
   METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
   LPDISPATCH lpDispatch = pThis->GetIDispatch(FALSE);
   ASSERT(lpDispatch != NULL);
   return lpDispatch->Invoke(dispidMember, riid, lcid,
                             wFlags, pdispparams, pvarResult,
                             pexcepinfo, puArgErr);
}

オブジェクトのメソッドおよびプロパティのアクセス関数についても実装する必要があります。一般に、ユーザー定義のメソッドとプロパティ関数は、ClassWizard を使用して生成したメソッドに渡すことができます。ただし、変数に直接アクセスするようにプロパティを設定する場合は、変数の値を取得または代入するコードを記述する必要があります。次に例を示します。

STDMETHODIMP CAutoClickDoc::XDualAClick::put_text(BSTR newText)
{
   METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
   // MFC automatically converts from Unicode BSTR to 
   // Ansi CString, if necessary...
   pThis->m_str = newText;
   return NOERROR;
}
STDMETHODIMP CAutoClickDoc::XDualAClick::get_text(BSTR* retval)
{
   METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
   // MFC automatically converts from Ansi CString to 
   // Unicode BSTR, if necessary...
   pThis->m_str.SetSysString(retval);
   return NOERROR;
}

デュアル インターフェイスのポインタ渡し

デュアル インターフェイスのポインタ渡しは、特に CCmdTarget::FromIDispatch を呼び出す必要がある場合は、単純ではありません。FromIDispatch は MFC のIDispatch ポインタについてのみ動作します。これに対処する方法の 1 つは、MFC で設定された元の IDispatch ポインタをクエリし、必要としている関数にそのポインタを渡すことです。次に例を示します。

STDMETHODIMP CAutoClickDoc::XDualAClick::put_Position(
      IDualAutoClickPoint FAR* newPosition)
{
   METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
   LPDISPATCH lpDisp = NULL;
   newPosition->QueryInterface(IID_IDispatch, (LPVOID*)&lpDisp);
   pThis->SetPosition(lpDisp);
   lpDisp->Release();
   return NOERROR;
}

デュアル インターフェイス メソッドを通じてポインタを返す前に、MFC の IDispatch ポインタからデュアル インターフェイスのポインタへの変換が必要になる場合があります。次に例を示します。

STDMETHODIMP CAutoClickDoc::XDualAClick::get_Position(
      IDualAutoClickPoint FAR* FAR* retval)
{
   METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
   LPDISPATCH lpDisp;
   lpDisp = pThis->GetPosition();
   lpDisp->QueryInterface(IID_IDualAutoClickPoint, (LPVOID*)retval);
   return NOERROR;
}

アプリケーションのタイプ ライブラリの登録

AppWizard では、OLE オートメーション サーバー アプリケーションのタイプ ライブラリをシステムに登録するためのコードが生成されません。タイプ ライブラリを登録する方法はいくつかありますが、アプリケーションが OLE タイプ情報を更新するとき、つまりアプリケーションがスタンドアロンで実行されるたびに、タイプ ライブラリを登録するのが便利です。

アプリケーションがスタンドアロンで実行されるたびにそのアプリケーションのタイプ ライブラリを登録する方法は、以下のとおりです。

  • 標準のインクルード ヘッダー ファイル STDAFX.H の中で AFXCTL.H をインクルードして AfxOleRegisterTypeLib 関数の定義にアクセスします。

  • アプリケーションの InitInstance 関数の中で COleObjectFactory::UpdateRegistryAll の呼び出しを見つけます。この呼び出しに続けて AfxOleRegisterTypeLib の呼び出しを追加し、タイプ ライブラリに対応する LIBID とタイプ ライブラリの名前を指定します。

    // When a server application is launched stand-alone, it is a good idea
    // to update the system registry in case it has been damaged.
    m_server.UpdateRegistry(OAT_DISPATCH_OBJECT);
    COleObjectFactory::UpdateRegistryAll();
    // DUAL_SUPPORT_START
    // Make sure the type library is registered or dual interface won't work.
    AfxOleRegisterTypeLib(AfxGetInstanceHandle(), LIBID_ACDual, _T("AutoClik.TLB"));
    // DUAL_SUPPORT_END
    

タイプ ライブラリの変更に伴うプロジェクト ビルド設定の変更

タイプ ライブラリが再ビルドされるたびに UUID 定義を含むヘッダー ファイルを MkTypLib を使用して生成するように、プロジェクトのビルド設定を変更するには次のようにします。

  1. [ビルド] メニューの [設定] をクリックし、各設定についてファイルの一覧から ODL ファイルを選択します。

  2. [OLE タイプ] パネルをクリックし、[出力ヘッダー ファイル名] ファイル名フィールドでファイル名を指定します。既存のファイルは MkTypLib によって上書きされるため、プロジェクトで使用されていないファイル名を使用してください。[OK] をクリックして [ビルド設定] ダイアログ ボックスを閉じます。

MkTypLib で生成したヘッダー ファイルからプロジェクトに UUID 定義を追加するには以下のようにします。

  1. 標準のヘッダー ファイル STDAFX.H 中で MkTypLib で生成したヘッダー ファイルをインクルードします。

  2. ファイル INITIIDS.CPP を新規作成し、プロジェクトに追加します。このファイル内で、OLE2.H と INITGUID.H の後に MkTypLib で生成したヘッダー ファイルをインクルードします。

    // initIIDs.c: defines IIDs for dual interfaces
    // This must not be built with precompiled header.
      #include <ole2.h>
      #include <initguid.h>
      #include "acdual.h"
    
  3. [ビルド] メニューの [設定] をクリックし、各設定についてファイルの一覧から INITIIDS.CPP を選択します。

  4. [C++] パネルで、カテゴリ "コンパイル済みヘッダー" をクリックし、" コンパイル済みヘッダーを使用しない"オプションをクリックします。[OK] をクリックして [ビルド設定] ダイアログ ボックスを閉じます。

タイプ ライブラリ中での正しいオブジェクト クラス名の指定

Visual C++ 付属のウィザードでは、OLE で作成可能なクラスに対するサーバーの ODL ファイルにおける coclass を指定するときに、実装クラス名が使用されますが、これは正しくありません。動作はしますが、実装クラス名は、ユーザーが使用できるクラス名にならない可能性があります。正しい名前を指定するには、ODL ファイルを開き、すべての coclass ステートメントで実装クラス名を正しい外部名で置き換えます。

coclass ステートメントが変更されると、MkTypLib で生成されたヘッダー ファイル内の CLSID の変数名もそれに応じて変更されます。新しい変数名を使用するにはコードを更新する必要があります。

例外およびオートメーション エラー インターフェイスの処理

ユーザー定義のオートメーション オブジェクトのメソッドおよびプロパティ アクセス関数では、例外をスローする可能性があります。この場合、デュアル インターフェイスの実装内で例外を処理し、OLE オートメーション エラー処理インターフェイス IErrorInfo を通じて、例外に関する情報をコントローラに返す必要があります。このインターフェイスには、IDispatch インターフェイスと VTBL インターフェイスの両方を通じて、状況依存のエラーに関する詳細な情報が用意されています。エラー ハンドラが使用可能であることを示すには、ISupportErrorInfo インターフェイスを実装する必要があります。

エラー処理機構の例として、ClassWizard で生成される標準ディスパッチ サポートの実装に使用される関数がスローする例外について説明します。MFC の IDispatch::Invoke の実装では、通常これらの例外をキャッチして EXCEPTINFO 構造体に変換し、これを Invoke 呼び出しの戻り値として返します。ただし、VTBL インターフェイスを使用するときには、例外をキャッチするのはプログラマの責任です。デュアル インターフェイス メソッドの保護の例を次に示します。

STDMETHODIMP CAutoClickDoc::XDualAClick::put_text(BSTR newText)
{
   METHOD_PROLOGUE(CAutoClickDoc, DualAClick)
   TRY_DUAL(IID_IDualAClick)
   {
      // MFC automatically converts from Unicode BSTR to 
      // Ansi CString, if necessary...
      pThis->m_str = newText;
      return NOERROR;
   }
   CATCH_ALL_DUAL
}

CATCH_ALL_DUAL は例外発生時に正しいエラー コードを返します。CATCH_ALL_DUAL は ICreateErrorInfo インターフェイスを使用して、MFC 例外を OLE オートメーション エラー処理情報に変換します。CATCH_ALL_DUAL マクロの例が ACDUAL のサンプルのファイル MFCDUAL.H にあります。このマクロが例外を処理するために呼び出す関数 DualHandleException は、ファイル MFCDUAL.CPP 内にあります。CATCH_ALL_DUAL では、発生した例外の型に基づいて戻り値となるエラー コードが次のように決定します。

  • COleDispatchException : この場合、HRESULT は次のコードを使用して組み込まれています。

    hr = MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF, 
                               (e->m_wCode + 0x200));
    

    この場合、次のコードを使って HRESULT を構築します。これは、例外の原因となったインターフェイスに固有な HRESULT を作成します。標準 OLE インターフェイス用のシステム定義の HRESULT と衝突しないように 0x200 のオフセットがとられます。

  • CMemoryException : この場合、E_OUTOFMEMORY を返します。

  • その他のすべての例外 : この場合、E_UNEXPECTED を返します。

OLE オートメーション エラー ハンドラを使うことを示すには、ISupportErrorInfo インターフェイスも実装する必要があります。

まず、オートメーション クラス定義に、ISupportErrorInfo をサポートすることを示すコードを追加します。

次に、オートメーション クラスのインターフェイス マップに、ISupportErrorInfo 実装クラスを MFC の QueryInterface 機構に関連付けるコードを追加します。INTERFACE_PART ステートメントは、ISupportErrorInfo に対して定義されたクラスに合わせて設定します。

最後に、ISupportErrorInfo をサポートするために定義したクラスを実装します。

ACDUAL のサンプルには、これらの 3 つのステップの作業に役立つ DECLARE_DUAL_ERRORINFO、DUAL_ERRORINFO_PART、および IMPLEMENT_DUAL_ERRORINFO の 3 個のマクロが含まれています。これらはいずれも MFCDUAL.H にあります。

以下の例では ISupportErrorInfo をサポートするために定義したクラスを実装しています。CAutoClickDoc はオートメーション クラスの名前であり、IID_IDualAClick は OLE オートメーション エラー オブジェクトを通じて通知されるエラーの発生源となっているインターフェイスの IID です。

STDMETHODIMP_(ULONG) CAutoClickDoc::XSupportErrorInfo::AddRef() 
{
   METHOD_PROLOGUE(CAutoClickDoc, SupportErrorInfo) 
   return pThis->ExternalAddRef(); 
} 
STDMETHODIMP_(ULONG) CAutoClickDoc::XSupportErrorInfo::Release() 
{ 
   METHOD_PROLOGUE(CAutoClickDoc, SupportErrorInfo) 
   return pThis->ExternalRelease(); 
} 
STDMETHODIMP CAutoClickDoc::XSupportErrorInfo::QueryInterface( 
   REFIID iid, LPVOID* ppvObj) 
{ 
   METHOD_PROLOGUE(CAutoClickDoc, SupportErrorInfo) 
   return pThis->ExternalQueryInterface(&iid, ppvObj); 
} 
STDMETHODIMP CAutoClickDoc::XSupportErrorInfo::InterfaceSupportsErrorInfo( 
   REFIID iid) 
{ 
   METHOD_PROLOGUE(CAutoClickDoc, SupportErrorInfo) 
   return (iid == IID_IDualAClick) ? S_OK : S_FALSE; 
}

参照

その他の技術情報

番号順テクニカル ノート

カテゴリ別テクニカル ノート