Share via


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

Note

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

このノートでは、MFC ベースの 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 サンプルを使用して、デュアル インターフェイスの IID を作成できます。

[ 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);

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

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

ODL ファイルが更新された後は、MFC のインターフェイス マップ メカニズムを使用して、オブジェクト クラスのデュアル インターフェイスの実装クラスを定義し、MFC の QueryInterface メカニズムで対応するエントリを作成します。 ODL のインターフェイス ステートメントの各エントリに対して、 INTERFACE_PART ブロックに 1 つのエントリとディスパッチ インターフェイスのエントリが必要です。 propput 属性を持つ各 ODL エントリには 、put_propertyname という名前の関数が必要です。 propget 属性を持つ各エントリには 、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 Automation サーバー アプリケーションのタイプ ライブラリをシステムに登録するコードは生成されません。 タイプ ライブラリを登録する方法は他にもありますが、OLE 型情報を更新する場合、つまり、アプリケーションがスタンドアロンで実行されるたびに、アプリケーションでタイプ ライブラリを登録すると便利です。

アプリケーションがスタンドアロンで実行されるたびにアプリケーションのタイプ ライブラリを登録するには:

  • 標準の Include AFXCTL.H には、ヘッダー ファイル STDAFX.H が含まれ、AfxOleRegisterTypeLib 関数の定義にアクセスします。

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

    // 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. MkTypLib で生成されたヘッダー ファイルを標準の include ヘッダー ファイル stdafx.h に含めます。

  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 ファイル内のコクラスを指定します。 これは機能しますが、実装クラス名は、オブジェクトのユーザーが使用するクラス名ではない可能性があります。 正しい名前を指定するには、ODL ファイルを開き、各コクラス ステートメントを見つけて、実装クラス名を正しい外部名に置き換える必要があります。

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

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

オートメーション オブジェクトのメソッドとプロパティのアクセサー関数によって例外がスローされる場合があります。 その場合は、デュアル インターフェイスの実装で処理し、OLE Automation エラー処理インターフェイス IErrorInfo を介して例外に関する情報をコントローラーに渡す必要があります。 このインターフェイスは、IDispatch インターフェイスと VTBL インターフェイスの両方を介して、詳細なコンテキスト エラー情報を提供します。 エラー ハンドラーを使用可能にするためには、 ISupportErrorInfo インターフェイスを実装する必要があります。

エラー処理メカニズムを説明するために、標準ディスパッチ を実装するために使用される ClassWizard で生成された関数は、スロー例外をサポートするとします。 MFC の IDispatch::Invoke の実装では、通常、これらの例外がキャッチされ、Invoke 呼び出しによって返される EXCEPTINFO 構造体に変換されます。 ただし、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は MFCDUAL ファイルにあります。ACDUAL サンプルの H。例外を処理するために呼び出す関数は、DualHandleExceptionMFCDUAL ファイルにあります。CPP.)CATCH_ALL_DUALは、発生した例外の種類に基づいて返すエラー コードを決定します。

  • COleDispatchException - この場合、HRESULT は次のコードを使用して構築されます。

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

    これにより、例外の原因となるインターフェイスに固有の HRESULT が作成されます。 エラー コードは、標準 OLE インターフェイスのためにシステム定義された HRESULT との競合を回避するために、エラー コードが 0x200 でオフセットされます。

  • CMemoryException - この場合、E_OUTOFMEMORY が返されます。

  • その他の例外 - この場合、E_UNEXPECTED が返されます。

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

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

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

最後に、ISupportErrorInfo をサポートするように定義されているクラスを実装します。

(ACDUAL サンプルには、次の3つの手順 DECLARE_DUAL_ERRORINFODUAL_ERRORINFO_PARTIMPLEMENT_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;
}

関連項目

番号順テクニカル ノート
カテゴリ別テクニカル ノート