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::FromIDispatch
。 FromIDispatch
僅適用于 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 定義的標頭檔:
在 [ 建 置 ] 功能表上,按一下 [設定],然後從每個組態的檔案清單中選取 ODL 檔案。
按一下 [ OLE 類型 ] 索引標籤,並在 [輸出標頭 檔案名] 欄位中指定檔案名 。 使用專案尚未使用的檔案名,因為 MkTypLib 會覆寫任何現有的檔案。 按一下 [確定 ] 關閉 [ 建置設定 ] 對話方塊。
若要從 MkTypLib 產生的標頭檔將 UUID 定義新增 至您的專案:
在您的標準中包含 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++ 的精靈會錯誤地使用實作類別名稱,在伺服器的 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_ERRORINFO
、 DUAL_ERRORINFO_PART
和 IMPLEMENT_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;
}