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::FromIDispatch。FromIDispatch只能使用於 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 所產生:
在建置 ] 功能表中,按一下 設定,綴恁寁 ODL 檔從每個設定檔清單。
按一下 OLE 類型 索引標籤,然後指定檔名中的 輸出標頭檔案名稱] 欄位。因為 MkTypLib 就會覆寫所有現存檔案,請使用不已經由您的專案的檔名。按一下 [ 確定 來關閉 建置設定對話方塊。
若要新增 UUID MkTypLib 所產生標頭檔中的定義加入您的專案:
包含 MkTypLib 產生在您的標準標頭檔包含標頭檔,STDAFX。H.
建立新的檔案時,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"
在建置 ] 功能表中,按一下 設定,綴恁寁 INITIIDS。CPP 的每個設定的檔案清單。
按一下 [ 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;
}