zoukankan      html  css  js  c++  java
  • 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技术是微软技术框架的“根”(之一)。
  • 相关阅读:
    mysql修改数据表名
    HDU 5742 It's All In The Mind (贪心)
    HDU 5752 Sqrt Bo (数论)
    HDU 5753 Permutation Bo (推导 or 打表找规律)
    HDU 5762 Teacher Bo (暴力)
    HDU 5754 Life Winner Bo (博弈)
    CodeForces 455C Civilization (并查集+树的直径)
    CodeForces 455B A Lot of Games (博弈论)
    CodeForces 455A Boredom (DP)
    HDU 4861 Couple doubi (数论 or 打表找规律)
  • 原文地址:https://www.cnblogs.com/weiym/p/3202640.html
Copyright © 2011-2022 走看看