




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領
文檔簡介
1、PAGE PAGE 73目錄真是想不到系列之一(VB到底為我們做了什么)(02)真是想不到系列之二(VB葵花寶典指針技術)(11)真是想不到系列之三(VB指針葵花寶典之函數(shù)指針)(22)真是想不到系列之四(VB指針葵花寶典之SafeArray)(27)真是想不到系列之五(高效字串指針類)(37)真是想不到系列之六(有用的技術和沒用的指針)(41)Matthew Curland的VB函數(shù)指針調(diào)用收藏(45) 每次看大師的東西到了精彩之處,我就會拍案叫絕:哇噻,真是想不到!。在經(jīng)過很多次這種感慨之后,我發(fā)現(xiàn)只要我們動了腦筋,我們自己也能有讓別人想不到的東西。于是想到要把這些想不到的東拿出來和大家一
2、起分享,希望拋磚引玉,能引出更多讓人想不到的東西。 真是想不到系列之一:VB到底為我們做了什么?關鍵字:VB、底層、WIN32、API、COM難度:中級要求:熟悉VB,會用VC調(diào)試器,了解WIN32 SDK、COM。 VB一直以來被認為有以下優(yōu)缺點:優(yōu)點是上手快、開發(fā)效率高;缺點是能力有限,運行效率低。這正是有些軟件把VB做為首選語言,而有些軟件肯定不會用VB做的原因。而很多VC,DELPHI的程序員都認為VB里搞開發(fā)不自由,它讓我們做事變?nèi)菀椎耐瑫r,也讓我們發(fā)揮的余地越來越小。的確,簡單和功能強大這兩者本身就是一對矛盾。那怕一行代碼不寫,僅僅起動運行一個空窗體這樣簡單動作,VB在底下就為我們
3、做了大量復雜的工作(決不僅僅是注冊窗口類、顯示窗口、起動消息循環(huán)這么簡單),這些工作對程序員是透明的。我們在感謝VB開發(fā)小組對我們程序員體貼入微的同時,不禁也要責怪為什么在文檔中對這些底層的動作只字未提,雖然這些動作對最終的程序也許并無影響,但我們擁有知情權(quán),更何況這些動作有時的確會影響我們的工作(我將在本系列后面的VB多線程中談到這種影響)。 然而,所有希望從本文得到未公開技術秘密的朋友你將會很失望,因為我能夠知道的和你一樣多,我們所能做的一切就是站在外面來猜VB在里面做了什么?所以我決不是要帶大家一起去將VB反向工程,而是想通過猜想VB的內(nèi)部工作來將一些原來比較模糊的概念搞清楚。作為一個系
4、列的第一篇文章,它的目的是為了后面的深入打下基礎,所以我會在需要的時候指出我們必須掌握的知識點,如果你不清楚,請及時地學習相關書籍來補課,具體見參考書目。 最后,要聲明我在本文中所做的各種實驗和推斷僅是我個人的觀點,不能保證其正確性,并且不承擔任何相關的法律責任。 好,開始吧!首先準備好我們的武器,我下面要使用的工具主要有:VB6中文企業(yè)版+SP5(廢話),還有SPY+、Dependency Walk和OLE Viewer(以下簡稱SPY和DEPEND和OLEVIEW,SPY在VB光盤的commontoolsvb下的SPY目錄中,OLEVIEW是其下OLETOOLS目錄中的OLEVIEW.EX
5、E,注意其下還有一個OLE2VW32.EXE功能類似,不過本文所指的是OLEVIEW.EXE,還Denpend在其下的UnsupprtDEPEND里)。還要用用VC(上面提的工具在VC里有),因為我們還要看看VB生成的代碼,搞VB高級開發(fā)的朋友一定要會用VC調(diào)試器,懂點匯編更好。當然,本文的重點不在這兒,所以沒有VC也不要緊。 打開VB6新建一標準EXE工程,在工程-引用對話框里應該已有四個引用,簡單點就是:1、Visual Basic For Application(VBA) 2、VB運行時對象庫 3、VB對象庫 4、OLE自動化。前面三個是任何VB工程都必須的,你想不要都不行,不信你試著去
6、掉對它們的引用。那么這三個核心類型庫各有什么用,在最終生成的可執(zhí)行程序中扮演怎樣的角色,這是本文要分析的第一個問題。 1)VB、VBA、VBS的區(qū)別你搞清楚了嗎? 首先VBS不應該和VB、VBA放在一起比較,它是微軟按照自己定義的ActiveX Scripting規(guī)范完全從頭開始寫成的腳本語言,雖然它的語法結(jié)構(gòu)和VB非常相似,但VBS僅僅依靠自動化對象來擴充其功能(只有后期綁定),它不能用implements來實現(xiàn)接口,不可能在VBS里直接使用API,沒有VarPtr這樣能得到指針的函數(shù),而VBS缺少的這些功能正是VB和VBA所特有的。當然,這不是說VBS不如VB或VBA,Windows已經(jīng)為
7、VBS提供了足夠強大的功能,我們可以用VBS來做腳本COM組件,而且借自動化對象的能力VBS可以說能力無限,所以有病毒用VBS來寫,對程序員來說VBS最重要的功能莫過于可以給自己的軟件提供宏功能,就象VC中提供的VBS宏功能那樣。注意,VBS是Free的,這和在Office中使用VBA來提供宏功能不同,要集成VBA需要價格不低的許可證費用,關于腳本語言可參見MSDN中Platform SDKTools and LanguagesScripting。(在本系列后面的文章腳本功能中我會實做一個用VBS來提供宏功能的小軟件) 那么VB和VBA又有什么不同呢?好吧,眼見為實,開始我們的實驗吧! 如果裝
8、了Office 2000以上版本,那么打開OLEVIEW,點擊File下的View TypeLib查看位于E:Program FilesCommon FilesMicrosoft SharedVBAVBA6下的VBE6.dll的類型庫,再用同樣的方法看看MSVBVM60.dll的類型庫,你會發(fā)現(xiàn)它們的類型庫基本上一模一樣,除了VBE6多了一個VBEGlobal接口和實現(xiàn)這個接口的Global對象,這個Global對象我們也可以在VBA編程環(huán)境(比如用WORD的VB編輯器)中用對象瀏覽器看到。它有二個方法Load和UnLoad,還有一個UserForms屬性,這是因為VBA6使用MS Form
9、2.0 Form設計器(FM20.dll)來設計和使用UserForm窗體(而在VB6中,我們可以使用多個設計器。比如通過使用MS Form 2.0 Form設計器,我們就能在VB中使用VBA所使用的UserForm用戶窗體)。和VBA的Global對象類似,在VB中也有GLobal對象,從VB的對象瀏覽器中可以知道它在vb6.olb這個類型庫中,這個類型庫就是每個工程都必須引用的VB對象庫,所有的VB內(nèi)置對象都在這里。而VBA的UserForm中使用的對象都在FM20.dll中。 除了上述不同外,VB和VBA還有一個最大的不同,就是VBA不能生成EXE可執(zhí)行文件,但可以猜想在IDE環(huán)境中VB
10、A和VB都要把代碼編譯成p-code來執(zhí)行,后面我將用實驗來證明的確是這樣,雖然在具體的實現(xiàn)上VB和VBA有很大的不同。 從上面的分析上可以看到VB和VBA還是有很大不同的,這種不同主要體現(xiàn)在編程環(huán)境和對象結(jié)構(gòu)上,但在本質(zhì)上它們之間卻有著不可割舍的血源關系。如果剛才你仔細地觀察了MSVBVM60.dll的類型庫,你就會發(fā)現(xiàn)如下的片斷: / Generated .IDL file (by the OLE/COM Object Viewer) dllname(VBA6.DLL), uuid(35BFBDA0-2BCC-1069-82D5-00DD010EDFAA), helpcontext(0 x
11、000f6ec4) module Strings entry(0 x60000000), helpcontext(0 x000f665f) short _stdcall Asc(in BSTR String); entry(0 x60000001), helpcontext(0 x000f6e9f) BSTR _stdcall _B_str_Chr(in long CharCode); 什么?在MSVBVM60.dll中的對象其方法卻定義在VBA6.DLL中?!VB安裝目錄下不就有個VBA6.DLL嗎?再用OLEVIEW看看它,哇噻,真是想不到它居然和MSVBVM60.DLL的一模一樣。怎么回
12、事?趕快再拿出DEPEND來看看VBA6.dll、MSVBVM60.dll和VBE6.dll這三個DLL的輸出函數(shù)。哈,又有新發(fā)現(xiàn),我們可以發(fā)現(xiàn)在三個DLL的輸出函數(shù)中從編號512到717絕大部分都是一模一樣的一些以rtc開頭的函數(shù),比如595的rtcMsgBox(rtc是什么?應該是Run Time Component? Control? Code?有誰知道嗎?),這說明三個DLL都有著相同的運行時VBA函數(shù)。 我們再用DEPEND來觀察一下VB6.EXE, 我們可以發(fā)現(xiàn)VB6.EXE引入了VBA6.DLL中一些它特有的以Eb和Tip開頭的函數(shù),從這些函數(shù)的名稱上可以發(fā)現(xiàn)它們的功能都是IDE
13、相關的,比如79的EbShowCode和82的TipDeleteModule。VB6.EXE恰恰沒有引入任何rtc開頭的函數(shù)(注意一)。我們再來看看MSVBVM60.DLL,隨便找一個用了MsgBox函數(shù)的編譯后的文件,用DEPEND來觀察它,就會發(fā)現(xiàn)它引入MSVBVM60.DLL輸出的595號rtcMsgBox函數(shù)(注意二)。并且引入MSVBVM60.DLL中很多以下劃線開頭的函數(shù),比如_vbaVarAbs(注意三)。其實從這個三個注意中我們已經(jīng)可以進行一些猜想,無論對錯,你可以先想想。 如果你沒有跟著我做實驗,而僅僅是看這篇文章的話,我猜想你應該有點昏了。如果你自己動手做了這些實驗,現(xiàn)在你
14、應該充滿了疑問而急侍看到結(jié)論。所以請一定要親手試一試,學習研究問題的方法比看結(jié)論更重要。 到這里至少我們可以得出結(jié)論:VB和VBA本就是同宗的姐妹,只不過姐姐VB的功夫要比妹妹VBA歷害些。不過姐姐只會單打獨斗是女強人;妹妹卻只會傍大款。姐姐有生育能力,是真正的女人;妹妹卻不會生崽,但深譜相夫之道,一番教導指揮之下可使她老公增色不少,而VBS呢,也是大戶人家的女兒,不過沒有VB和VBA姐妹優(yōu)秀的血統(tǒng),嬌小玲瓏干不得粗活只能指揮些自動聽話的對象來干活,她樂于助人品德好不象VBA那樣只認大款,VB、VBA、vbs三個女人我都喜歡。 2)Native Code(本地代碼)到底做了什么? 打起精神,我
15、們再深入一步。用OLEVIEW得到的類型庫還不能正確的反映各對象方法對應的DLL中的函數(shù)入口,你應該已經(jīng)發(fā)現(xiàn)用OLEVIEW得到的IDL文件中各個方法的entry屬性值都是0 x600000XX這樣的假東西。要得到類型庫中各方法在DLL中的真正入口,我們需要自己來寫段程序。 即使在VB中我們也可以非常容易地獲取類型庫信息,再加上點COM初始化和調(diào)用代碼,我們就能用自己的代碼實現(xiàn)VB6才引入的CallByName函數(shù)(在本系列后面的Hack COM中我會更深入談談COM,作為一名VB程序員對COM的理解非常重要)。由于本文的關鍵不是指導如何在VB里使用類型庫,所以下面提供的方法盡量從簡。 新建一
16、個標準EXE工程,添加對TypeLib Infomation的引用,在Form中放一個名為lblInfo的標簽,然后添加如下代碼:程序1Private Sub Form_Load() Dim oTLInfo As TypeLibInfo Dim oMemInfo As MemberInfo Dim sDllName As String Dim sOrdinal As Integer Set oTLInfo = TLI.TypeLibInfoFromFile(MSVBVM60.DLL) lblInfo = MATH模塊包含以下方法: & vbCrLf For Each oMemInfo In o
17、TLInfo.TypeInfos.NamedItem(Math).Members With oMemInfo .GetDllEntry sDllName, vbNullString, sOrdinal lblInfo = lblInfo & .Name _ & 定義在 & sDllName & 中, _ & 其編號為 & sOrdinal _ & vbCrLf End With NextEnd Sub 運行以后我們就可以知道MATH模塊中的Abs方法定義在VBA6.DLL中,其編號為656。在DEPEND中查看VBA6.DLL中編號為656的函數(shù),果然就是rtcAbsVar,用VBE6.DLL
18、試試結(jié)果相同。 還記得前面的注意一吧,VB6.EXE沒有引入rtc開頭的函數(shù)這說明在IDE環(huán)境中執(zhí)行的VBA方法實際上是通過COM調(diào)用VBA對象庫中的方法(跟蹤p-code是噩夢,所以我無法驗證它用的是什么綁定方式)。而注意二中提到的最終可執(zhí)行程序中引入了rtcMsgBox,如我們所料最終的程序會直接調(diào)用它,這要比COM調(diào)用快一點,但在跟蹤最終程序時,我發(fā)現(xiàn)rtcMsgBox內(nèi)部卻是經(jīng)過了二萬五千里長征后才會去調(diào)用MessageBoxA這個API,其間有多次對其它對象的COM調(diào)用,慢!可能是因為顯示的是模態(tài)對話框,在多進程多線程環(huán)境有很多需要考慮的因素吧,如果你是瘋狂在意效率的程序員,你應該試
19、試用API來重寫MsgBox,絕對快不少。再來看看注意三,讓我們把以下的程序編譯成使用本地代碼的程序2.EXE(為了后面的實驗,可以在工程屬性的編譯選項卡中將它設成無優(yōu)化和生成符號化調(diào)試信息程序2.EXE):程序2Private Declare Sub DebugBreak Lib kernel32 ()Private Sub Main() Dim i As Long, j As Long Dim k i = &H1234 DebugBreak k = 1234 j = Abs(k) j = Abs(i) MsgBox ss j = VarPtr(i)End Sub 用DEPEND觀察程序2.
20、EXE,我們可以發(fā)現(xiàn)程序2.EXE并沒有如我們預期的一樣在引入595的rtcMsgBox的同時引入656的rtcAbsVar,相反它引入了_vbaVarAbs和_vbaI4Abs,看看函數(shù)名就知道一個針對的是Variant,一個針對的是long。這說明VB在最終生成的代碼中對象Abs這樣的可以進一步針對不同類型優(yōu)化的VBA函數(shù)進行了相應的處理,觀察一下所有以_vba開頭的函數(shù)絕大部分都是那些最基本最常用的VBA函數(shù),可以說_vba開頭的VBA函數(shù)是rtc開頭的VBA函數(shù)的優(yōu)化版本,它們基本上是VB開發(fā)小組重新寫的,絕大多數(shù)在函數(shù)內(nèi)部實現(xiàn)自身功能,而rtc開頭的函數(shù)大多數(shù)是調(diào)用COM服務對象來完
21、成工作。從這么多_vba開頭的函數(shù)上可以看出VB小組在Native Code(本地代碼)的優(yōu)化上下了不少功夫,這決對不是吹牛。它的確高度優(yōu)化了不少科學計算相關的函數(shù),以ABS為例Native Code要比p-code快4倍以上。但是并不是所有的計算函數(shù)都經(jīng)過了這樣的優(yōu)化,比如Rnd函數(shù),它就沒有對應的_vba開頭的優(yōu)化函數(shù),而是直接對應到rtcRandomNext函數(shù)上,雖然rtcRandomNext也已經(jīng)優(yōu)化過,但內(nèi)部依然用了COM調(diào)用,還是不如自己重寫的快,我不明白為什么VB開發(fā)小組沒有考慮為它寫一個對應的_vbaRnd。 不要以為上面的分析沒有意義,因為我們可以從現(xiàn)象看本質(zhì),也可以從本質(zhì)
22、來解釋現(xiàn)象。比如我們再做一個實驗,給你的代碼加入一個類模塊,你可以試試聲明一個和內(nèi)部方法同名的公有的方法(這是一個很有用的技術,在本系列后面的錯誤處理中我們會用到這種方法),比如我們可以聲明一個Public Function Rnd(x) as single,同樣我們可以自己寫一個同名的MsgBox。但是你試試能不能聲明一個Public Function abs(x) ,這時VB肯定會彈出一個莫名其妙的編譯錯誤提示框告訴你缺少標識符,這種錯誤發(fā)生在你的函數(shù)名和VB關鍵字沖突的時候。但是為什么同樣是MATH模塊中的函數(shù),abs是關鍵字,rnd卻不是,VB文檔里是不會告訴你為什么的,但如果你認真的
23、看了我上面的實驗分析,我們就能猜想這是因為VB對需要進一步優(yōu)化的函數(shù)已經(jīng)做了高度優(yōu)化處理,VB開發(fā)小組為了保護他們的勞動成果,并顯示他們對自己優(yōu)化技術的自信,而禁止我們重寫這些函數(shù),同時VB開發(fā)小組也承認還有些函數(shù)有待進一步優(yōu)化,所以準許我們重寫之。在這里我要提出一個偉大的猜想:凡是能夠被重寫的函數(shù)就能夠被優(yōu)化,就象凡是大于2的偶數(shù)就能夠被分解成兩個質(zhì)因數(shù)的和一樣。 說到優(yōu)化,還應該談談直接API調(diào)用和使用API類型庫的差別,還必須談談VB所使用的后端優(yōu)化器(和VC用的是一樣的優(yōu)化器),還想談談如何盡最大可能來使用vTable綁定(準備在本系列中另寫一篇優(yōu)化來談這些問題)。 看了本地代碼,我們
24、再來看看p-code,要是你看了MSDN中關于p-code的原理,你肯定會頭大。平心而論p-code真是一個了不起的技術,代碼大小平均可以縮小50%。我們把程序2編譯成p-code看看,還是用DEPEND來觀察,發(fā)現(xiàn)它并沒有引入_vba開頭函數(shù)(沒有使用優(yōu)化的VBA函數(shù)?),卻引入了CallEngine這樣的東西(肯定是為了調(diào)用p-code偽碼解釋引擎),而且和Native Code一樣都引入了rtcMsgBox(編譯生成的p-code在調(diào)用MsgBox時應該比在IDE環(huán)境中運行的p-code快)。 如果你迫不及待地運行了程序2,你就會發(fā)現(xiàn)它將彈出一個應用程序錯誤對話框,說程序發(fā)生異常。別怕,
25、這是因為調(diào)用了DebugBreak這個API的緣故,這個API其實就是產(chǎn)生一個Int 3中斷,使得我們能夠中斷程序執(zhí)行。如果你裝了VC這樣的支持即時調(diào)試的調(diào)試器,你可以在錯誤對話框中點擊取消,這樣可以起動調(diào)試器來調(diào)試程序。我就是這樣跟蹤程序運行的。如果你想看看VB生成的程序反匯編代碼可以自己試試,我們可以用同樣的技術在VB或VBA的IDE中來中斷程序執(zhí)行,比如我們完全可以在Word的VB編輯器中運行上面程序2的代碼,從而中斷于Word的進程中,并可觀察到VBA生成的p-code代碼。比如VB和VBA在IDE中生成的p-code代碼就會發(fā)現(xiàn)它們這間有很大的不同。 所以,IDE中運行的程序和最終生
26、成的程序是完全不同的。用SPY+看看你在IDE中運行的窗體,你會發(fā)現(xiàn)它在VB的主線程下,也就是說在IDE中你用程序做出的窗體和VB IDE工作窗口一樣屬于VB IDE,你的程序在IDE中運行時申請的資源也屬于VB IDE。有些程序在IDE中運行會讓IDE死掉(在VB5中寫純API多線程就千萬別在IDE中運行,定死無疑,相比之下VB6的IDE健壯得多)。還有些程序可能在IDE中能正常工作,但生成EXE后就工作不了??傊?,在寫系統(tǒng)程序時要考慮到這種不同可能引起的問題。 3)VB的編譯技術,要我怎么夸你,又要我怎么罵你。 看了上面對Native Code的高度評價,你可能會對VB做出的東西更有信心了
27、,腰板更直了。是的,作為VB程序員沒有什么需要害羞的,一個功力深厚的VB程序員理應拿比普通VC程序員更多的工資,因為他的生產(chǎn)力是VC程序員的好幾倍,而做出的程序在質(zhì)量上和VC做的相差無幾。 甚至有大師開玩笑說VB的內(nèi)置對象就是用VB寫出的,比如我們可以自己寫Form.cls、Label.ctl,呵呵,我們還真不能排除這種可能性(雖然用VB不可能直接生成vb6.olb)。如果真是這樣,看來VB小組自己都對自己的編譯優(yōu)化技術非常有信心。 實際上我們看看VB安裝目錄下的C2.exe的屬性,再看看VC的C2.DLL的屬性,就會發(fā)現(xiàn)它們是同一個東西,同樣Link.exe也是VC的,所以我們完全可以對VB
28、程序的后端優(yōu)化編譯器以及聯(lián)結(jié)放心了。它們根本就是VC開發(fā)小組東西,或者VB、VC都是同一個編譯器開發(fā)小組在做編譯模塊??傊?,我們可以壯著膽說我們VB做的程序其二次優(yōu)化和聯(lián)結(jié)用的是和VC一樣的技術,嘿嘿,你有的我也有,我有的你沒有的(純屬詭辯)。 還有,沒有任何編譯器比VB編譯器更快,因為在IDE中VB就是一種解釋型語言,這才是VB開發(fā)效率高的關鍵,快得幾乎感覺不得編譯過程。其請求時編譯,后臺編譯技術更是一只獨秀,厲害啊!想想看,別的語言的程序員有多少時間花在了等待代碼編譯和重新聯(lián)結(jié)上?。?不要高興得太早,因為最終的目的還是要生成可執(zhí)行文件。在VB中沒有分塊編譯和增量聯(lián)結(jié)的功能,VB在生成可執(zhí)行
29、程序時總是編譯所有模塊并完全重新聯(lián)結(jié),而在別的編譯語言中我們可以僅編譯最近修改過的文件(分塊編譯),聯(lián)結(jié)時將新生成的代碼附在可執(zhí)行程序的后面,并將原來的代嗎標記為作廢(增量聯(lián)結(jié),最終的可執(zhí)行程序會越來越大,但聯(lián)結(jié)時間大大縮短)。做實驗看看,會發(fā)現(xiàn)在VB中每次生成可執(zhí)行文件所花時間都是相同的。我不知VB開發(fā)小組為什么不提供分塊編譯和增量聯(lián)結(jié)的功能,可能VB開發(fā)小組認為生成可執(zhí)行文件在VB中不是經(jīng)常要做的工作。但是實際上這種理由是說不過去的,因為如前面所說IDE中運行程序和最終程序有很大不同,如我們要經(jīng)常編譯出可執(zhí)行文件才能真正對它進行Profile,又如我們要調(diào)試多線程程序不能在VB IDE中做
30、,在這些情況下每次修改后都要重新生成可執(zhí)行文件,我們浪費了不少時間去編譯已編譯過的代碼,聯(lián)結(jié)已聯(lián)結(jié)過的程序。我猜想這是因為VB生成可執(zhí)行程序時進行了全局優(yōu)化,所以必須得全部重新編譯聯(lián)結(jié)。但提供一個新的功能讓我們能夠生成不進行全局優(yōu)化的可以分塊編譯的調(diào)試版本,對Vb開發(fā)小組應該不是難事吧!(我有一個變通的解決方案,還在試驗中)在來看看VB6安裝目錄下的VBAEXE6.lib,怎么只有1k大一點,可以猜想里面應該不會有代碼,多半是些象vTable這樣的函數(shù)地址跳轉(zhuǎn)表,或者是些全局常量,我也不知道。但至少說明VB可以用靜態(tài)聯(lián)結(jié)庫了,為什么不把這個功能提供給我們,讓我們有更多的選擇。 再做個實驗看看,
31、做一個標準EXE工程,里面只有一個標準模塊,模塊里面只一個Sub Main,Sub Main里面什么也沒有,將它生成為EXE文件??纯?,嚯,有16k多。你要是有時間跟蹤這個什么也不做的程序看看,就會知道它要做很多事,初始化Err和App對象,準備COM調(diào)用,準備VB、VBA對象庫,甚至為使用ActiveX控制也做了準備,嘿嘿,看服務多周到。你必須得用VB對象庫中的控制,不用也不行。你再多找?guī)讉€EXE工程看看,有很多東西相同,都是一個模子做出的,而且你沒有選擇模子自由。ActiveX工程也是一樣,都是Dual雙接口,你做的ActiveX控制都必須要躲在一個Extender Object后面。是的
32、,在VB里有很多東西你沒有選擇的自由。如果需要這種自由要么不用VB,要么就得采取一些未公開的非官方的古怪的技巧(本系列文章最重要的目的之一,就是介紹這樣的非官方技巧)。 這又到文章開頭說的,VB讓我們做事情變得容易的同時也讓我們失去了不少自由。在最終代碼的生成上則也采取了公式化的做法。當然,我們應該全面地來看待這個問題,如同生產(chǎn)線上生產(chǎn)的東西不一定比手工的精致,群養(yǎng)的家禽不如野味好吃的道理一樣,如果需要精致的野味,意味著更多的勞動和更大的成本,這和VB所追求的更容易更便宜的目標是相違背的。 4)VB程序員也得有HACK精神。 本文的最后這個標題是嚴重離題了,但我想在此為本系列文章定下一個充滿H
33、ACK精神的基調(diào)。HACK精神是什么?沒有準確的定義,我的理解是:HACK精神 = 總想探尋未知領域的好奇心 + 凡事總想知道為什么的研究欲 + 總想拿出自己的東西的創(chuàng)新精神 + 解決問題的耐心和恒心。 VB的程序員也一樣需要這種精神。 最后,我們都知道VB開發(fā)小組已經(jīng)達上.NET的快車飛起來了,不能不說VB6以后再沒有VB的新版本了。微軟已經(jīng)用.NET為我們劃出了新的圈子,VB.NET是這個新圈子里的新產(chǎn)物。在圈子里面我們能夠飛得更高,但是圈子外面的天空更大,所以我依然樂意站在圈子外,虔誠地祈禱真正的VB7的誕生,阿門。 每次看大師的東西到了精彩之處,我就會拍案叫絕:哇噻,真是想不到!。在經(jīng)
34、過很多次這種感慨之后,我發(fā)現(xiàn)只要我們動了腦筋,我們自己也能有讓別人想不到的東西。于是想到要把這些想不到的東拿出來和大家一起分享,希望拋磚引玉,能引出更多讓人想不到的東西。 VB真是想不到系列之二:VB葵花寶典-指針技術關鍵字:VB、指針、動態(tài)內(nèi)存分配、效率、安全難度:中級至高級要求:熟悉VB,掌握基本的C,了解匯編,了解內(nèi)存分配原理。 想當年東方不敗,黑木崖密室一戰(zhàn),僅憑一根繡花針獨戰(zhàn)四大高手,神出鬼沒,堪稱天下武林第一高手。若想成為VB里的東方不敗,熟習VB葵花寶典,掌握VB指針技術,乃是不二的法門。 欲練神功,引刀,其實掌握VB指針技術,并不需要那么痛苦。因為說穿了,也就那么幾招,再勤加練
35、習,終可至神出鬼沒之境。廢話少說,讓我們先從指針的定義說起。 一、指針是什么? 不需要去找什么標準的定義,它就是一個32位整數(shù),在C語言和在VB里都可以用Long類型來表示。在32位Windows平臺下它和普通的32位長整型數(shù)沒有什么不同,只不過它的值是一個內(nèi)存地址,正是因為這個整數(shù)象針一樣指向一個內(nèi)存地址,所以就有了指針的概念。 有統(tǒng)計表明,很大一部分程序缺陷和內(nèi)存的錯誤訪問有關。正是因為指針直接和內(nèi)存打交道,所以指針一直以來被看成一個危險的東西。以至于不少語言,如著名的JAVA,都不提供對指針操作的支持,所有的內(nèi)存訪問方面的處理都由編譯器來完成。而象C和C+,指針的使用則是基本功,指針給了
36、程序員極大的自由去隨心所欲地處理內(nèi)存訪問,很多非常巧妙的東西都要依靠指針技術來完成。 關于一門高級的程序設計語言是不是應該取消指針操作,關于沒有指針操作算不算一門語言的優(yōu)點,我在這里不討論,因為互聯(lián)網(wǎng)上關于這方面的沒有結(jié)果的討論,已經(jīng)造成了占用幾個GB的資源。無論最終你是不是要下定決心修習指針技術葵花寶典,了解這門功夫總是有益處的。 注意:在VB里,官方是不鼓勵使用什么指針的,本文所講的任何東西你都別指望取得官方的技術支持,一切都要靠我們自己的努力,一切都更刺激! 讓我們開始神奇的VB指針探險吧! 二、來看看指針能做什么?有什么用? 先來看兩個程序,程序的功能都是交換兩個字串:【程序一】:標準
37、的做法SwapStr Sub SwapStr(sA As String, sB As String) Dim sTmp As String sTmp = sA: sA = sB: sB = sTmp End Sub【程序二】:用指針的做法SwapPtr Private Declare Sub CopyMemory Lib kernel32 Alias RtlMoveMemory _ (Destination As Any, Source As Any, ByVal Length As Long) Sub SwapPtr(sA As String, sB As String) Dim lTmp
38、As Long CopyMemory lTmp, ByVal VarPtr(sA), 4 CopyMemory ByVal VarPtr(sA), ByVal VarPtr(sB), 4 CopyMemory ByVal VarPtr(sB), lTmp, 4 End Sub 你是不是以為第一個程序要快,因為它看著簡單而且不用調(diào)用API(調(diào)用API需要額外的處理,VB文檔明確指出大量調(diào)用API將降低程序性能)。但事實上,在VB集成環(huán)境中運行,程序二要比程序一快四分之一;而編譯成本機代碼或p-code,程序二基本上要比程序一快一倍。下面是兩個函數(shù)在編譯成本機代碼后,運行不同次數(shù)所花時間的比較:運
39、行100000次,SwapStr需要170毫秒,SwapPtr需要90毫秒。運行200000次,SwapStr需要340毫秒,SwapPtr需要170毫秒。運行2000000次,SwapStr需要3300毫秒,SwapPtr需要1500毫秒。 的確,調(diào)用API是需要額外指令來處理,但是由于使用了指針技術,它沒有進行臨時字串的分配和拷貝,因此速度提高了不少。 怎么樣,想不到吧!C/C+程序員那么依賴指針,無非也是因為使用指針往往能更直接的去處理問題的根源,更有駕馭一切的快感。他們不是不知道使用指針的危險,他們不是不愿意開衛(wèi)星定位無級變速的汽車,只是騎摩托更有快感,而有些地方只有摩托才走得過去。
40、和在C里類似,在VB里我們使用指針也不過三個理由: 一是效率,這是一種態(tài)度一種追求,在VB里也一樣; 二是不能不用,因為操作系統(tǒng)是C寫的,它時刻都在提醒我們它需要指針; 三是突破限制,VB想照料我們的一切,VB給了我們很強的類型檢查,VB象我們老媽一樣,對我們關心到有時我們會受不了,想偶爾不聽媽媽的話嗎?你需要指針! 但由于缺少官方的技術支持,在VB里,指針變得很神秘。因此在C里一些基本的技術,在VB里就變得比較困難。本文的目的就是要提供給大家一種簡單的方法,來將C處理指針的技術拿到VB里來,并告訴你什么是可行的,什么可行但必須要小心的,什么是可能但不可行的,什么是根本就不可能的。 三、 程咬
41、金的三板斧 是的,程序二基本上就已經(jīng)讓我們看到VB指針技術的模樣了??偨Y(jié)一下,在VB里用指針技術我們需要掌握三樣東西:CopyMemory,VarPtr/StrPtr/ObjPtr, AdressOf. 三把斧頭,程咬金的三板斧,在VB里Hack的工具。1、CopyMemory 關于CopyMemory和Bruce McKinney大師的傳奇,MSDN的Knowledge Base中就有文章介紹,你可以搜索ID: Q129947的文章。正是這位大師給32位的VB帶來了這個可以移動內(nèi)存的API,也正是有了這個API,我們才能利用指針完成我們原來想都不敢想的一些工作,感謝Bruce McKinne
42、y為我們帶來了VB的指針革命。 如CopyMemory的聲明,它是定義在Kernel32.dll中的RtlMoveMemory這個API,32位C函數(shù)庫中的memcpy就是這個API的包裝,如MSDN文檔中所言,它的功能是將從Source指針所指處開始的長度為Length的內(nèi)存拷貝到Destination所指的內(nèi)存處。它不會管我們的程序有沒有讀寫該內(nèi)存所應有的權(quán)限,一但它想讀寫被系統(tǒng)所保護的內(nèi)存時,我們就會得到著名的Access Violation Fault(內(nèi)存越權(quán)訪問錯誤),甚至會引起更著名的general protection (GP) fault(通用保護錯誤) 。所以,在進行本系列
43、文章里的實驗時,請注意隨時保存你的程序文件,在VB集成環(huán)境中將工具-選項中的環(huán)境選項卡里的啟動程序時設為保存改變,并記住在立即窗口中執(zhí)行危險代碼之前一定要保存我們的工作成果。2、VatPtr/StrPtr/ObjPtr 它們是VB提供給我們的好寶貝,它們是VBA函數(shù)庫中的隱藏函數(shù)。為什么要隱藏?因為VB開發(fā)小組,不鼓勵我們用指針嘛。 實際上這三個函數(shù)在VB運行時庫MSVBVM60.DLL(或MSVBVM50.DLL)中是同一個函數(shù)VarPtr(可參見我在本系列第一篇文章里介紹的方法)。其庫型庫定義如下: entry(VarPtr), hidden long _stdcall VarPtr(in
44、 void* Ptr); entry(VarPtr), hidden long _stdcall StrPtr(in BSTR Ptr); entry(VarPtr), hidden long _stdcall ObjPtr(in IUnknown* Ptr); 即然它們是VB運行時庫中的同一個函數(shù),我們也可以在VB里用API方式重新聲明這幾個函數(shù),如下:Private Declare Function ObjPtr Lib MSVBVM60 Alias VarPtr _ (var As Object) As LongPrivate Declare Function VarPtr Lib MS
45、VBVM60 _ (var As Any) As Long(沒有StrPtr,是因為VB對字符串處理方式有點不同,這方面的問題太多,在本系列中另用一篇VB字符串全攻略來詳談。 順便提一下,聽說VB.NET里沒有這幾個函數(shù),但只要還能調(diào)用API,我們就可以試試上面的幾個聲明,這樣在VB.NET里我們一樣可以進行指針操作。 但是請注意,如果通過API調(diào)用來使用VarPtr,整個程序二SwapPtr將比原來使用內(nèi)置VarPtr函數(shù)時慢6倍。) 如果你喜歡刨根問底,那么下面就是VarPtr函數(shù)在C和匯編語言里的樣子: 在C里樣子是這樣的: long VarPtr(void* pv) return (l
46、ong)pv; 所對就的匯編代碼就兩行: mov eax,dword ptr esp+4 ret 4 彈出棧里參數(shù)的值并返回。 之所以讓大家了解VarPtr的具體實現(xiàn),是想告訴大家它的開銷并不大,因為它們不過兩條指令,即使加上參數(shù)賦值、壓棧和調(diào)用指令,整個獲取指針的過程也就六條指令。當然,同樣的功能在C語言里,由于語言的直接支持,僅需要一條指令即可。但在VB里,它已經(jīng)算是最快的函數(shù)了,所以我們完全不用擔心使用VarPtr會讓我們失去效率!速度是使用指針技術的根本要求。 一句話,VarPtr返回的是變量所在處的內(nèi)存地址,也可以說返回了指向變量內(nèi)存位置的指針,它是我們在VB里處理指針最重要的武器之
47、一。3、ByVal和ByRef ByVal傳遞的參數(shù)值,而ByRef傳遞的參數(shù)的地址。在這里,我們不用去區(qū)別傳指針/傳地址/傳引用的不同,在VB里,它們根本就是一個東西的三種不同說法,即使VB的文檔里也有地方在混用這些術語(但在C+里的確要區(qū)分指針和引用) 初次接觸上面的程序二SwapPtr的朋友,一定要搞清在里面的CopyMemory調(diào)用中,在什么地方要加ByVal,什么地方不加(不加ByVal就是使用VB缺省的ByRef) 準確的理解傳值和傳地址(指針)的區(qū)別,是在VB里正確使用指針的基礎。 現(xiàn)在一個最簡單的實驗來看這個問題,如下面的程序三:【程序三】:體會ByVal和ByRef Sub
48、TestCopyMemory() Dim k As Long k = 5Note: CopyMemory ByVal VarPtr(k), 40000, 4 Debug.Print k End Sub 上面標號Note處的語句的目的,是將k賦值為40000,等同于語句k=40000,你可以在立即窗口試驗一下,會發(fā)現(xiàn)k的值的確成了40000。 實際上上面這個語句,翻譯成白話,就是從保存常數(shù)40000的臨時變量處拷貝4個字節(jié)到變量k所在的內(nèi)存中。 現(xiàn)在我們來改變一個Note處的語句,若改成下面的語句:Note2: CopyMemory ByVal VarPtr(k), ByVal 40000, 4
49、 這句話的意思就成了,從地址40000拷貝4個字節(jié)到變量k所在的內(nèi)存中。由于地址40000所在的內(nèi)存我們無權(quán)訪問,操作系統(tǒng)會給我們一個Access Violation內(nèi)存越權(quán)訪問錯誤,告訴我們試圖讀取位置0 x00009c40處內(nèi)存時出錯,該內(nèi)存不能為Read。 我們再改成如下的語句看看。Note3: CopyMemory VarPtr(k), 40000, 4 這句話的意思就成了,從保存常數(shù)40000的臨時變量處拷貝4個字節(jié)到到保存變量k所在內(nèi)存地址值的臨時變量處。這不會出出內(nèi)存越權(quán)訪問錯誤,但k的值并沒有變。 我們可以把程序改改以更清楚的休現(xiàn)這種區(qū)別,如下面的程序四:【程序四】:看看我們的
50、東西被拷貝到哪兒去了 Sub TestCopyMemory() Dim i As Long, k As Long k = 5 i = VarPtr(k)NOTE4: CopyMemory i, 40000, 4 Debug.Print k Debug.Print i i = VarPtr(k)NOTE5: CopyMemory ByVal i, 40000, 4 Debug.Print k End Sub程序輸出:54000040000 由于NOTE4處使用缺省的ByVal,傳遞的是i的地址(也就是指向i的指針),所以常量40000拷貝到了變量i里,因此i的值成了40000,而k的值卻沒有變化
51、。但是,在NOTE4前有:i=VarPtr(k),本意是要把i本身做為一個指針來使用。這時,我們必須如NOTE5那樣用ByVal來傳遞指針i,由于i是指向變量k的指針,所以最后常量40000被拷貝了變量k里。 希望你已經(jīng)理解了這種區(qū)別,在后面問題的討論中,我還會再談到它。4、AddressOf 它用來得到一個指向VB函數(shù)入口地址的指針,不過這個指針只能傳遞給API使用,以使得API能回調(diào)VB函數(shù)。 本文不準備詳細討論函數(shù)指針,關于它的使用請參考VB文檔。5、拿來主義。 實際上,有了CopyMemory,VarPtr,AddressOf這三把斧頭,我們已經(jīng)可以將C里基本的指針操作拿過來了。 如下
52、面的C程序包括了大部分基本的指針指針操作: struct POINT int x; int y; ; int Compare(void* elem1, void* elem2) void PtrDemo() /指針聲明: char c = X; /聲明一個char型變量 char* pc; long* pl; /聲明普通指針 POINT* pPt; /聲明結(jié)構(gòu)指針 void* pv; /聲明無類型指針 int (*pfnCastToInt)(void *, void*);/聲明函數(shù)指針: /指針賦值: pc = &c; /將變量c的地址值賦給指針pc pfnCompare = Compare;
53、 /函數(shù)指針賦值。 /指針取值: c = *pc; /將指針pc所指處的內(nèi)存值賦給變量c /用指針賦值: *pc = Y /將Y賦給指針pc所指內(nèi)存變量里。 /指針移動: pc+; pl-; 這些對指針操作在VB里都有等同的東西, 前面討論ByVal和ByRef時曾說過傳指針和傳地址是一回事,實際上當我們在VB里用缺省的ByRef聲明函數(shù)參數(shù)時,我們已經(jīng)就聲明了指針。 如一個C聲明的函數(shù):long Func(char* pc) 其對應的VB聲明是:Function Func(pc As Byte) As Long 這時參數(shù)pc使用缺省的ByRef傳地址方式來傳遞,這和C里用指針來傳遞參數(shù)是一樣
54、。 那么怎么才能象C里那樣明確地聲明一個指針呢? 很簡單,如前所說,用一個32位長整數(shù)來表達指針就行。在VB里就是用Long型來明確地聲明指針,我們不用區(qū)分是普通指針、無類型指針還是函數(shù)指針,通通都可用Long來聲明。而給一個指針賦值,就是賦給它用VarPar得到的另一個變量的地址。具體見程序五?!境绦蛭濉浚和珻一樣,各種指針。 Type POINT X As Integer Y As Integer End Type Public Function Compare(elem1 As Long, elem2 As Long) As Long End Function Function FnPt
55、rToLong(ByVal lngFnPtr As Long) As Long FnPtrToLong = lngFnPtr End Function Sub PtrDemo() Dim l As Long, c As Byte, ca() As Byte, Pt As POINT Dim pl As Long, pc As Long, pv As Long, pPt As Long, pfnCompare As Long c = AscB(X) pl = VarPtr(l) 對應C里的long、int型指針 pc = VarPtr(c) 對應char、short型指針 pPt = VarPt
56、r(Pt) 結(jié)構(gòu)指針 pv = VarPtr(ca(0) 字節(jié)數(shù)組指針,可對應任何類型,也就是void* pfnCompare = FnPtrToLong(AddressOf Compare) 函數(shù)指針 CopyMemory c, ByVal pc, LenB(c) 用指針取值 CopyMemory ByVal pc, AscB(Y), LenB(c) 用指針賦值 pc = pc + LenB(c) : pl = pl - LenB(l) 指針移動 End Sub 我們看到,由于VB不直接支持指針操作,在VB里用指針取值和用指針賦值都必須用CopyMemory這個API,而調(diào)用API的代價是比
57、較高的,這就決定了我們在VB里使用指針不能象在C里那樣自由和頻繁,我們必須要考慮指針操作的代價,在后面的指針應用我們會再變談這個問題。 程序五中關于函數(shù)指針的問題請參考VB文檔,無類型指針void*會在下面關于Any的問題里說。 程序五基本上已經(jīng)包括了我們能在VB里進行的所有指針操作,僅此而已。 下面有一個小測試題,如果現(xiàn)在你就弄懂了上面程咬金的三板斧,你就應該能做得出來。 上面提到過,VB.NET中沒有VarPtr,我們可以用聲明API的方式來引入MSVBVM60.DLL中的VarPtr。現(xiàn)在的問題如果不用VB的運行時DLL文件,你能不能自己實現(xiàn)一個ObjPtr。答案在下一節(jié)后給出。 四、指
58、針使用中應注意的問題 1、關于ANY的問題 如果以一個老師的身份來說話,我會說:最好永遠也不要用Any!是的,我沒說錯,是永遠!所以我沒有把它放在程咬金的三板斧里。當然,這個問題和是不是應該使用指針這個問題一樣會引發(fā)一場沒有結(jié)果的討論,我告訴你的只是一個觀點,因為有時我們會為了效率上的一點點提高或想偷一點點懶而去用Any,但這樣做需要要承擔風險。 Any不是一個真正的類型,它只是告訴VB編譯器放棄對參數(shù)類型的檢查,這樣,理論上,我們可以將任何類型傳遞給API。 Any在什么地方用呢?讓我們來看看,在VB文檔里的是怎么說的,現(xiàn)在就請打開MSDN(Visual Studio 6自帶的版本),翻到V
59、isual Basic文檔-使用Visual Basic-部件工具指南-訪問DLL和Windows API部分,再看看將 C 語言聲明轉(zhuǎn)換為 Visual Basic 聲明這一節(jié)。文檔里告訴我們,只有C的聲明為LPVOID和NULL時,我們才用Any。實際上如果你愿意承擔風險,所有的類型你都可以用Any。當然,也可以如我所說,永遠不要用Any。 為什么要這樣?那為什么VB官方還要提供Any?是信我的,還是信VB官方的?有什么道理不用Any? 如前面所說,VB官方不鼓勵我們使用指針。因為VB所標榜的優(yōu)點之一,就是沒有危險的指針操作,所以的內(nèi)存訪問都是受VB運行時庫控制的。在這一點上,JAVA語言
60、也有著同樣的標榜。但是,同JAVA一樣,VB要避免使用指針而得到更高的安全性,就必須要克服沒有指針而帶來的問題。VB已經(jīng)盡最大的努力來使我們遠離指針的同時擁有強類型檢查帶來的安全性。但是操作系統(tǒng)是C寫的,里面到處都需要指針,有些指針是沒有類型的,就是C程序員常說的可怕的void*無類型指針。它沒有類型,因此它可以表示所有類型。如CopyMemory所對應的是C語言的memcpy,它的聲明如下: void *memcpy( void *dest, const void *src, size_t count ); 因memcpy前兩個參數(shù)用的是void*,因此任何類型的參數(shù)都可以傳遞給他。 一個用
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權(quán)或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 腦部協(xié)調(diào)力測試題及答案
- 大多數(shù)電焊測試題及答案
- 物業(yè)夜班人員管理制度
- 元朝邊疆管理制度
- 醫(yī)院預決算管理制度
- 結(jié)構(gòu)分析軟件評測師試題及答案
- 體系會議管理制度
- 果樹高壓育苗管理制度
- 廣告公司分店管理制度
- 拆除公司項目管理制度
- YS/T 756-2011碳酸銫
- GB/T 29047-2021高密度聚乙烯外護管硬質(zhì)聚氨酯泡沫塑料預制直埋保溫管及管件
- GB/T 21268-2014非公路用旅游觀光車通用技術條件
- GA/T 445-2010公安交通指揮系統(tǒng)建設技術規(guī)范
- 國家開放大學《可編程控制器應用實訓》形考任務2(實訓二)參考答案
- 室內(nèi)五人制足球競賽規(guī)則
- 2022年展覽館項目可行性研究報告
- 廣州版五年級英語下冊期末知識點復習ppt課件
- 產(chǎn)品研發(fā)流程管理制度管理辦法
- 計算方法全書課件完整版ppt整本書電子教案最全教學教程ppt課件
- 單代號網(wǎng)絡圖
評論
0/150
提交評論