共用方式為


TN065:OLE Automation 伺服程式的雙重介面支援

注意

下列技術提示自其納入線上文件以來,未曾更新。 因此,有些程序和主題可能已過期或不正確。 如需最新資訊,建議您在線上文件索引中搜尋相關的主題。

此附注討論如何將雙介面支援新增至 MFC 型 OLE Automation 伺服器應用程式。 ACDUAL 範例說明雙重介面支援,而此附注中的範例程式碼取自 ACDUAL。 此附注中所述的宏,例如DECLARE_DUAL_ERRORINFO、DUAL_ERRORINFO_PART和IMPLEMENT_DUAL_ERRORINFO,都是 ACDUAL 範例的一部分,而且可以在 MFCDUAL.H 中找到。

雙重介面

雖然 OLE Automation 可讓您實 IDispatch 作介面、VTBL 介面或雙介面(包含這兩者),但 Microsoft 強烈建議您為所有公開的 OLE Automation 物件實作雙重介面。 雙重介面在僅限 -only 或 VTBL 專用介面上 IDispatch 具有顯著優勢:

  • 系結可以透過 VTBL 介面或在執行時間透過 IDispatch 進行。

  • 可以使用 VTBL 介面的 OLE 自動化控制器可能會受益于改善的效能。

  • 使用 IDispatch 介面的現有 OLE Automation 控制器將繼續運作。

  • VTBL 介面更容易從 C++ 呼叫。

  • 需要雙重介面才能與 Visual Basic 物件支援功能相容。

將雙重介面支援新增至 CCmdTarget 型類別

雙重介面實際上只是衍生自 IDispatch 的自訂介面。 在型類別中 CCmdTarget 實作雙介面支援的最直接方式,就是先使用 MFC 和 ClassWizard 在類別上實作一般分派介面,然後稍後新增自訂介面。 在大多數情況下,您的自訂介面實作只會委派回 MFC IDispatch 實作。

首先,修改伺服器的 ODL 檔案,為您的物件定義雙重介面。 若要定義雙重介面,您必須使用介面語句,而不是 DISPINTERFACE Visual C++ 精靈產生的語句。 新增介面語句,而不是移除現有的 DISPINTERFACE 語句。 藉由保留 DISPINTERFACE 表單,您可以繼續使用 ClassWizard 將屬性和方法新增至物件,但您必須將對等的屬性和方法新增至介面語句。

雙重介面的介面語句必須具有 OLEAUTOMATION DUAL 屬性,而且介面必須衍生自 IDispatch 。 您可以使用 GUIDGEN 範例來建立 雙重介面的 IID

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

一旦您已就地設定介面語句,請開始新增方法和屬性的專案。 針對雙重介面,您必須重新排列參數清單,讓雙介面中的方法和屬性存取子函式傳回 HRESULT ,並以 屬性 [retval,out] 傳遞其傳回值作為參數。 請記住,對於屬性,您必須同時新增具有相同識別碼的 read ( propget ) 和 write ( propput ) access 函式。例如:

[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 介面語句中每個專案的區塊中 INTERFACE_PART 都需要一個專案,再加上分派介面的專案。 每個具有 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::FromIDispatchFromIDispatch 僅適用于 MFC 的 IDispatch 指標。 解決這個問題的其中一種方法是查詢 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 類型資訊時,讓應用程式註冊型別程式庫是很方便的,也就是每當應用程式獨立執行時。

每當應用程式獨立執行時,若要註冊應用程式的類型程式庫:

  • 包含 AFXCTL。標準中的 H 包含標頭檔 STDAFX。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
    

修改專案建置設定以容納型別程式庫變更

若要修改專案的組建設定,每當重建型別程式庫時,MkTypLib 就會產生包含 UUID 定義的標頭檔:

  1. 在 [ ] 功能表上,按一下 [設定],然後從每個組態的檔案清單中選取 ODL 檔案。

  2. 按一下 [ OLE 類型 ] 索引標籤,並在 [輸出標頭 檔案名] 欄位中指定檔案名 。 使用專案尚未使用的檔案名,因為 MkTypLib 會覆寫任何現有的檔案。 按一下 [確定 ] 關閉 [ 建置設定 ] 對話方塊。

若要從 MkTypLib 產生的標頭檔將 UUID 定義新增 至您的專案:

  1. 在您的標準中包含 MkTypLib 產生的標頭檔,包括標頭檔 stdafx.h

  2. 建立新的檔案 INITIIDS。CPP,並將它新增至您的專案。 在此檔案中,請在包含 OLE2 之後,包含 MkTypLib 產生的標頭檔。H 和 INITGUID。H:

    // 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++] 索引 標籤,按一下 [先行編譯標頭 ] 類別 ,然後選取 [不使用先行編譯標頭] 選項按鈕。 按一下 [確定] 關閉 [ 建置 設定] 對話方塊。

在類型程式庫中指定正確的物件類別名稱

隨附于 Visual C++ 的精靈會錯誤地使用實作類別名稱,在伺服器的 ODL 檔案中指定 OLE 可建立類別的 coclass。 雖然這樣會運作,但實作類別名稱可能不是您想要物件使用者使用的類別名稱。 若要指定正確的名稱,請開啟 ODL 檔案,找出每個 coclass 語句,並以正確的外部名稱取代實作類別名稱。

請注意,當 coclass 語句變更時,MkTypLib 產生的標頭檔中 CLSID 的變數名稱 將會隨之變更。 您必須更新程式碼,才能使用新的變數名稱。

處理例外狀況和自動化錯誤介面

自動化物件的方法和屬性存取子函式可能會擲回例外狀況。 如果是,您應該在雙介面實作中處理它們,並透過 OLE Automation 錯誤處理介面將例外狀況的相關資訊傳回控制器。 IErrorInfo 此介面透過 和 VTBL 介面提供詳細的內容錯誤資訊 IDispatch 。 若要指出有可用的錯誤處理常式,您應該實 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 Automation 錯誤處理資訊。 (範例 CATCH_ALL_DUAL 宏位於 MFCDUAL 檔案中。ACDUAL 範例中的 H。它呼叫來處理例外狀況的 DualHandleException 函式,位於 MFCDUAL 檔案中。CPP.)CATCH_ALL_DUAL會根據發生的例外狀況類型,決定要傳回的錯誤碼:

  • COleDispatchException - 在此情況下, HRESULT 會使用下列程式碼來建構:

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

    這會建立 HRESULT 造成例外狀況之介面的特定 。 錯誤碼會依0x200位移,以避免與標準 OLE 介面的系統定義 HRESULT s 發生任何衝突。

  • CMemoryException - 在此情況下, E_OUTOFMEMORY 會傳回 。

  • 任何其他例外狀況 - 在此情況下, E_UNEXPECTED 會傳回 。

若要指出使用 OLE Automation 錯誤處理常式,您也應該實作 ISupportErrorInfo 介面。

首先,將程式碼新增至您的自動化類別定義,以顯示它支援 ISupportErrorInfo

其次,將程式碼新增至自動化類別的介面對應,以將實作類別與 MFC 的機制 QueryInterface 產生關聯 ISupportErrorInfo 。 語句 INTERFACE_PART 會比對 為 定義的 ISupportErrorInfo 類別。

最後,實作定義以支援 的 ISupportErrorInfo 類別。

(The ACDUAL 範例包含三個宏,可協助執行這三個步驟、 DECLARE_DUAL_ERRORINFODUAL_ERRORINFO_PARTIMPLEMENT_DUAL_ERRORINFO ,全部包含在 MFCDUAL.H. 中。

下列範例會實作定義以支援 的 ISupportErrorInfo 類別。 CAutoClickDoc是自動化類別的名稱,而 IID_IDualAClick 是介面的 IID ,該介面是 透過 OLE Automation 錯誤物件報告的錯誤來源:

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;
}

另請參閱

依編號顯示的技術提示
依分類區分的技術提示