最近看公司的一些新產(chǎn)品和框架 , 發(fā)現(xiàn)清一色的“COM思想架構(gòu) ”, 這里說的“COM思想架構(gòu)”是指不完全是標(biāo)準(zhǔn)COM組件的方式,而是指在設(shè)計上用到了COM思想。
COM組件技術(shù)大概在1993年產(chǎn)生, 20年了, 為什么還有這么多人使用?
我們先來看看標(biāo)準(zhǔn)COM組件:
標(biāo)準(zhǔn)COM組件(DLL方式)需要實現(xiàn)如下4個導(dǎo)出函數(shù):
DllRegisterServer 將組件信息寫入注冊表
DllUnregisterServer 取消注冊
DllCanUnloadNow判斷組件是否可以從內(nèi)存中卸載
DllGetClassObject返回IClassFactory指針,然后我們就可以通過該接口的CreateInstance方法創(chuàng)建對象并取得所需的接口。
采用標(biāo)準(zhǔn)COM組件,有很多好處:
面向接口和對象編程
語言無關(guān)性, 采用二進(jìn)制標(biāo)準(zhǔn),可以實現(xiàn)跨語言調(diào)用
版本升級方便,增加新接口, 組件升級后老客戶程序不用重新編譯
位置透明, 客戶程序不用關(guān)心組件的位置
重用方便, 通過包容和聚合可以快速重用已有組件
我們可以看到標(biāo)準(zhǔn)COM組件非常強(qiáng)大, 但是很多時候我們并不需要標(biāo)準(zhǔn)COM組件的所有特性,比如我們不希望引入注冊表, 也不希望引入COM運(yùn)行庫,我們希望我們的程序是完全“綠色”的。這時我們就會采用“COM思想架構(gòu)“開發(fā)非標(biāo)準(zhǔn)的COM組件。
實際上微軟本身已經(jīng)有很多API采用這種設(shè)計方案了,我們來看一些例子:
XmlLite
我們可以用depends.exe看看該DLL的導(dǎo)出函數(shù):
調(diào)用這些導(dǎo)出的CreateXXX函數(shù)返回返回一個繼承于IUnknown的接口, 然后我們就可以調(diào)用接口提供的方法了, 可以看下IXmlReader的方法:
IXmlReader : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE SetInput(
/* [annotation] */
__in_opt IUnknown *pInput) = 0;
virtual HRESULT STDMETHODCALLTYPE GetProperty(
/* [annotation] */
__in UINT nProperty,
/* [annotation] */
__out LONG_PTR *ppValue) = 0;
virtual HRESULT STDMETHODCALLTYPE SetProperty(
/* [annotation] */
__in UINT nProperty,
/* [annotation] */
__in_opt LONG_PTR pValue) = 0;
virtual HRESULT STDMETHODCALLTYPE Read(
/* [annotation] */
__out_opt XmlNodeType *pNodeType) = 0;
virtual HRESULT STDMETHODCALLTYPE GetNodeType(
/* [annotation] */
__out XmlNodeType *pNodeType) = 0;
virtual HRESULT STDMETHODCALLTYPE MoveToFirstAttribute( void) = 0;
virtual HRESULT STDMETHODCALLTYPE MoveToNextAttribute( void) = 0;
.......
};
Direct2D
關(guān)于是微軟下一代2D渲染接口, 關(guān)于它的詳情參考
Direct2D, 我們同樣分析一下它的導(dǎo)出函數(shù):

實際看到這里也用了COM思想的方法,我們可以看看D2D1CreateFactory返回的ID2D1Factory的接口:
interface DX_DECLARE_INTERFACE("06152247-6f50-465a-9245-118bfd3b6007") ID2D1Factory : public IUnknown
{
//
// Cause the factory to refresh any system metrics that it might have been snapped
// on factory creation.
//
STDMETHOD(ReloadSystemMetrics)(
) PURE;
//
// Retrieves the current desktop DPI. To refresh this, call ReloadSystemMetrics.
//
STDMETHOD_(void, GetDesktopDpi)(
_Out_ FLOAT *dpiX,
_Out_ FLOAT *dpiY
) PURE;
STDMETHOD(CreateRectangleGeometry)(
_In_ CONST D2D1_RECT_F *rectangle,
_Outptr_ ID2D1RectangleGeometry **rectangleGeometry
) PURE;
STDMETHOD(CreateRoundedRectangleGeometry)(
_In_ CONST D2D1_ROUNDED_RECT *roundedRectangle,
_Outptr_ ID2D1RoundedRectangleGeometry **roundedRectangleGeometry
) PURE;
STDMETHOD(CreateEllipseGeometry)(
_In_ CONST D2D1_ELLIPSE *ellipse,
_Outptr_ ID2D1EllipseGeometry **ellipseGeometry
) PURE;
......
}; // interface ID2D1Factory
思考為什么會有越來越多的新程序采用這種”COM思想架構(gòu)“, 這個要回到COM的根 ---- IUnknown接口:
IUnknown
{
public:
BEGIN_INTERFACE
virtual HRESULT STDMETHODCALLTYPE QueryInterface(
/* [in] */ REFIID riid,
/* [annotation][iid_is][out] */
__RPC__deref_out void **ppvObject) = 0;
virtual ULONG STDMETHODCALLTYPE AddRef( void) = 0;
virtual ULONG STDMETHODCALLTYPE Release( void) = 0;
END_INTERFACE
};
IUnknow接口是個偉大的創(chuàng)造!
IUnknow的AddRef和Release實現(xiàn)對象的引用計數(shù)管理, 引用計數(shù)用來管理對象的生存周期。
通過引用計數(shù)一來可以很方便的共享對象, 另外也能確保對象被正確釋放(確保對象的new和delete在同一模塊中)。
QueryInterface實現(xiàn)接口查詢, 通過這種方式可以很方便的對現(xiàn)有組件進(jìn)行升級, 只要接口不改 ,可以隨意修改內(nèi)部實現(xiàn)而不用客戶程序重新編譯。
另外也可以直接增加新接口, 只要在QueryInterface內(nèi)增加并可以查詢到該新接口, 我們就可以調(diào)用該新接口。
我們可以看到QueryInterface讓C++這種靜態(tài)語言有了某些動態(tài)語言的特性, 在C# 中我們可以通過反射查詢到某個類的成員函數(shù)和成員變量, Objective-C中我們也可以根據(jù)函數(shù)名動態(tài)調(diào)用某個函數(shù), 在腳本語言中,我們可以在運(yùn)行時動態(tài)查詢和修改某個類的信息。通過COM的QueryInterface, 我們可以動態(tài)查詢某個組件類實現(xiàn)哪些接口(函數(shù))。當(dāng)然他們之間有本質(zhì)的區(qū)別, 動態(tài)語言運(yùn)行時內(nèi)存中保存有類信息, 而C++的QueryInterface通過switch case, 返回的是存有虛表指針的對象指針。
最后再簡單談下IUnknown的升級版IDispatch和IInspectable。
先看IDispatch:
IDispatch : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount(
/* [out] */ __RPC__out UINT *pctinfo) = 0;
virtual HRESULT STDMETHODCALLTYPE GetTypeInfo(
/* [in] */ UINT iTInfo,
/* [in] */ LCID lcid,
/* [out] */ __RPC__deref_out_opt ITypeInfo **ppTInfo) = 0;
virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames(
/* [in] */ __RPC__in REFIID riid,
/* [size_is][in] */ __RPC__in_ecount_full(cNames) LPOLESTR *rgszNames,
/* [range][in] */ UINT cNames,
/* [in] */ LCID lcid,
/* [size_is][out] */ __RPC__out_ecount_full(cNames) DISPID *rgDispId) = 0;
virtual /* [local] */ HRESULT STDMETHODCALLTYPE Invoke(
/* [in] */ DISPID dispIdMember,
/* [in] */ REFIID riid,
/* [in] */ LCID lcid,
/* [in] */ WORD wFlags,
/* [out][in] */ DISPPARAMS *pDispParams,
/* [out] */ VARIANT *pVarResult,
/* [out] */ EXCEPINFO *pExcepInfo,
/* [out] */ UINT *puArgErr) = 0;
};
IDispatch繼承于IUnknown, 通過IDispatch, 我們可以實現(xiàn)腳本語言對COM組件的調(diào)用,我們可以通過GetTypeInfo獲取對象的類型信息, 通過GetIDsOfNames函數(shù)以字符串的方式獲取函數(shù)的DISPID, 通過Invoke動態(tài)調(diào)用某個函數(shù)。IE的DOM對象與JS的交互全部是通過IDispatch(Ex)接口實現(xiàn)的。當(dāng)然,除非你的組件要與腳本語言交互, 否者一般不用實現(xiàn)該接口。
再看IInspectable:
IInspectable : public IUnknown
{
public:
virtual HRESULT STDMETHODCALLTYPE GetIids(
/* [out] */ __RPC__out ULONG *iidCount,
/* [size_is][size_is][out] */ __RPC__deref_out_ecount_full_opt(*iidCount) IID **iids) = 0;
virtual HRESULT STDMETHODCALLTYPE GetRuntimeClassName(
/* [out] */ __RPC__deref_out_opt HSTRING *className) = 0;
virtual HRESULT STDMETHODCALLTYPE GetTrustLevel(
/* [out] */ __RPC__out TrustLevel *trustLevel) = 0;
};
IInspectable也繼承于IUnknown, 它是WinRT所有對象的基接口, 所以WinRT還是基于COM技術(shù)。
其中GetTrustLevel返回信任等級, GetRuntimeClassName返回類名, 而GetIids返回當(dāng)前類對象實現(xiàn)了哪些接口(所有接口的iid), 得到接口的iid后, 我們就可以通過QueryInterface查詢我們需要的接口了, 得到接口指針就可以調(diào)用內(nèi)部函數(shù)了。
最后總結(jié)下,回答下文章開頭的問題, 很多人說COM過時了, 也許”純正的標(biāo)準(zhǔn)COM“確實是使用的人越來越少了, 但是COM的思想?yún)s一直在后續(xù)的軟件開發(fā)中被使用和發(fā)揚(yáng), 可以說COM技術(shù)是微軟技術(shù)框架的“根”(之一)。
posted on 2013-07-20 16:59
Richard Wei 閱讀(4671)
評論(6) 編輯 收藏 引用 所屬分類:
COM