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技术是微软技术框架的“根”(之一)。
相关文章
相关标签/搜索