精華區beta Programming 關於我們 聯絡資訊
如何用VB來實作IEnumVariant介面 Gwyshell 使用VB的人一定知道For Each Next 的用法吧!他可以用來將物件(Collection) 中所包含的所有物件列舉出來。但我們不禁要問,到底VB式如何達到這個效 果的呢?你如果曾經使用過物件精靈來產生一個Collection物件的話,你可能 會發現一件事實,VB Class Builder 所產生的Collection物件的是建構在 VBA.Collection 物件之上的。也就是說他所有的功能都已由原本的Collection 所實作了,但對於那些想追根究底的人可就傷腦筋了。到底他是如何運作的呢? 是否我們也能做出這樣的效果能。其實讓我們在仔細的看看Class Builder 所產 生的Collection物件,你會發現一個很特別的方法 NewEnum ,而他的傳回值 是一個IUnknown。如果你不曾接觸過COM( Component Object Module),你可 能會一頭霧水,倒底他是什麼東西呢?其實他已經充斥了我們都周遭,幾乎所 有的地方都有他的存在。在此我只能簡單的說他(IUnknown)是所有介面的基礎, 所有的介面都有IUnknown介面。如果你想知道的更仔細請你讀讀Inside of COM 這本書有詳細介紹。現在我們在回到正題上,既然NewEnum會傳回一個 IUnknown介面,那麼他一定是傳回一個什麼物件來。到目前為止我們只知道 NewEnum有些古怪,讓我們就從這裡再深入些吧!現在我們看看Class Builder 在NewEnum寫了些什麼?" Set NewEnum = mCol.[_NewEnum] " 原來他就是直 接將內部的VBA.Collection 的NewEnum傳回的。如果你利用物件瀏覽器來檢 視VBA.Collection 你將找不到 NewEnum 這個成員,他是一個隱藏的成員,所 以你必須設定顯示所有的隱藏成員。現在你應該能看見他了,"_NewEnum() As Unknown " 就是他。VB 將他傳回來有什麼作用呢?請你打開屬性編輯工具來 檢視Class Builder 所產生的Collection,之後找到NewEnum,並點選進階選項, 你會發現 Procedure ID "-4"。 其實他就是你的IUnknown介面了,如果你想知 道的更多,那請你自行看看書了,那已經離題了。不過我可以說的是屬性編輯 他能夠幫助你更進一步的描述物件。如果你想試試的話,就請你拿掉他,將他 改成別的值看看,再試一下For Each 你將會接到一個介面不符的錯誤。理由很 簡單,VB並不是呼叫 NewEnum 這個方法,而他是找他的IUnknown介面。 但目前為止我們都回沒有談到如何實作這個功能,但我有義務在說明如何實作 此功能之前先將必備的知識解釋清楚。前面我們說過VB的For Each 的動作其 實就是呼叫IUnknown介面,那麼到底是什麼介面讓他能真正的運作呢?其實 如果你是有點經驗的COM程式設計者,你應該早就知道了Interface IEnumVariant。如果你有SDK手冊的話,請你看看他的說明,裡面有很明白的 說明。IEnumVariant他是一個非常重要的介面,他的重要性你在StdOle2 的 TypeLib 就可以發現他。請你利用 Object Viewer(Browser)並尋找 IEnumVariant,你會在StdOle找到他。這裡我們將要面臨一件悲慘的事情發生, 幾乎所有的COM介面都是為C而設計的,所以很多的介面並不能在VB內直 接的被實作,如果你想知道更多有關在VB中COM的設計,請查閱MS發行 的VB Compoent的相關書籍。很不幸的我們的IEnumVariant就是一個例子,但 是我們明白COM是二元碼的標準,他是獨立於語言之外的,我們應該還是有 辦法使用他,不過這將會比使用C來實作他還困難。因為我們要作的是如何在 VB的運作方式下依然能克服這個先天的障礙。現在我們來看看SDK中 IEnumVariant的描述: interface IEnumVARIANT : IUnknown { virtual HRESULT Next(unsigned long celt, VARIANT FAR* rgvar, unsigned long FAR* pceltFetched) = 0; virtual HRESULT Skip(unsigned long celt) = 0; virtual HRESULT Reset() = 0; virtual HRESULT Clone(IEnumVARIANT FAR* FAR* ppenum) = 0; }; 想在VB中使用這個介面的我們必須克服好幾項困難,要如何在VB中描述這 些變數的結構、形態並使用他們,以及VB中對於這相介面的處理能力與方法 必須了解,這一部分也是最困難的。難的是你要如何明白VB是如何工作的, 這裡我並不想提及太多,還是回歸我們的正題。如果你曾自己寫過元件,特別 是用C去寫的人,一定明白什麼是介面描述語言,簡單的說他就是用來描述我 們物件的語言,他就是用來產生物件的Stub , Proxy , Marshaling等功能的工具 (詳見COM的說明)。但身為VB的使用者我們不必Care太多這些事情,這也 是為什麼VB這麼好用的原因。現在我稍微解釋一下為什麼我們不能在使用 Stdole.IEnumVariant,許多COM 的介面都會傳一個HRESULT值,但是很不幸 的VB就是不能處理他。現在請你打開本文章的範例程式,在Bugs目錄下的就 是一個會讓你VB Crash 的一個範例,稍後我在介紹他的功能。另一個使我們 不能使用他的原因是,Stdole.IEnumVariant所宣告的形態無法在VB中使用。不 過我已經用IDL寫了兩個相容的版本能在VB下運作,已經附於範例之中,目 錄TypeLib中存在這兩個版本IEnumVariant.tlb 與 IEnumVariantA.tlb,你就不 需要在操心了!。 現在讓我們仔細的看看我所附的範例,請解開Zip 檔 ,你可以發現三個目錄 Bugs\ 、 Safe\ 、TypeLib\ 分別存放 範例(1) 、範例(2)與形態資料庫,另外根 目錄下則放的是正式的版本。首先先打開Bugs版本,他是一個會 Crash VB 的 Bug的版本,他用以說明為什麼VB不能用直接使用這個介面。如果你執行這 個版本你的VB就會當掉,所以請小心使用。這個範例中有一個CEnumerate物 件他會直接Implements 這個介面並使用他(在Form Load 中使用他)。當你執 行他的時候,VB在做 For Each Next時就會以下列的順序呼叫他: 以Com的觀點 Objcet -> Retrive Interface IUnknown IUnknown->QueryInterface of IEnumVariant IEnumVariant ->Next 以VB的觀點 Set IEnumVariant = Object.NewEnum IEnumVariant.Next() 這裡我還必須說明的是 Next()可能是個不合法的名稱。在Bugs範例中你應該 在呼叫Next()時就會當掉了,理由就如同前面所說傳回值 HRESULT 的誤用。 IGwyVbEnumVARIANT_Next 照理說應該是個HRESULT,而在VB中他是個 long,這裡我們將傳回1 ,也就是IGwyVbEnumVARIANT_Next =1。其實 1 就 是 S_False(但通常我們不會直接使用1去判斷S_False,SDK中也是如此建議)。 但是由於HRESULT不能夠被正常的使用導致VB會因此而Crash,現在你可以 試試將IGwyVbEnumVARIANT_Next =1給註解掉,在執行看看,此時你會得到 一個For loop not initialized 的錯誤。所以我們說這是一個危險的IEnumVariant 版本至少在VB中是這樣,但他是一個較正確的版本。現在請你開啟 Safe版本, 他是一個鴕鳥心態版本,既然傳回值這麼危險,那麼索性拿掉傳回值,就不會 有什麼危險了,不過沒有危險,就沒有成果,他是一個不能Work的版本,他 是個無窮回圈,因為Next的傳回直是0,也就是 S_OK ,他會迫使VB不斷的 檢測這個Next()。現在我們將希望都放在Root的範例上了!這個版本就具有許 多技巧性了,也是幾乎在VB中實作不相容介面的常用方法,就是修改物件的 VTable。VTable ? 就是 Virtual Table ! 如果你是個C++的程式設計師,你就 明白VTable是什麼了。這裡所涉及的是你要如何修該VB的VTable的問題了, 我們將取代那些不合理的Entry將他們引入正途。現在讓你來猜猜這個不正常 的Entry的修正版應該放在哪?沒錯只有Moudle中能放。Why ? 理由很簡單, 只有放在Moudle中的方法可以使用 Addressof 來取得位址,另一個考量是這 個Entry只能存在一份,那就非Module莫屬了!現在請你看看Root版中的 CEnumerate物件得Class_Initialize() 中我將會把IEnumVariant的VTable修改, 我將他們以模組中的版本取代(如果你想更進一步了解Vtable的運作方式Inside of COM中有說明),之後Next , Skip 這兩個方法將用不到了。他會改成以Module 中的ModuleNext 與 ModuleSkip 來取代。之後你就可以按照SDK所說明的方 法去使用他了!但記得S_OK==0 , S_FALSE == 1。 寫到這裡我不禁覺得有時候你想為你的語言加一些所謂的“神兵力器“,你所 需要的努力往往使你必須付出更大代價與努力。但也或許是那就一份傻勁與執 著吧:) ~Gwyshell 1999/1/8 下載範例 http://www.mgt.ncu.edu.tw/~im841150/Programer.htm -- (Gwyshell) ActiveX Component 元件提供下載 , 並且提供VB範例程式提供下載. 包括 如何製作 MessageHook Server;Timer Class 列舉目錄下的所有檔案;Shell 動態選單, 就像 WinZip or UltraEditor 的右 鍵 功能. 辨識時鐘圖形上時鐘的時鐘; 鍵盤監視器;使用 mailslot 作為通訊的 範例程式 其中元件中還提供了 NetResource Class (包裝 WNet* API) Http://www.mgt.ncu.edu.tw/~im841150/ > -------------------------------------------------------------------------- < 發信人: Jack@ms9.hinet.net (Sun Jack), 看板: Programming 標 題: Re: 如何用VB來實作IEnumVariant介面 發信站: DCI HiNet (Sun Jan 17 18:38:51 1999) 轉信站: Ptt!news.ntu!spring!serv.hinet.net!netnews.hinet.net!news >如何用VB來實作IEnumVariant介面 Gwyshell > >使用VB的人一定知道For Each Next 的用法吧!他可以用來將物件(Collection) >中所包含的所有物件列舉出來。但我們不禁要問,到底VB式如何達到這個效 >果的呢?你如果曾經使用過物件精靈來產生一個Collection物件的話,你可能 >會發現一件事實,VB Class Builder 所產生的Collection物件的是建構在 >VBA.Collection 物件之上的。也就是說他所有的功能都已由原本的Collection >所實作了,但對於那些想追根究底的人可就傷腦筋了。到底他是如何運作的呢? >是否我們也能做出這樣的效果能。其實讓我們在仔細的看看Class Builder 所產 >生的Collection物件,你會發現一個很特別的方法 NewEnum ,而他的傳回值 >是一個IUnknown。如果你不曾接觸過COM( Component Object Module),你可 >能會一頭霧水,倒底他是什麼東西呢?其實他已經充斥了我們都周遭,幾乎所 >有的地方都有他的存在。在此我只能簡單的說他(IUnknown)是所有介面的基礎, >所有的介面都有IUnknown介面。如果你想知道的更仔細請你讀讀Inside of COM >這本書有詳細介紹。現在我們在回到正題上,既然NewEnum會傳回一個 >IUnknown介面,那麼他一定是傳回一個什麼物件來。到目前為止我們只知道 >NewEnum有些古怪,讓我們就從這裡再深入些吧!現在我們看看Class Builder >在NewEnum寫了些什麼?" Set NewEnum = mCol.[_NewEnum] " 原來他就是直 >接將內部的VBA.Collection 的NewEnum傳回的。如果你利用物件瀏覽器來檢 >視VBA.Collection 你將找不到 NewEnum 這個成員,他是一個隱藏的成員,所 >以你必須設定顯示所有的隱藏成員。現在你應該能看見他了,"_NewEnum() As >Unknown " 就是他。VB 將他傳回來有什麼作用呢?請你打開屬性編輯工具來 >檢視Class Builder 所產生的Collection,之後找到NewEnum,並點選進階選項, >你會發現 Procedure ID "-4"。 其實他就是你的IUnknown介面了,如果你想知 >道的更多,那請你自行看看書了,那已經離題了。不過我可以說的是屬性編輯 >他能夠幫助你更進一步的描述物件。如果你想試試的話,就請你拿掉他,將他 >改成別的值看看,再試一下For Each 你將會接到一個介面不符的錯誤。理由很 >簡單,VB並不是呼叫 NewEnum 這個方法,而他是找他的IUnknown介面。 > >但目前為止我們都回沒有談到如何實作這個功能,但我有義務在說明如何實作 >此功能之前先將必備的知識解釋清楚。前面我們說過VB的For Each 的動作其 >實就是呼叫IUnknown介面,那麼到底是什麼介面讓他能真正的運作呢?其實 >如果你是有點經驗的COM程式設計者,你應該早就知道了Interface >IEnumVariant。如果你有SDK手冊的話,請你看看他的說明,裡面有很明白的 >說明。IEnumVariant他是一個非常重要的介面,他的重要性你在StdOle2 的 >TypeLib 就可以發現他。請你利用 Object Viewer(Browser)並尋找 >IEnumVariant,你會在StdOle找到他。這裡我們將要面臨一件悲慘的事情發生, >幾乎所有的COM介面都是為C而設計的,所以很多的介面並不能在VB內直 >接的被實作,如果你想知道更多有關在VB中COM的設計,請查閱MS發行 >的VB Compoent的相關書籍。很不幸的我們的IEnumVariant就是一個例子,但 >是我們明白COM是二元碼的標準,他是獨立於語言之外的,我們應該還是有 >辦法使用他,不過這將會比使用C來實作他還困難。因為我們要作的是如何在 >VB的運作方式下依然能克服這個先天的障礙。現在我們來看看SDK中 >IEnumVariant的描述: > >interface IEnumVARIANT : IUnknown { > virtual HRESULT Next(unsigned long celt, > VARIANT FAR* rgvar, > unsigned long FAR* pceltFetched) = 0; > virtual HRESULT Skip(unsigned long celt) = 0; > virtual HRESULT Reset() = 0; >virtual HRESULT Clone(IEnumVARIANT FAR* FAR* ppenum) = 0; }; > >想在VB中使用這個介面的我們必須克服好幾項困難,要如何在VB中描述這 >些變數的結構、形態並使用他們,以及VB中對於這相介面的處理能力與方法 >必須了解,這一部分也是最困難的。難的是你要如何明白VB是如何工作的, >這裡我並不想提及太多,還是回歸我們的正題。如果你曾自己寫過元件,特別 >是用C去寫的人,一定明白什麼是介面描述語言,簡單的說他就是用來描述我 >們物件的語言,他就是用來產生物件的Stub , Proxy , Marshaling等功能的工具 >(詳見COM的說明)。但身為VB的使用者我們不必Care太多這些事情,這也 >是為什麼VB這麼好用的原因。現在我稍微解釋一下為什麼我們不能在使用 >Stdole.IEnumVariant,許多COM 的介面都會傳一個HRESULT值,但是很不幸 >的VB就是不能處理他。現在請你打開本文章的範例程式,在Bugs目錄下的就 >是一個會讓你VB Crash 的一個範例,稍後我在介紹他的功能。另一個使我們 >不能使用他的原因是,Stdole.IEnumVariant所宣告的形態無法在VB中使用。不 >過我已經用IDL寫了兩個相容的版本能在VB下運作,已經附於範例之中,目 >錄TypeLib中存在這兩個版本IEnumVariant.tlb 與 IEnumVariantA.tlb,你就不 >需要在操心了!。 >現在讓我們仔細的看看我所附的範例,請解開Zip 檔 ,你可以發現三個目錄 >Bugs\ 、 Safe\ 、TypeLib\ 分別存放 範例(1) 、範例(2)與形態資料庫,另外根 >目錄下則放的是正式的版本。首先先打開Bugs版本,他是一個會 Crash VB 的 >Bug的版本,他用以說明為什麼VB不能用直接使用這個介面。如果你執行這 >個版本你的VB就會當掉,所以請小心使用。這個範例中有一個CEnumerate物 >件他會直接Implements 這個介面並使用他(在Form Load 中使用他)。當你執 >行他的時候,VB在做 For Each Next時就會以下列的順序呼叫他: >以Com的觀點 >Objcet -> Retrive Interface IUnknown >IUnknown->QueryInterface of IEnumVariant >IEnumVariant ->Next >以VB的觀點 >Set IEnumVariant = Object.NewEnum >IEnumVariant.Next() >這裡我還必須說明的是 Next()可能是個不合法的名稱。在Bugs範例中你應該 >在呼叫Next()時就會當掉了,理由就如同前面所說傳回值 HRESULT 的誤用。 >IGwyVbEnumVARIANT_Next 照理說應該是個HRESULT,而在VB中他是個 >long,這裡我們將傳回1 ,也就是IGwyVbEnumVARIANT_Next =1。其實 1 就 >是 S_False(但通常我們不會直接使用1去判斷S_False,SDK中也是如此建議)。 >但是由於HRESULT不能夠被正常的使用導致VB會因此而Crash,現在你可以 >試試將IGwyVbEnumVARIANT_Next =1給註解掉,在執行看看,此時你會得到 >一個For loop not initialized 的錯誤。所以我們說這是一個危險的IEnumVariant >版本至少在VB中是這樣,但他是一個較正確的版本。現在請你開啟 Safe版本, >他是一個鴕鳥心態版本,既然傳回值這麼危險,那麼索性拿掉傳回值,就不會 >有什麼危險了,不過沒有危險,就沒有成果,他是一個不能Work的版本,他 >是個無窮回圈,因為Next的傳回直是0,也就是 S_OK ,他會迫使VB不斷的 >檢測這個Next()。現在我們將希望都放在Root的範例上了!這個版本就具有許 >多技巧性了,也是幾乎在VB中實作不相容介面的常用方法,就是修改物件的 >VTable。VTable ? 就是 Virtual Table ! 如果你是個C++的程式設計師,你就 >明白VTable是什麼了。這裡所涉及的是你要如何修該VB的VTable的問題了, >我們將取代那些不合理的Entry將他們引入正途。現在讓你來猜猜這個不正常 >的Entry的修正版應該放在哪?沒錯只有Moudle中能放。Why ? 理由很簡單, >只有放在Moudle中的方法可以使用 Addressof 來取得位址,另一個考量是這 >個Entry只能存在一份,那就非Module莫屬了!現在請你看看Root版中的 >CEnumerate物件得Class_Initialize() 中我將會把IEnumVariant的VTable修改, >我將他們以模組中的版本取代(如果你想更進一步了解Vtable的運作方式Inside >of COM中有說明),之後Next , Skip 這兩個方法將用不到了。他會改成以Module >中的ModuleNext 與 ModuleSkip 來取代。之後你就可以按照SDK所說明的方 >法去使用他了!但記得S_OK==0 , S_FALSE == 1。 > >寫到這裡我不禁覺得有時候你想為你的語言加一些所謂的“神兵力器“,你所 >需要的努力往往使你必須付出更大代價與努力。但也或許是那就一份傻勁與執 >著吧:) > > ~Gwyshell 1999/1/8 >下載範例 http://www.mgt.ncu.edu.tw/~im841150/VBTools.htm > > >-- >(Gwyshell) ActiveX Component 元件提供下載 , 並且提供VB範例程式提供下載. >包括 如何製作 MessageHook Server;Timer Class > 列舉目錄下的所有檔案;Shell 動態選單, 就像 WinZip or UltraEditor 的右鍵 > 功能. 辨識時鐘圖形上時鐘的時鐘; 鍵盤監視器;使用 mailslot 作為通訊的範例程式 > 其中元件中還提供了 NetResource Class (包裝 WNet* API) > Http://www.mgt.ncu.edu.tw/~im841150/ 我看了你的範例,有個問題想請教,在你Implement 的 IEnumNext中,未何 沒有呼叫 IUnknown 的 AddRef, (在 "Inside COM" 這本書第358頁提到這部份), 你是用什麼方法讓 Reference Count 能保持正確? 另外為什麼把參數 pceltFetched 設為0 ? P.S.:上次提到的VB 繼承的問題,我最近又翻了手上的書,才了解VB 是有繼承,我 參考的資料: "Inside COM" 第159 頁最後一段 "Programming Distributed Applications with COM and Microsoft Visual Basic 6.0" ,第二章中對這部份的講解比"Inside COM"還要清楚