1. 程式人生 > >COM元件非常好的分析文章

COM元件非常好的分析文章

轉載於http://blog.sina.com.cn/s/blog_470ecf2a01014ahy.html


原文地址:COM三大介面:IUnknown、IClassFactory、IDispatch。[轉載] 作者:海貝

(1)COM元件有三個最基本的介面類,分別是IUnknownIClassFactoryIDispatch

 COM規範規定任何元件、任何介面都必須從IUnknown繼承IUnknown包含三個函式,分別是 QueryInterfaceAddRefRelease這三個函式是無比重要的,而且它們的排列順序也是不可改變的。

QueryInterface用於查詢元件實現的其它介面,說白了也就是看看這個元件的父類中還有哪些介面類AddRef用於增加引用計數,Release用於減少引用計數。引用計數也是COM中的一個非常重要的概念。大體上簡單的說來可以這麼理解,COM元件是個DLL,當客戶程式要用它時就要把它裝到記憶體裡。另一方面,一個元件也不是隻給你一個人用的,可能會有很多個程式同時都要用到它。但實際上DLL只裝載了一次,即記憶體中只有一個COM元件,那COM元件由誰來釋放?由客戶程式嗎?不可能,因為如果你釋放了元件,那別人怎麼用,所以只能由COM元件自己來負責。所以出現了引用計數的概念,COM維持一個計數,記錄當前有多少人在用它,每多一次呼叫計數就加一,少一個客戶用它就減一,當最後一個客戶釋放它的時侯,
COM知道已經沒有人用它了,它的使用已經結束了,那它就把它自己給釋放了。引用計數是COM程式設計裡非常容易出錯的一個地方,但所幸VC的各種各樣的類庫裡已經基本上把AddRef的呼叫給隱含了,在我的印象裡,我程式設計的時侯還從來沒有呼叫過AddRef,我們只需在適當的時侯呼叫Release。至少有兩個時侯要記住呼叫Release,第一個是呼叫了 QueryInterface以後,第二個是呼叫了任何得到一個介面的指標的函式以後,記住多查MSDN 以確定某個函式內部是否呼叫了AddRef,如果是的話那呼叫Release的責任就要歸你了。 IUnknown的這三個函式的實現非常規範但也非常煩瑣,容易出錯,所幸的事我們可能永遠也不需要自己來實現它們。

 

IClassFactory的作用是建立COM元件。我們已經知道COM元件實際上就是一個類,那我們平常是怎麼例項化一個類物件的?是用'new’命令!很簡單吧,COM元件也一樣如此。但是誰來new它呢?不可能是客戶程式,因為客戶程式不可能知道元件的類名字,如果客戶知道元件的類名字那元件的可重用性就要打個大大的折扣了,事實上客戶程式只不過知道一個代表著元件的128位的數字串而已,這個等會再介紹。所以客戶無法自己建立元件,而且考慮一下,如果元件是在遠端的機器上,你還能new出一個物件嗎?所以建立元件的責任交給了一個單獨的物件,這個物件就是類廠每個元件都必須有一個與之相關的類廠,這個類廠知道怎麼樣建立元件,當客戶請求一個元件物件的例項時,實際上這個請求交給了類廠,由類廠建立元件例項,然後把例項指標交給客戶程式。這個過程在跨程序及遠端建立元件時特別有用,因為這時就不是一個簡單的new操作就可以的了,它必須要經過排程,而這些複雜的操作都交給類廠物件去做了。IClassFactory最重要的一個函式就是CreateInstance,顧名思議就是建立元件例項,一般情況下我們不會直接呼叫它,API函式都為我們封裝好它了,只有某些特殊情況下才會由我們自己來呼叫它,這也是VC編寫COM元件的好處,使我們有了更多的控制機會,而VB給我們這樣的機會則是太少太少了。

 

IDispatch叫做排程介面。它的作用何在呢?這個世上除了C++還有很多別的語言,比如VB VJVBScriptJavaScript等等。可以這麼說,如果這世上沒有這麼多亂七八糟的語言,那就不會有IDispatch:-) 我們知道COM元件是C++類,是靠虛擬函式表來呼叫函式的,對於VC來說毫無問題,這本來就是針對C++而設計的,以前VB不行,現在VB也可以用指標了,也可以通過VTable來呼叫函數了,VJ也可以,但還是有些語言不行,那就是指令碼語言,典型的如 VBScriptJavaScript。不行的原因在於它們並不支援指標,連指標都不能用還怎麼用多型性啊,還怎麼調這些虛擬函式啊。唉,沒辦法,也不能置這些指令碼語言於不顧吧,現在網頁上用的都是這些指令碼語言,而分散式應用也是COM元件的一個主要市場,它不得不被這些指令碼語言所呼叫,既然虛擬函式表的方式行不通,我們只能另尋他法了。時勢造英雄,IDispatch應運而生。:-) 排程介面把每一個函式每一個屬性都編上號,客戶程式要呼叫這些函式屬性的時侯就把這些編號傳給IDispatch介面就行了,IDispatch再根據這些編號呼叫相應的函式,僅此而已。當然實際的過程遠比這複雜,僅給一個編號就能讓別人知道怎麼呼叫一個函式那不是天方夜潭嗎,你總得讓別人知道你要呼叫的函式要帶什麼引數,引數型別什麼以及返回什麼東西吧,而要以一種統一的方式來處理這些問題是件很頭疼的事。IDispatch介面的主要函式是Invoke,客戶程式都呼叫它,然後Invoke再呼叫相應的函式,如果看一看MS的類庫裡實現 Invoke的程式碼就會驚歎它實現的複雜了,因為你必須考慮各種引數型別的情況,所幸我們不需要自己來做這件事,而且可能永遠也沒這樣的機會。:-)

 

IUnknown

 IUnknown
        {
        public:
            BEGIN_INTERFACE
            virtual HRESULT STDMETHODCALLTYPE QueryInterface( 
                REFIID riid,
                void __RPC_FAR *__RPC_FAR *ppvObject) = 0;
            
            virtual ULONG STDMETHODCALLTYPE AddRef( void) = 0;
            
            virtual ULONG STDMETHODCALLTYPE Release( void) = 0;
     
        };

 QueryInterface用於查詢元件實現的其它介面。COM規範規定任何元件、任何介面都必須從IUnknown繼承,IUnknown包含三個函式,分別是 QueryInterface、AddRef、Release。這三個函式是無比重要的,而且它們的排列順序也是不可改變的。


IClassFactory

    IClassFactory : public IUnknown
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE CreateInstance( 
            IUnknown *pUnkOuter,
            REFIID riid,
            void **ppvObject) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE LockServer( 
            BOOL fLock) = 0;
        
    };

IClassFactory的作用是建立COM元件。我們已經知道COM元件實際上就是一個類,那我們平常是怎麼例項化一個類物件的?是用‘new’命令!很簡單吧,COM元件也一樣如此。但是誰來new它呢?不可能是客戶程式,因為客戶程式不可能知道元件的類名字,如果客戶知道元件的類名字那元件的可重用性就要打個大大的折扣了,事實上客戶程式只不過知道一個代表著元件的128位的數字串而已,

IClassFactory最重要的一個函式就是CreateInstance,顧名思議就是建立元件例項,一般情況下我們不會直接呼叫它,API函式都為我們封裝好它了,只有某些特殊情況下才會由我們自己來呼叫它,這也是VC編寫COM元件的好處,使我們有了更多的控制機會,而VB給我們這樣的機會則是太少太少了。

IDispatch

  IDispatch : public IUnknown
    {
    public:
        virtual HRESULT STDMETHODCALLTYPE GetTypeInfoCount( 
            UINT *pctinfo) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE GetTypeInfo( 
            UINT iTInfo,
            LCID lcid,
            ITypeInfo **ppTInfo) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE GetIDsOfNames( 
            REFIID riid,
            LPOLESTR *rgszNames,
            UINT cNames,
            LCID lcid,
            DISPID *rgDispId) = 0;
        
        virtual HRESULT STDMETHODCALLTYPE Invoke( 
            DISPID dispIdMember,
            REFIID riid,
            LCID lcid,
            WORD wFlags,
            DISPPARAMS *pDispParams,
            VARIANT *pVarResult,
            EXCEPINFO *pExcepInfo,
            UINT *puArgErr) = 0;