• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            山寨:不是最好的,是最適合我們的!歡迎體驗(yàn)山寨 中文版MSDN

            Blog @ Blog

            當(dāng)華美的葉片落盡,生命的脈絡(luò)才歷歷可見(jiàn)。 -- 聶魯達(dá)

            常用鏈接

            統(tǒng)計(jì)

            積分與排名

            BBS

            Blog

            Web

            最新評(píng)論

            學(xué)習(xí)筆記

             

            起源及復(fù)合文件

            復(fù)合文件概念:

            文件的 COM 結(jié)構(gòu)化存儲(chǔ)的實(shí)現(xiàn)。復(fù)合文件將單獨(dú)對(duì)象存儲(chǔ)在單一的、結(jié)構(gòu)化文件中,此文件由兩個(gè)主要元素組成:存儲(chǔ)對(duì)象流對(duì)象。二者結(jié)合使用,可象文件內(nèi)的文件系統(tǒng)一樣起作用。

            特點(diǎn):

            1)復(fù)合文件的內(nèi)部是使用指針構(gòu)造的一棵樹進(jìn)行管理的。使用的是單向指針,因此當(dāng)做定位操作的時(shí)候,向后定位比向前定位要快

            2)復(fù)合文件中的流對(duì)象,是真正保存數(shù)據(jù)的空間。它的存儲(chǔ)單位512字節(jié)。

            3)不同的進(jìn)程,或同一個(gè)進(jìn)程的不同線程可以同時(shí)訪問(wèn)一個(gè)復(fù)合文件的不同部分而互不干擾

            4)復(fù)合文件則提供了非常方便的增量訪問(wèn)能力

            5)當(dāng)頻繁地刪除文件,復(fù)制文件后,磁盤空間會(huì)變的很零碎,需要使用磁盤整理工具進(jìn)行重新整合。和磁盤管理非常相似,復(fù)合文件也會(huì)產(chǎn)生這個(gè)問(wèn)題,在適當(dāng)?shù)臅r(shí)候也需要整理,但比較簡(jiǎn)單,只要調(diào)用一個(gè)函數(shù)就可以完成了

            復(fù)合文件函數(shù)
              復(fù)合文件的函數(shù)和磁盤目錄文件的操作非常類似。所有這些函數(shù),被分為3種類型:WIN API 全局函數(shù)存儲(chǔ) IStorage 接口函數(shù) IStream 接口函數(shù)接口看成是完成一組相關(guān)操作功能的函數(shù)集合

            小結(jié):

            復(fù)合文件,結(jié)構(gòu)化存儲(chǔ),是微軟組件思想的起源,在此基礎(chǔ)上繼續(xù)發(fā)展出了持續(xù)性、命名、ActiveX、對(duì)象嵌入、現(xiàn)場(chǎng)激活......一系列的新技術(shù)、新概念。因此理解和掌握復(fù)合文件是非常重要的,即使在你的程序中并沒(méi)有全面使用組件技術(shù),復(fù)合文件技術(shù)也是可以單獨(dú)被應(yīng)用的。

            GUID 和接口

            CLSID(注1)的方式間接描述這些對(duì)象數(shù)據(jù)的處理程序路徑CLSID 其實(shí)就是一個(gè)號(hào)碼,或者說(shuō)是一個(gè)16字節(jié)的數(shù)CLSID 的結(jié)構(gòu)定義如下:

            typedef struct _GUID  {
                DWORD Data1;    
            // 
            隨機(jī)數(shù)
                WORD Data2;    // 和時(shí)間相關(guān)
                WORD Data3;    // 和時(shí)間相關(guān)
                BYTE Data4[8]; // 和網(wǎng)卡MAC相關(guān)
            } GUID;

            typedef GUID CLSID;  
            // 組件ID
            typedef GUID IID;    // 接口ID
            產(chǎn)生 CLSID

            1.    如果使用開發(fā)環(huán)境編寫組件程序,則IDE會(huì)自動(dòng)幫你產(chǎn)生 CLSID

            2.    你可以手工寫 CLSID,但千萬(wàn)不要和人家已經(jīng)生成的 CLSID 重復(fù)呀,所以嚴(yán)重地不推薦

            3.    程序中,可以用函數(shù) CoCreateGuid() 產(chǎn)生 CLSID

            4.    使用工具產(chǎn)生 GUID(注2);

            微軟為了使用方便,也支持另一個(gè)字符串名稱方式,叫 ProgID由于 CLSID ProgID 其實(shí)是一個(gè)概念的兩個(gè)不同的表示形式,所以在程序中可以隨便使用任何一種。疑問(wèn)?!字符串名稱容易重復(fù)啊!!

            介紹一下 CLSID ProgID 之間的轉(zhuǎn)換方法和相關(guān)的函數(shù):

            函數(shù)

            功能說(shuō)明

            CLSIDFromProgID()

            CLSIDFromProgIDEx()

            ProgID得到CLSID。沒(méi)什么好說(shuō)的,你自己都可以寫,查注冊(cè)表貝

            ProgIDFromCLSID()

            CLSID 得到 ProgID,調(diào)用者使用完成后要釋放 ProgID 的內(nèi)存(5)

            CoCreateGuid()

            隨機(jī)生成一個(gè) GUID

            IsEqualGUID()IsEqualCLSID()IsEqualIID()

            比較2個(gè)ID是否相等

            StringFromCLSID()StringFromGUID2()StringFromIID()

            CLSID,IID 得到注冊(cè)表中CLSID樣式的字符串,注意釋放內(nèi)存

            關(guān)于接口

            COM接口(組件)的集合,接口是方法屬性的集合。 要了解COM,就得先了解IUnknown接口,IUnknown接口的C++形式的定義如下:  
              interface   IUnknown  
              {  
                      virtual   HRESULT   _stdcall   QueryInterface([in]REFIID   iid,[out]void   *   *   ppv)=0;  
                      virtual   ULONG   _stdcall   AddRef(void)=0;  
                      virtual   ULONG   _stdcall   Release(void)=0;  
              }  
             
            她實(shí)現(xiàn)了接口查詢引用計(jì)數(shù),她是一個(gè)純抽象基類。 所有COM   定義的接口都必須從她繼承。  

            IUnknown是所有接口的基礎(chǔ),他負(fù)責(zé)兩項(xiàng)工作:   
            IUnknown::QueryInterface
            負(fù)責(zé)得到該組件的其他接口的指針   
            IUnknown::AddRef/Release
            負(fù)責(zé)管理該組件的生存期,但有人使用該組件時(shí),保證該組件不會(huì)被意外刪除;再?zèng)]人使用該組件時(shí),保證該組件被自動(dòng)刪除  

            下面是容器和組件之間的一個(gè)模擬對(duì)話過(guò)程:

             

            容器 協(xié)商部分

            組件 應(yīng)答部分

            1

            根據(jù)CLSID啟動(dòng)組件
            CoCreateInstance()

            生成對(duì)象,執(zhí)行構(gòu)造函數(shù),執(zhí)行初始化動(dòng)作。

            2

            你有IUnknown接口嗎?

            有,給你!

            3

            恩,太好了,那么你有IPersistStorage接口嗎?(9)
            IUnknown::QueryInterface(IID_IPersistStorage...)

            沒(méi)有!

            4

            真差勁,連這個(gè)都沒(méi)有。那你有IPersistStreamInit接口嗎?(10)
            IUnknown::QueryInterface(IID_IPersistStreamInit...)

            哈,這個(gè)有,給!

            5

            好,好,這還差不多。你現(xiàn)在給我初始化吧。
            IPersistStreamInit::InitNew()

            OK,初始化完成了。

            6

            完成了?好!現(xiàn)在你讀數(shù)據(jù)去吧。
            IPersistStreamInit::Load()

            讀完啦。我根據(jù)數(shù)據(jù),已經(jīng)在窗口中顯示出來(lái)了。

            7

            好,現(xiàn)在咱們各自處理用戶的鼠標(biāo)、鍵盤消息吧......

            ......

            8

            哎呀!用戶要保存退出程序了。你的數(shù)據(jù)被用戶修改了嗎?
            IPersistStreamInit::IsDirty()

            改了,用戶已經(jīng)修改啦。

            9

            那好,那么用戶修改后,你的數(shù)據(jù)需要多大的存儲(chǔ)空間呀?
            IPersistStreamInit::GetSizeMax()

            恩,我算算呀......好了,總共需要500KB

            10

            暈,你這么個(gè)小玩意居然占用這么大空間?!......好了,你可以存了。
            IPersistStreamInit::Save()

            謝謝,我已經(jīng)存好了。

            11

            恩。拜拜了您那。(11)
            IPersistStreamInit::Release()
            IUnknown::Release()

            執(zhí)行析構(gòu)函數(shù),刪除對(duì)象。

            12

            我自己也該退出了......
            PostQuitMessage()

             

            數(shù)據(jù)類型

            簡(jiǎn)單調(diào)用組件

            1、組件的啟動(dòng)和釋放


            圖一 組件調(diào)用機(jī)制

            由上圖可以看出,當(dāng)調(diào)用組件的時(shí)候,其實(shí)是依靠代理(運(yùn)行在本地)和存根(運(yùn)行在遠(yuǎn)端)之間的通訊完成的。具體來(lái)說(shuō),當(dāng)客戶程序通過(guò) CoCreateInstance() 函數(shù)啟動(dòng)組件,則代理接管該調(diào)用,它和存根通訊,存根則它所在的本地(相對(duì)于客戶程序來(lái)說(shuō)就是遠(yuǎn)程了)執(zhí)行 new 操作加載對(duì)象。遵守幾個(gè)原則:
              (1)啟動(dòng)組件得到一個(gè)接口指針(Interface)后,不要調(diào)用AddRef()。因?yàn)橄到y(tǒng)知道你得到了一個(gè)指針,所以它已經(jīng)幫你調(diào)用了AddRef()函數(shù);
              (2)通過(guò)QueryInterface()得到另一個(gè)接口指針后,不要調(diào)用AddRef()。因?yàn)?/span>......和上面的道理一樣;
              (3)當(dāng)你把接口指針賦值給(保存到)另一個(gè)變量中的時(shí)候,請(qǐng)調(diào)用AddRef()
              (4)當(dāng)不需要再使用接口指針的時(shí)候,務(wù)必執(zhí)行Release()釋放;
              (5)當(dāng)使用智能指針的時(shí)候,可以省略指針的維護(hù)工作;

            2、內(nèi)存分配和釋放
            函數(shù)內(nèi)部根據(jù)實(shí)際需要?jiǎng)討B(tài)申請(qǐng)內(nèi)存,而調(diào)用者負(fù)責(zé)釋。這雖然違背了上述原則,但 COM 從方便性和效率出發(fā),確實(shí)是這么設(shè)計(jì)的。

             

            C語(yǔ)言

            C++語(yǔ)言

            Windows 平臺(tái)

            COM

            IMalloc 接口

            BSTR

            申請(qǐng)

            malloc()

            new

            GlobalAlloc()

            CoTaskMemAlloc()

            Alloc()

            SysAllocString()

            重新申請(qǐng)

            realloc()

             

            GlobalReAlloc()

            CoTaskRealloc()

            Realloc()

            SysReAllocString()

            釋放

            free()

            delete

            GlobalFree()

            CoTaskMemFree()

            Free()

            SysFreeString()

            以上這些函數(shù)必須要按類型配合使用(比如:new 申請(qǐng)的內(nèi)存,則必須用 delete 釋放)。在 COM 內(nèi)部,當(dāng)然你可以隨便使用任何類型的內(nèi)存分配釋放函數(shù),但組件如果需要與客戶進(jìn)行內(nèi)存的交互,則必須使用上表中的后三類函數(shù)族。
              (1BSTR 內(nèi)存在上回書中,已經(jīng)有比較豐富的介紹了,不再重復(fù);
              (2CoTaskXXX()函數(shù)族,其本質(zhì)上就是調(diào)用C語(yǔ)言的函數(shù)(malloc...);
              (3IMalloc 接口又是對(duì) CoTaskXXX() 函數(shù)族的一個(gè)包裝。包裝后,同時(shí)增強(qiáng)了一些功能,比如:IMalloc::GetSize()可以取得尺寸,使用 IMallocSpy 可以監(jiān)視內(nèi)存的使用;

            3參數(shù)傳遞方向
            參數(shù)是動(dòng)態(tài)分配的內(nèi)存指針,那么遵守如下的規(guī)定:

            方向

            申請(qǐng)人

            釋放人

            提示

            [in]

            調(diào)用者

            調(diào)用者

            組件接收指針后,不能重新分配內(nèi)存

            [out]

            組件

            調(diào)用者

            組件返回指針后,調(diào)用者愛(ài)咋咋地”(3)

            [in,out]

            調(diào)用者

            調(diào)用者

            組件可以重新分配內(nèi)存

             

            ATL 寫第一個(gè)組件(關(guān)于流程多寫代碼就熟悉了,這里不重復(fù)!)

            1建立 ATL 工程方法


            圖一、建立 ATL DLL 工程

            Dynamic Link Library(DLL) 表示建立一個(gè) DLL 的組件程序。
              Executable(EXE) 表示建立一個(gè) EXE 的組件程序。
              Service(EXE) 表示建立一個(gè)服務(wù)程序,系統(tǒng)啟動(dòng)后就會(huì)加載并執(zhí)行的程序。
              Allow merging of proxy/stub code 選擇該項(xiàng)表示把代理/存根代碼合并到組件程序中,否則需要單獨(dú)編譯,單獨(dú)注冊(cè)代理存根程序。代理/存根,這個(gè)是什么概念?還記得我們?cè)?/span>上回書中介紹的嗎?當(dāng)調(diào)用者調(diào)用進(jìn)程外或遠(yuǎn)程組件功能的時(shí)候,其實(shí)是代理/存根負(fù)責(zé)數(shù)據(jù)交換的。關(guān)于代理/存根的具體變成和操作,以后再說(shuō)啦......
              Support MFC 除非有特殊的原因,我們寫 ATL 程序,最好不要選擇該項(xiàng)。

            2、增加 ATL 對(duì)象類方法


            圖二、選擇建立簡(jiǎn)單COM對(duì)象

            Category Object 普通組件。其中可以選擇的組件對(duì)象類型很多,但本質(zhì)上,就是讓向?qū)臀覀兡J(rèn)加上一些接口。比如我們選 "Simple Object",則向?qū)Ыo我們的組件加上 IUnknown 接口;我們選 "Internet Explorer Object",則向?qū)С思由?/span> IUnknown 接口外,再增加一個(gè)給 IE 所使用的 IObjectWithSite 接口。當(dāng)然了,我們完全可以手工增加任何接口。
              Category Controls ActiveX 控件。其中可以選擇的 ActiveX 類型也很多。我們?cè)诤罄m(xù)的專門介紹 ActiveX 編程中再討論。
              Category Miscellaneous 輔助雜類組件。
              Categroy Data Access 數(shù)據(jù)庫(kù)類組件(我最討厭數(shù)據(jù)庫(kù)編程了,所以我也不會(huì))

            圖四、接口屬性

            Threading Model 選擇組件支持的線程模型。默認(rèn)Apartment,它代表什么那?簡(jiǎn)單地說(shuō):當(dāng)在線程中調(diào)用組件函數(shù)的時(shí)候,這些調(diào)用會(huì)排隊(duì)進(jìn)行。因此,這種模式下,我們可以暫時(shí)不用考慮同步的問(wèn)題。
               Interface 接口基本類型。Dual 表示支持雙接口(2),這個(gè)非常 非常重要,非常非常常用,但我們今天不講。Custom 表示自定義借口。切記!切記!我們的這第一個(gè) COM 程序中,一定要選擇它!!!!(如果你選錯(cuò)了,請(qǐng)刪除全部?jī)?nèi)容,重新來(lái)過(guò)。)
               Aggregation 我們寫的組件,將來(lái)是否允許被別人聚合(3)使用。Only 表示必須被聚合才能使用,有點(diǎn)類似 C++ 中的純虛類,你要是總工程師,只負(fù)責(zé)設(shè)計(jì)但不親自寫代碼的話,才選擇它。
              Support ISupportErrorInfo 是否支持豐富信息的錯(cuò)誤處理接口。以后就講。
              Support Connection Points 是否支持連接點(diǎn)接口(事件、回調(diào))。以后就講。
              Free Threaded Marshaler 暫時(shí)我也不知道

            3、添加接口函數(shù)方法

             

            編譯、注冊(cè)、調(diào)用

            關(guān)于編譯
              2-1 最小依賴
              最小依賴,表示編譯器會(huì)把 ATL 中必須使用的一些函數(shù)靜態(tài)連接到目標(biāo)程序中。這樣目標(biāo)文件尺寸會(huì)稍大,但獨(dú)立性更強(qiáng),安裝方便;反之系統(tǒng)執(zhí)行的時(shí)候需要有 ATL.DLL 文件的支持。如何選擇設(shè)置為最小依賴呢?答案是:刪除預(yù)定義宏“_ATL_DLL”,操作方法見(jiàn)圖一、圖二。


            圖一、在vc6.0中,設(shè)置方法

            圖二、在 vc.net 2003中,設(shè)置方法

              2-2 CRT庫(kù)
              如果在 ATL 組件程序中調(diào)用了 CRT 的運(yùn)行時(shí)刻庫(kù)函數(shù),比如開平方 sqrt() ,那么編譯的時(shí)候可能會(huì)報(bào)錯(cuò)“error LNK2001: unresolved external symbol _main”。怎么辦?刪除預(yù)定義宏“_ATL_MIN_CRT”!操作方法也見(jiàn)圖一、圖二。(vc.net 2003 中的這個(gè)項(xiàng)目屬性叫 ATL 中最小使用 CRT”
              2-3 MBCS/UNICODE
              這個(gè)不多說(shuō)了,在預(yù)定義宏中,分別使用 _MBCS _UNICODE
              2-4 IDL 的編譯
              COM 在設(shè)計(jì)初期,就定了一個(gè)目標(biāo):要能實(shí)現(xiàn)跨語(yǔ)言的調(diào)用。既然是跨語(yǔ)言的,那么組件的接口描述就必須在任何語(yǔ)言環(huán)境中都要能夠認(rèn)識(shí)。怎么辦?用 .h 文件描述?------ C語(yǔ)言程序員笑了,真方便!BASIC 程序員哭了:-( 因此,微軟使用了一個(gè)新的文件格式---IDL文件(接口定義描述語(yǔ)言)IDL 是一個(gè)文本文件,它的語(yǔ)言語(yǔ)法比較簡(jiǎn)單,很象C。具體 IDL 文件的講解,見(jiàn)下一回《COM 組件設(shè)計(jì)與應(yīng)用(八)之添加新接口》。IDL 經(jīng)過(guò)編譯,生成二進(jìn)制的等價(jià)類型庫(kù)文件 TLB 提供給其它語(yǔ)言來(lái)使用。圖三示意了 ATL COM 程序編譯的過(guò)程:


            圖三、ATL 組件程序編譯過(guò)程

              說(shuō)明1:編譯后,類型庫(kù)以 TLB 文件形式單獨(dú)存在,同時(shí)也保存在目標(biāo)文件的資源中。因此,我們將來(lái)在 #import 引入類型庫(kù)的時(shí)候,既可以指定 TLB 文件,也可以指定目標(biāo)文件;
              說(shuō)明2:我們作為 C/C++ 的程序員,還算是比較幸福的。因?yàn)?/span> IDL 編譯后,特意為我們提供了 C 語(yǔ)言形式的接口文件。
              說(shuō)明3IDL 編譯后生成代理/存根源程序,有:dlldata.cxxx_p.cxxxps.defxxxps.mak,我們可以用 NMAKE.EXE 再次編譯來(lái)產(chǎn)生真正的代理/存根DLL目標(biāo)文件(1)

            關(guān)于注冊(cè)
              情況1當(dāng)我們使用 ATL 編寫組件程序,注冊(cè)不用我們來(lái)負(fù)責(zé)。編譯成功后,IDE 會(huì)幫我們自動(dòng)注冊(cè);
              情況2當(dāng)我們使用 MFC 編寫組件程序,由于編譯器不知道你寫的是否是 COM 組件,所以它不會(huì)幫我們自動(dòng)注冊(cè)。這個(gè)時(shí)候,我們可以執(zhí)行菜單“Tools\Register Control”來(lái)注冊(cè)。
              情況3當(dāng)我們寫一個(gè)具有 COM 功能的 EXE 程序時(shí),注冊(cè)的方法就是運(yùn)行一次這個(gè)程序;
              情況4當(dāng)我們需要使用第三方提供的組件程序時(shí),可以命令行運(yùn)行“regsvr32.exe 文件名來(lái)注冊(cè)。順便說(shuō)一句,反注冊(cè)的方法是“regsvr32.exe /u 文件名
              情況5當(dāng)我們需要在程序中(比如安裝程序)需要執(zhí)行注冊(cè),那么:

            typedef HRESULT (WINAPI * FREG)();
            TCHAR szWorkPath[ MAX_PATH ];

            ::GetCurrentDirectory( 
            sizeof(szWorkPath), szWorkPath );    // 
            保存當(dāng)前進(jìn)程的工作目錄
            ::SetCurrentDirectory( 組件目錄 );    // 切換到組件的目錄

            HMODULE hDLL = ::LoadLibrary( 
            組件文件名 );    // 動(dòng)態(tài)裝載組件
            if(hDLL)
            {
                FREG lpfunc = (FREG)::GetProcAddress( hDLL, _T("DllRegisterServer") );    
            // 取得注冊(cè)函數(shù)指針
                // 
            如果是反注冊(cè),可以取得"DllUnregisterServer"函數(shù)指針
                if ( lpfunc )    lpfunc();    // 執(zhí)行注冊(cè)。這里為了簡(jiǎn)單,沒(méi)有判斷返回值
                ::FreeLibrary(hDLL);
            }

            ::SetCurrentDirectory(szWorkPath);    
            // 切換回原先的進(jìn)程工作目錄

            上面的示例,在多數(shù)情況下可以簡(jiǎn)化掉切換工作目錄的代碼部分。但是,如果這個(gè)組件在裝載的時(shí)候,它需要同時(shí)加載一些必須依賴的DLL時(shí),有可能由于它自身程序的 BUG 導(dǎo)致無(wú)法正確定位。咳......還是讓我們自己寫的程序,來(lái)彌補(bǔ)它的錯(cuò)誤吧......誰(shuí)讓咱們是好人呢 ,誰(shuí)讓咱們的水平比他高呢,誰(shuí)讓咱們?cè)?/span> vckbase 上是個(gè)榜眼......

            關(guān)于組件調(diào)用

            總的來(lái)說(shuō),調(diào)用組件程序大概有如下方法:

            #include 方法

            IDL編譯后,為方便C/C++程序員的使用,會(huì)產(chǎn)生xxx.hxxx_i.c文件。我們真幸福,直接#include后就可以使用了

            #import 方法

            比較通用的方法,vc 會(huì)幫我們產(chǎn)生包裝類,讓我們的調(diào)用更方便

            加載類型庫(kù)包裝類 方法

            如果組件提供了 IDispatch 接口,用這個(gè)方法調(diào)用組件是最簡(jiǎn)單的啦。不過(guò)還沒(méi)講IDispatch,只能看以后的文章啦

            加載ActiveX包裝類 方法

            ActiveX 還沒(méi)介紹呢,以后再說(shuō)啦

             

            實(shí)現(xiàn)多接口

            按照函數(shù)的功能進(jìn)行分類,把不同功能分類的函數(shù)用多個(gè)接口表現(xiàn)出來(lái)。這樣可以有如下的一些好處:
                1
            、一個(gè)接口中的函數(shù)個(gè)數(shù)有限、功能集中,使用者容易學(xué)習(xí)、記憶調(diào)用。一個(gè)接口到底提供多少個(gè)函數(shù)合適那?答案是:如果你是黑猩猩,那么一個(gè)接口最多3個(gè)函數(shù),如果你是人,那么一個(gè)接口最好不要超過(guò)7個(gè)函數(shù)。(1)
                2
            、容易維護(hù)。至少你肉眼搜索的時(shí)候也方便一些呀。
                3
            、容易升級(jí)。當(dāng)我們給組件增加函數(shù)的時(shí)候,不要修改已經(jīng)發(fā)表的接口,而是提供一個(gè)新的接口來(lái)完成功能擴(kuò)展

            實(shí)現(xiàn)

            1 import "oaidl.idl";
            2 import "ocidl.idl";
            3    [
            4        
            object,
            5        uuid(072EA6CA-7D08-4E7E-B2B7-B2FB0B875595),
                
            6        helpstring("IMathe Interface"),
            7        pointer_default(unique)
            8    ]
            9    
            interface IMathe : IUnknown
            10    
            {
            11        [helpstring("method Add")] HRESULT Add([
            inlong n1, [inlong n2, [out,retval] long *pnVal);
            12    };

            13 [
            14    uuid(CD7672F7-C0B4-4090-A2F8-234C0062F42C),
            15    version(1.0),
            16    helpstring("Simple3 1.0 Type Library")
            17 ]
            18 library SIMPLE3Lib
            19 
            {
            20    importlib("stdole32.tlb");
            21    importlib("stdole2.tlb");

            22    [
            23        uuid(C6F241E2-43F6-4449-A024-B7340553221E),
            24        helpstring("Mathe Class")
            25    ]
            26    coclass Mathe
            27    
            {
            28        [
            defaultinterface IMathe;
            29    };
            30 };

            1-2

            引入 IUnknown ATL已經(jīng)定義的其它接口描述文件。import 類似與 C 語(yǔ)言中的 #include

            3-12

            一個(gè)接口的完整描述

            4

            object 表示本塊描述的是一個(gè)接口。IDL文件是借用了PRC遠(yuǎn)程數(shù)據(jù)交換格式的說(shuō)明方法

            5

            uuid(......) 接口的 IID,這個(gè)值是 ATL 自動(dòng)生成的,可以手工修改或用 guidgen.exe 產(chǎn)生(3)

            6

            在某些軟件或工具中,能看到這個(gè)提示

            7

            定義接口函數(shù)中參數(shù)所使用指針的默認(rèn)屬性(4)

            9

            接口叫 IMathe 派生自 IUnknown,于是 IMathe 接口的頭三個(gè)函數(shù)一定就是QueryInterface,AddRefRelease

            10-12

            接口函數(shù)列表

            13-30

            類型庫(kù)的完整描述(類型庫(kù)的概念以后再說(shuō)),下面所說(shuō)明的行,是需要先了解的

            18

            #import 時(shí)候的默認(rèn)命名空間

            23

            組件的 CLSIDCoCreateInstance()的第一個(gè)參數(shù)就是它

            27-29

            接口列表

            28

            [default]表示誰(shuí)提供了IUnknown接口

            手工修改IDL文件,黑體字部分是手工輸入的。完成后保存。

            import "oaidl.idl";
            import "ocidl.idl";
                [
                    
            object,
                    uuid(072EA6CA-7D08-4E7E-B2B7-B2FB0B875595),
                
                    helpstring("IMathe Interface"),
                    pointer_default(unique)
                ]
                
            interface IMathe : IUnknown
                
            {
                    [helpstring("method Add")] HRESULT Add([
            inlong n1, [inlong n2, [out,retval] long *pnVal);
                };


                [    
            // 
            所謂手工輸入,其實(shí)也是有技巧的:把上面的接口描述(IMathe)復(fù)制、粘貼下來(lái),然后再改更方便哈
                    object,
                    uuid(072EA6CB-7D08-4E7E-B2B7-B2FB0B875595), 
            // 手工或用工具產(chǎn)生的 IID
                
                    helpstring("IStr Interface"),
                    pointer_default(unique)
                ]
                
            interface IStr : IUnknown
                
            {
                    
            // 目前還沒(méi)有任何接口函數(shù)
                };

             [
                uuid(CD7672F7-C0B4-4090-A2F8-234C0062F42C),
                version(1.0),
                helpstring("Simple3 1.0 Type Library")
            ]
            library SIMPLE3Lib
            {
                importlib("stdole32.tlb");
                importlib("stdole2.tlb");

                [
                    uuid(C6F241E2-43F6-4449-A024-B7340553221E),
                    helpstring("Mathe Class")
                ]
                coclass Mathe
                
            {
                    [
            defaultinterface IMathe;
                    
            interface IStr;    // 
            別忘了呦,這里還有一個(gè)那
                };
            };

                3-4、打開頭文件(Mathe.h),手工增加類的派生關(guān)系和接口入口表 ,然后保存。

            class ATL_NO_VTABLE CMathe : 
                
            public CComObjectRootEx <CComSingleThreadModel>,
                
            public CComCoClass <CMathe, &CLSID_Mathe>,
                
            public IMathe,    // 
            別忘了,這里加一個(gè)逗號(hào)
                public IStr    // 增加一個(gè)基類
            {
            public:
                CMathe()
                
            {
                }

            DECLARE_REGISTRY_RESOURCEID(IDR_MATHE)

            DECLARE_PROTECT_FINAL_CONSTRUCT()

            BEGIN_COM_MAP(CMathe)    
            // 接口入口表。這里填寫的接口,才能被QueryInterface()找到
                COM_INTERFACE_ENTRY(IMathe)
                COM_INTERFACE_ENTRY(IStr)
            END_COM_MAP()

                3-5、好了,一切就緒。接下來(lái),就可以在 IStr 接口中增加函數(shù)了。

             

            IDispatch 接口

            自動(dòng)化組件,其實(shí)就是實(shí)現(xiàn)了 IDispatch 接口的組件。IDispatch 接口有4個(gè)函數(shù),解釋語(yǔ)言的執(zhí)行器就通過(guò)這僅有的4個(gè)函數(shù)來(lái)執(zhí)行組件所提供的功能。IDispatch 接口用 IDL 形式說(shuō)明如下:(1)

            [

                object,

                uuid(00020400-0000-0000-C000-000000000046),     // IDispatch 接口的 IID = IID_IDispatch

                pointer_default(unique)

            ]

             

            interface IDispatch : IUnknown

            {

                typedef [unique] IDispatch * LPDISPATCH;        // 轉(zhuǎn)定義 IDispatch * LPDISPATCH

             

                HRESULT GetTypeInfoCount([out] UINT * pctinfo); // 有關(guān)類型庫(kù)的這兩個(gè)函數(shù),咱們以后再說(shuō)

                HRESULT GetTypeInfo([in] UINT iTInfo,[in] LCID lcid,[out] ITypeInfo ** ppTInfo);

             

                HRESULT GetIDsOfNames(        // 根據(jù)函數(shù)名字,取得函數(shù)序號(hào)(DISPID)

                            [in] REFIID riid,

                            [in, size_is(cNames)] LPOLESTR * rgszNames,

                            [in] UINT cNames,

                            [in] LCID lcid,

                            [out, size_is(cNames)] DISPID * rgDispId

                        );

             

                [local]               // 本地版函數(shù)

                HRESULT Invoke(       // 根據(jù)函數(shù)序號(hào),解釋執(zhí)行函數(shù)功能

                            [in] DISPID dispIdMember,

                            [in] REFIID riid,

                            [in] LCID lcid,

                            [in] WORD wFlags,

                            [in, out] DISPPARAMS * pDispParams,

                            [out] VARIANT * pVarResult,

                            [out] EXCEPINFO * pExcepInfo,

                            [out] UINT * puArgErr

                        );

             

                [call_as(Invoke)]     // 遠(yuǎn)程版函數(shù)

                HRESULT RemoteInvoke(

                            [in] DISPID dispIdMember,

                            [in] REFIID riid,

                            [in] LCID lcid,

                            [in] DWORD dwFlags,

                            [in] DISPPARAMS * pDispParams,

                            [out] VARIANT * pVarResult,

                            [out] EXCEPINFO * pExcepInfo,

                            [out] UINT * pArgErr,

                            [in] UINT cVarRef,

                            [in, size_is(cVarRef)] UINT * rgVarRefIdx,

                            [in, out, size_is(cVarRef)] VARIANTARG * rgVarRef

                        );

            }

            MFC 實(shí)現(xiàn)自動(dòng)化組件

            3-1:建立一個(gè)工作區(qū)(Workspace)
            3-2
            :建立一個(gè) MFC DLL 工程(Project),工程名稱為“Simple5”

            3-3:一定要選擇 automation,切記!切記!

            3-4:建立新類

            3-5:在新建類中支持automation

            Class information - Name 你隨便寫個(gè)類名子啦
            Class information - Base class
            一定要從 CComTarget 派生呀,只有它才提供了 IDispatch 的支持
            Automation - None
            表示不支持自動(dòng)化,你要選擇了它,那就白干啦
            Automation - Automation
            支持自動(dòng)化,但不能被直接實(shí)例化。后面在講解多個(gè) IDispatch 的時(shí)候就用到它了,現(xiàn)在先不要著急。
            Automation - Createable by type ID
            一定要選擇這個(gè)項(xiàng)目,這樣我們?cè)诤竺娴恼{(diào)用中,VB就能夠CreateObject(),VC就能夠CreateDispatch()對(duì)組件對(duì)象實(shí)例化了。注意一點(diǎn),這個(gè) ID 其實(shí)就是組件的 ProgID 啦。
                3-6
            :?jiǎn)?dòng) ClassWizard,選擇 Automation 卡片,準(zhǔn)備建立函數(shù)


                3-7
            :添加函數(shù)。我們要寫一個(gè)整數(shù)加法函數(shù)Add()

             

            IDispatch 及雙接口的調(diào)用 雙接口表示在一個(gè)接口中,同時(shí)支持自定義接口 IDispatch

            IDispatch 接口和雙接口
                使用者要想調(diào)用普通的 COM 組件功能,必須要加載這個(gè)組件的類型庫(kù)(Type library)文件 tlb(比如在 VC 中使用 #import)。然而,在腳本程序中,由于腳本是被解釋執(zhí)行的,所以無(wú)法使用加載類型庫(kù)的方式進(jìn)行預(yù)編譯。那么腳本解釋器如何使用 COM 組件那?這就是自動(dòng)化(IDispatch)組件大顯身手的地方了。IDispatch 接口需要實(shí)現(xiàn)4個(gè)函數(shù),調(diào)用者只通過(guò)這4個(gè)函數(shù),就能實(shí)現(xiàn)調(diào)用自動(dòng)化組件中所有的函數(shù)。這4個(gè)函數(shù)功能如下:

            HRESULT GetTypeInfoCount(
                [out] UINT * pctinfo)

            組件中提供幾個(gè)類型庫(kù)?當(dāng)然一般都是一個(gè)啦。
            但如果你在一個(gè)組件中實(shí)現(xiàn)了多個(gè) IDispatch 接口,那就不一定啦(注1

            HRESULT GetTypeInfo(
                [in] UINT iTInfo,
                [in] LCID lcid,
                [out] ITypeInfo ** ppTInfo)

            調(diào)用者通過(guò)該函數(shù)取得他想要的類型庫(kù)
            幸好,在 99% 的情況下,我們都不用關(guān)心這兩個(gè)函數(shù)的實(shí)現(xiàn),因?yàn)?/span> MFC/ATL 都幫我們完成了默認(rèn)的一個(gè)實(shí)現(xiàn),如果是自己完成函數(shù)代碼,甚至可以直接返回 E_NOTIMPL 表示沒(méi)有實(shí)現(xiàn)。(注2

            HRESULT GetIDsOfNames(
                [in] REFIID riid,
                [in,size_is(cNames)] LPOLESTR * rgszNames,
                [in] UINT cNames,
                [in] LCID lcid,
                [out,size_is(cNames)] DISPID * rgDispId)

            根據(jù)函數(shù)名稱取得函數(shù)序號(hào),為調(diào)用 Invoke() 做準(zhǔn)備。
            所謂函數(shù)序號(hào),大家去觀察雙接口 IDL 文件和 MFC ODL 文件,每一個(gè)函數(shù)和屬性都會(huì)有 [id(序號(hào))....] 這樣的描述。

            HRESULT Invoke(
                [in] DISPID dispIdMember,
                [in] REFIID riid,
                [in] LCID lcid,
                [in] WORD wFlags,
                [in,out] DISPPARAMS * pDispParams,
                [out] VARIANT * pVarResult,
                [out] EXCEPINFO * pExcepInfo,
                [out] UINT * puArgErr)

            根據(jù)序號(hào),執(zhí)行函數(shù)
            使用 MFC/ATL 寫的組件程序,我們也不必關(guān)心這個(gè)函數(shù)的實(shí)現(xiàn)。如果是自己寫代碼,則該函數(shù)類似如下實(shí)現(xiàn):
            switch(dispIdMember)
            {
                case 1: .....; break;
                case 2: .....; break;
                ....
            }
            其實(shí),就是根據(jù)序號(hào)進(jìn)行分支調(diào)用啦。(3)
             

            Invoke() 函數(shù)的實(shí)現(xiàn)就可以看出,使用 IDispatch 接口的程序,其執(zhí)行效率是比較低的。ATL 從效率出發(fā),實(shí)現(xiàn)了一種叫“雙接口(dual)的接口模式。下面我們來(lái)看看,到底什么是雙接口:


            圖一、雙接口(dual) 結(jié)構(gòu)示意圖

                從上圖中可以看出,所謂雙接口,其實(shí)是在一個(gè) VTAB 的虛函數(shù)表容納三個(gè)接口(因?yàn)槿魏谓涌诙际菑?/span> IUnknown 派生的,所以就不強(qiáng)調(diào) IUnknown 了,叫做雙接口)。我們?nèi)绻麖娜我庖粋€(gè)接口中調(diào)用 QueryInterface()得到另外的接口指針的話,其實(shí),得到的指針地址都是同一個(gè)。雙接口有什么好處那?答:好呀,多好呀,特別好呀......

            使用方式

            因?yàn)?/span>

            所以

            腳本語(yǔ)言使用組件

            解釋器只認(rèn)識(shí) IDispatch 接口

            可以調(diào)用,但執(zhí)行效率最低

            編譯型語(yǔ)言使用組件

            它認(rèn)識(shí) IDispatch 接口

            可以調(diào)用,執(zhí)行效率比較低

            編譯型語(yǔ)言使用組件

            它裝載類型庫(kù)后,就認(rèn)識(shí)了 Ixxx 接口

            可以直接調(diào)用 Ixxx 函數(shù),效率最高啦

            結(jié)論

            雙接口,既滿足腳本語(yǔ)言的使用方便性,又滿足編譯型語(yǔ)言的使用高效性。
            于是,我們寫的所有的 COM 組件接口,都用雙接口實(shí)現(xiàn)嗎?
            錯(cuò)!否!NO
            如果不是明確非要支持腳本的調(diào)用,則最好不要使用雙接口,因?yàn)椋?/span>

            如果所有函數(shù)都放在一個(gè)雙接口中,那么層次、結(jié)構(gòu)、分類不清

            如果使用多個(gè)雙接口,則會(huì)產(chǎn)生其它問(wèn)題(注4

            雙接口、IDispatch接口只支持自動(dòng)化的參數(shù)類型,使用受到限制,某些情況下很不方便嘍

            還有很多弊病呦,不過(guò)現(xiàn)在我想不起來(lái)嘍......

            使用方法

            示例程序

            自動(dòng)化組件的使用方式

            簡(jiǎn)要說(shuō)明

            示例0

            在腳本中調(diào)用

            第九回/第十回中,已經(jīng)做了介紹

            示例1

            使用 API 方式調(diào)用

            揭示 IDispatch 的調(diào)用原理,但傻子才去這么使用那,會(huì)累死了

            示例2

            使用 CComDispatchDriver 的智能指針包裝類

            比直接使用 API 方式要簡(jiǎn)單多啦,這個(gè)不錯(cuò)!

            示例3

            使用 MFC 裝載類型庫(kù)的包裝方式

            簡(jiǎn)單!好用!常用!但它本質(zhì)上是使用 IDispatch 接口,所以執(zhí)行效率稍差

            示例4

            使用 #import 方式加載類型庫(kù)方式

            #import 方式使用組件,咱們?cè)?/span>第七回中講過(guò)啦。常用!對(duì)雙接口組件,直接調(diào)用自定義接口函數(shù),不再經(jīng)過(guò) IDispatch,因此執(zhí)行效率最高啦

             

            錯(cuò)誤與異常處理

            事件和通知

            流程:
              客戶端啟動(dòng)組件(Simple11.IEvent1.1)并得到接口指針 IEvent1 *
              調(diào)用接口方法 IEvent1::Advise() 把客戶端內(nèi)部的一個(gè)接收器(sink)接口指針(ICallBack *)傳遞到組件服務(wù)器中;
              調(diào)用 IEvent1::Add() 去計(jì)算兩個(gè)整數(shù)的和;
              但是計(jì)算結(jié)果并不通過(guò)該函數(shù)返回,而是通過(guò) ICallBack::Fire_Result() 返回給客戶端;
              當(dāng)客戶端不再需要接受事件的時(shí)候,調(diào)用 IEvent1::Unadvise() 斷開和組件的聯(lián)系。

            連接點(diǎn)

            持續(xù)性

            屬性包

            posted on 2008-09-24 11:33 isabc 閱讀(887) 評(píng)論(0)  編輯 收藏 引用


            只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。
            網(wǎng)站導(dǎo)航: 博客園   IT新聞   BlogJava   博問(wèn)   Chat2DB   管理


            廣告信息(免費(fèi)廣告聯(lián)系)

            中文版MSDN:
            歡迎體驗(yàn)

            久久亚洲精品无码aⅴ大香| 久久久久高潮综合影院| 伊人久久综合精品无码AV专区| 91久久精品视频| 精品久久久久久久久午夜福利| 三级三级久久三级久久| 亚洲Av无码国产情品久久| 精品久久久久久无码人妻热| 久久久九九有精品国产| 国产精品久久网| A狠狠久久蜜臀婷色中文网| 欧美喷潮久久久XXXXx| 无码人妻久久一区二区三区| 东方aⅴ免费观看久久av| 中文字幕亚洲综合久久菠萝蜜| 久久青青草原精品国产软件| 久久久久亚洲精品无码网址| 久久久久久国产精品美女| 久久天天躁狠狠躁夜夜av浪潮 | 91久久婷婷国产综合精品青草 | 久久这里只有精品久久| 久久久精品午夜免费不卡| 久久本道伊人久久| 丁香五月综合久久激情| 99久久人人爽亚洲精品美女| 久久国产成人| 伊人久久五月天| 精品国产青草久久久久福利| 少妇久久久久久被弄高潮| 国产精品久久亚洲不卡动漫| 91精品无码久久久久久五月天| 久久er国产精品免费观看8| 久久久久一级精品亚洲国产成人综合AV区 | 伊人久久精品无码av一区| 久久综合精品国产二区无码| 久久国产精品久久国产精品| 久久精品亚洲男人的天堂| 性色欲网站人妻丰满中文久久不卡| 日日噜噜夜夜狠狠久久丁香五月| 精品国产一区二区三区久久| 久久久久国产成人精品亚洲午夜|