COM思想的背後

最近看公司的一些新產品和框架 , 發現清一色的「COM思想架構 」, 這裏說的「COM思想架構」是指不徹底是標準COM組件的方式,而是指在設計上用到了COM思想。

COM組件技術大概在1993年產生, 20年了, 爲何還有這麼多人使用? 

咱們先來看看標準COM組件:
標準COM組件(DLL方式)須要實現以下4個導出函數:
DllRegisterServer 將組件信息寫入註冊表
DllUnregisterServer 取消註冊
DllCanUnloadNow判斷組件是否能夠從內存中卸載
DllGetClassObject返回IClassFactory指針,而後咱們就能夠經過該接口的CreateInstance方法建立對象並取得所需的接口。

採用標準COM組件,有不少好處:
面向接口和對象編程
語言無關性, 採用二進制標準,能夠實現跨語言調用
版本升級方便,增長新接口, 組件升級後老客戶程序不用從新編譯
位置透明, 客戶程序不用關心組件的位置
重用方便, 經過包容和聚合能夠快速重用已有組件

咱們能夠看到標準COM組件很是強大, 可是不少時候咱們並不須要標準COM組件的全部特性,好比咱們不但願引入註冊表, 也不但願引入COM運行庫,咱們但願咱們的程序是徹底「綠色」的。這時咱們就會採用「COM思想架構「開發非標準的COM組件。

實際上微軟自己已經有不少API採用這種設計方案了,咱們來看一些例子:

XmlLite
繼msxml以後微軟提供的另 一款高效的XML解析器, 它自己只有一個綠色DLL XmlLite.dll, 關於它的接口和使用方法能夠參考 XmlLite Introduction用於本機 C++ 的小巧快捷的 XML 分析器
咱們能夠用depends.exe看看該DLL的導出函數:


調用這些導出的CreateXXX函數返回返回一個繼承於IUnknown的接口, 而後咱們就能夠調用接口提供的方法了, 能夠看下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
關因而微軟下一代2D渲染接口, 關於它的詳情參考 Direct2D, 咱們一樣分析一下它的導出函數:

實際看到這裏也用了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思想架構「, 這個要回到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接口是個偉大的創造!

 IUnknow的AddRef和Release實現對象的引用計數管理, 引用計數用來管理對象的生存週期。
經過引用計數一來能夠很方便的共享對象, 另外也能確保對象被正確釋放(確保對象的new和delete在同一模塊中)。

QueryInterface實現接口查詢, 經過這種方式能夠很方便的對現有組件進行升級, 只要接口不改 ,能夠隨意修改內部實現而不用客戶程序從新編譯。
另外也能夠直接增長新接口, 只要在QueryInterface內增長並能夠查詢到該新接口, 咱們就能夠調用該新接口。

咱們能夠看到QueryInterface讓C++這種靜態語言有了某些動態語言的特性, 在C# 中咱們能夠經過反射查詢到某個類的成員函數和成員變量, Objective-C中咱們也能夠根據函數名動態調用某個函數, 在腳本語言中,咱們能夠在運行時動態查詢和修改某個類的信息。經過COM的QueryInterface, 咱們能夠動態查詢某個組件類實現哪些接口(函數)。固然他們之間有本質的區別, 動態語言運行時內存中保存有類信息, 而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, 咱們能夠實現腳本語言對COM組件的調用,咱們能夠經過GetTypeInfo獲取對象的類型信息, 經過GetIDsOfNames函數以字符串的方式獲取函數的DISPID, 經過Invoke動態調用某個函數。IE的DOM對象與JS的交互所有是經過IDispatch(Ex)接口實現的。固然,除非你的組件要與腳本語言交互, 否者通常不用實現該接口。

再看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技術。
其中GetTrustLevel返回信任等級, GetRuntimeClassName返回類名, 而GetIids返回當前類對象實現了哪些接口(全部接口的iid), 獲得接口的iid後, 咱們就能夠經過QueryInterface查詢咱們須要的接口了, 獲得接口指針就能夠調用內部函數了。

最後總結下,回答下文章開頭的問題, 不少人說COM過期了, 也許」純正的標準COM「確實是使用的人愈來愈少了, 可是COM的思想卻一直在後續的軟件開發中被使用和發揚, 能夠說COM技術是微軟技術框架的「根」(之一)。
相關文章
相關標籤/搜索