共用方式為


TN065: 雙重介面支援 OLE 自動化伺服器

注意事項注意事項

由於它第一次線上文件中包含尚未更新下列技術提示。如此一來,某些程序和主題可能已經過期或不正確。如需最新資訊,建議您先搜尋線上文件索引中有興趣的主題。

這個記事將告訴您如何將雙重介面支援加入至 MFC 架構 OLE 自動化伺服器應用程式。 ACDUAL 範例說明了雙重介面的支援,並在這張便箋中的程式碼範例取自 ACDUAL。 如所述切記,巨集DECLARE_DUAL_ERRORINFO, DUAL_ERRORINFO_PART,以及IMPLEMENT_DUAL_ERRORINFOACDUAL 範例的一部分,請參閱 MFCDUAL。H.

雙重介面

雖然 OLE 自動化,可讓您實作IDispatch介面、 VTBL 介面,或者雙重介面 (此範圍涵蓋這兩者),Microsoft 強烈建議您針對所有顯露 OLE 自動化物件實作雙重介面。 雙重介面有很大的好處,透過IDispatch-唯一或 VTBL 專用介面:

  • 繫結可以發生在編譯時期透過 VTBL 介面,或在執行階段透過IDispatch

  • OLE 自動化控制站可供 VTBL 介面可能獲益於更佳的效能。

  • 現有的 OLE 自動化控制站使用IDispatch介面仍可運作。

  • VTBL 介面是從 C++ 呼叫的工作變得更容易。

  • 雙重介面所需的 Visual Basic 物件支援的功能與相容性的。

以 CCmdTarget 為基礎的類別中加入雙重介面支援

雙重介面是真的只是一種自訂介面衍生自IDispatch。 最簡單的方式實作雙重介面支援在CCmdTarget-根據的類別,是第一個實作,正常分派介面上使用 MFC 和類別精靈,您的類別,然後在稍後加入自訂介面。 大多數的情況下,您的自訂介面實作將只是委派回 MFC IDispatch的實作。

首先,修改 ODL 檔為您的伺服器,來定義您的物件為雙重介面。 若要定義雙重介面,您必須使用陳述式的介面,而不是DISPINTERFACE Visual C++ 精靈可產生的陳述式。 而不移除現有的DISPINTERFACE陳述式中,加入新的介面陳述式。 保留DISPINTERFACE表單中,您可以繼續使用類別精靈來將屬性和方法加入至您的物件,但您必須將的對等的屬性和方法加入至您的介面陳述式。

雙重介面的介面陳述式必須有 OLEAUTOMATION屬性和介面必須衍生自IDispatch。 您可以使用 GUIDGEN 來建立範例 IID 的雙重介面:

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

一旦您備妥介面陳述式,開始加入方法和屬性的項目。 為雙重介面,您需要重新排列參數清單,使您的方法和雙重介面中的屬性存取子函式會傳回HRESULT ,並將其傳回值傳遞做為參數的屬性[retval,out]。 請記住對於屬性,您必須新增兩個讀取 (propget) 和寫入 (propput) 存取具有相同 id 的函式。 例如:

[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的機制。 您需要某一項INTERFACE_PART區塊 ODL,介面陳述式中的每個項目加上分派介面的項目。 每個 ODL 項 propput 屬性需要的功能,名為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);
}

您的物件方法和屬性存取子函式,您必須填入實作。 方法和屬性的函式通常可以委派至使用類別精靈所產生的方法。 不過,如果您設定屬性直接存取變數時,您需要撰寫 get/put 值到變數的程式碼。 例如:

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的指標。 一個方法來處理這種情況是以原始查詢IDispatch指標組 mfc 上,或將該指標傳遞給需要它的函式。 例如:

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 類型資訊,也就當單獨執行的應用程式註冊型別程式庫。

若要註冊應用程式的型別程式庫時所執行獨立:

  • 包括 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
    

修改專案建置設定,以配合型別程式庫變更

若要修改專案的建置設定,以便包含標頭檔 UUID 每次重新建置型別程式庫時,定義由 MkTypLib 所產生:

  1. 建置 ] 功能表中,按一下 設定,綴恁寁 ODL 檔從每個設定檔清單。

  2. 按一下 OLE 類型 索引標籤,然後指定檔名中的 輸出標頭檔案名稱] 欄位。 因為 MkTypLib 就會覆寫所有現存檔案,請使用不已經由您的專案的檔名。 按一下 [ 確定 來關閉 建置設定對話方塊。

若要新增 UUID MkTypLib 所產生標頭檔中的定義加入您的專案:

  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++ 精靈會使用實作的類別名稱來指定伺服器的 OLE 可建立類別的 ODL 檔 coclass。 雖然此操作可用,實作的類別名稱可能不是您想要使用物件的使用者類別名稱。 若要指定正確的名稱,請開啟 ODL 檔,找出每一個 coclass 陳述式,並使用正確的外部名稱取代實作類別名稱。

請注意,當 coclass 陳述式變更時,變數名稱的 CLSIDs MkTypLib 所產生標頭檔中的也會跟著變更。 您必須更新您的程式碼,以使用新的變數名稱。

處理例外狀況,以及自動化錯誤的介面

自動化物件的方法和屬性存取子函式可能會擲回例外狀況。 因此,應該雙重介面實作中處理它們,並會將例外狀況的資訊傳回給控制器透過 OLE 自動化錯誤處理介面,如果 IErrorInfo。 這個介面會提供詳細、 關聯式錯誤資訊傳送兩者的IDispatch和 VTBL 介面。 若要表示錯誤處理常式可用,您應該實作 ISupportErrorInfo 介面。

描述的錯誤處理機制,假設類別精靈所產生的函式用來實作標準分派支援擲回例外狀況。 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_DUALMFC 例外狀況會將轉換成 OLE 自動化錯誤處理資訊所使用的 ICreateErrorInfo 介面。 (例如CATCH_ALL_DUAL巨集是在 MFCDUAL 檔案中。H 中的 ACDUAL 範例。 此函式呼叫來處理例外狀況, DualHandleException,MFCDUAL 檔案中。CPP) 裡。CATCH_ALL_DUAL的錯誤程式碼,以傳回根據所發生的例外狀況的類型會決定:

  • COleDispatchException – 如此一來, HRESULT建構使用下列程式碼:

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

    這會建立HRESULT特定介面造成例外狀況。 錯誤碼相隔以避免發生與系統定義的任何衝突 0x200 HRESULTs 進行標準的 OLE 介面。

  • CMemoryException – 如此一來, E_OUTOFMEMORY會傳回。

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

若要指示請使用 OLE 自動化錯誤處理常式,您也應該實作 ISupportErrorInfo 介面。

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

第二,將程式碼加入您的自動化類別產生關聯的介面對應 ISupportErrorInfo 實作類別使用 MFC 的QueryInterface的機制。 INTERFACE_PART陳述式符合所定義的類別 ISupportErrorInfo

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

( ACDUAL 範例包含三個巨集],以協助執行下面三個步驟中, DECLARE_DUAL_ERRORINFO, DUAL_ERRORINFO_PART,以及IMPLEMENT_DUAL_ERRORINFO、 MFCDUAL 中包含的所有。H.)

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

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

請參閱

其他資源

技術的備忘稿編號

依類別的技術注意事項