VB的使用

語言: CN / TW / HK

一、今天講解VB的使用,明天講解VC與VB的相互呼叫:

1.指標是什麼?

不需要去找什麼標準的定義,它就是一個32位整數,在C語言和在VB裡都可以用Long型別來表示。在32位Windows平臺下它和普通的32位長整型數沒有什麼不同,只不過它

的值是一個記憶體地址,正是因為這個整數象針一樣指向一個記憶體地址,所以就有了指標的概念。

有統計表明,很大一部分程式缺陷和記憶體的錯誤訪問有關。正是因為指標直接和記憶體打交道,所以指標一直以來被看成一個危險的東西。以至於不少語言,如著名的JAVA,

都不提供對指標操作的支援,所有的記憶體訪問方面的處理都由編譯器來完成。而像C和C++,指標的使用則是基本功,指標給了程式設計師極大的自由去隨心所欲地處理記憶體訪問,

很多非常巧妙的東西都要依靠指標技術來完成。

注意:在VB裡,官方是不鼓勵使用什麼指標的,本文所講的任何東西你都別指望取得官方的技術支援,一切都要靠我們自己的努力,一切都更刺激!

2.來看看指標能做什麼?有什麼用?

先來看兩個程式,程式的功能都是交換兩個字串:

【程式一】:註釋:標準的做法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 As Long 

CopyMemory lTmp, ByVal VarPtr(sA), 4 

CopyMemory ByVal VarPtr(sA), ByVal VarPtr(sB), 4 

CopyMemory ByVal VarPtr(sB), lTmp, 4 

End Sub 

你是不是以為第一個程式要快,因為它看著簡單而且不用呼叫API(呼叫API需要額外的處理,VB文件明確指出大量呼叫API將降低程式效能)。但事實上,在VB整合環境中運

行,程式二要比程式一快四分之一;而編譯成本機程式碼或p-code,程式二基本上要比程式一快一倍。下面是兩個函式在編譯成本機程式碼後,執行不同次數所花時間的比較: 

執行100000次,SwapStr需要170毫秒,SwapPtr需要90毫秒。 

執行200000次,SwapStr需要340毫秒,SwapPtr需要170毫秒。 

執行2000000次,SwapStr需要3300毫秒,SwapPtr需要1500毫秒。 

怎麼樣,想不到吧!C/C++程式設計師那麼依賴指標,無非也是因為使用指標往往能更直接的去處理問題的根源,更有駕馭一切的快感。他們不是不知道使用指標的危險,他們不是不願意開衛星定位無級變速的汽車,只是騎摩托更有快感,而有些地方只有摩托才走得過去。 

  和在C裡類似,在VB裡我們使用指標也不過三個理由: 

一是效率,這是一種態度一種追求,在VB裡也一樣; 

二是不能不用,因為作業系統是C寫的,它時刻都在提醒我們它需要指標; 

三是突破限制,VB想照料我們的一切,VB給了我們很強的型別檢查,VB像我們老媽一樣,對我們關心到有時我們會受不了,想偶爾不聽媽媽的話嗎?你需要指標! 

但由於缺少官方的技術支援,在VB裡,指標變得很神祕。因此在C裡一些基本的技術,在VB裡就變得比較困難。本文的目的就是要提供給大家一種簡單的方法,來將C處理指標的技術拿到VB裡來,並告訴你什麼是可行的,什麼可行但必須要小心的,什麼是可能但不可行的,什麼是根本就不可能的。     

3. 程咬金的三板斧    

是的,程式二基本上就已經讓我們看到VB指標技術的模樣了。總結一下,在VB裡用指標技術我們需要掌握三樣東西:CopyMemory,VarPtr/StrPtr/ObjPtr, AdressOf. 三把斧頭,程咬金的三板斧,在VB裡Hack的工具。 

1)、CopyMemory 

關於CopyMemory和Bruce McKinney大師的傳奇,MSDN的Knowledge Base中就有文章介紹,你可以搜尋"ID: Q129947"的文章。正是這位大師給32位的VB帶來了這個可以移動記憶體的API,也正是有了這個API,我們才能利用指標完成我們原來想都不敢想的一些工作,感謝Bruce McKinney為我們帶來了VB的指標革命。  

如CopyMemory的宣告,它是定義在Kernel32.dll中的RtlMoveMemory這個API,32位C函式庫中的memcpy就是這個API的包裝,如MSDN文件中所言,它的功能是將從Source指標所指處開始的長度為Length的記憶體拷貝到Destination所指的記憶體處。它不會管我們的程式有沒有讀寫該記憶體所應有的許可權,一但它想讀寫被系統所保護的記憶體時, 我們就會得到著名的Access Violation Fault(記憶體越權訪問錯誤),甚至會引起更著名的general protection (GP) fault(通用保護錯誤) 。所以,在進行本系列文章裡的實驗時, 請注意隨時儲存你的程式檔案,在VB整合環境中將"工具"->"選項"中的"環境"選項卡里的"啟動程式時"設為"儲存改變",並記住在"立即"視窗中執行危險程式碼之前一定要儲存我們的工作成果。 

2)、VatPtr/StrPtr/ObjPtr 

它們是VB提供給我們的好寶貝,它們是VBA函式庫中的隱藏函式。為什麼要隱藏?因為VB開發小組,不鼓勵我們用指標嘛。 

實際上這三個函式在VB執行時庫MSVBVM60.DLL(或MSVBVM50.DLL)中是同一個函式VarPtr(可參見我在本系列第一篇文章裡介紹的方法)。 

其庫型庫定義如下: 

[entry("VarPtr"), hidden] 

long _stdcall VarPtr([in] void* Ptr); 

[entry("VarPtr"), hidden] 

long _stdcall StrPtr([in] BSTR Ptr); 

[entry("VarPtr"), hidden] 

long _stdcall ObjPtr([in] IUnknown* Ptr);    
即然它們是VB執行時庫中的同一個函式,我們也可以在VB裡用API方式重新宣告這幾個函式,如下: 

Private Declare Function ObjPtr Lib "MSVBVM60" Alias "VarPtr" _ 

(var As Object) As Long 

Private Declare Function VarPtr Lib "MSVBVM60"  _ 

(var As Any) As Long 

(沒有StrPtr,是因為VB對字串處理方式有點不同,這方面的問題太多,在本系列中另用一篇《VB字串全攻略》來詳談。 

順便提一下,聽說VB.NET裡沒有這幾個函式,但只要還能呼叫API,我們就可以試試上面的幾個宣告,這樣在VB.NET裡我們一樣可以進行指標操作。 

但是請注意,如果通過API呼叫來使用VarPtr,整個程式二SwapPtr將比原來使用內建VarPtr函式時慢6倍。) 

如果你喜歡刨根問底,那麼下面就是VarPtr函式在C和組合語言裡的樣子: 

在C裡樣子是這樣的: 

long VarPtr(void* pv){ 

return (long)pv; 

所對就的彙編程式碼就兩行: 

moveax,dword ptr [esp+4] 

ret             註釋:彈出棧裡引數的值並返回。 
之所以讓大家瞭解VarPtr的具體實現,是想告訴大家它的開銷並不大,因為它們不過兩條指令,即使加上引數賦值、壓棧和呼叫指令,整個獲取指標的過程也就六條指令。當然,同樣的功能在C語言裡,由於語言的直接支援,僅需要一條指令即可。但在VB裡,它已經算是最快的函數了,所以我們完全不用擔心使用VarPtr會讓我們失去效率!速度是使用指標技術的根本要求。 

一句話,VarPtr返回的是變數所在處的記憶體地址,也可以說返回了指向變數記憶體位置的指標,它是我們在VB裡處理指標最重要的武器之一。 

3)、ByVal和ByRef 

ByVal傳遞的引數值,而ByRef傳遞的引數的地址。在這裡,我們不用去區別傳指標/傳地址/傳引用的不同,在VB裡,它們根本就是一個東西的三種不同說法,即使VB的文件裡也有地方在混用這些術語(但在C++裡的確要區分指標和引用) 

初次接觸上面的程式二SwapPtr的朋友,一定要搞清在裡面的CopyMemory呼叫中,在什麼地方要加ByVal,什麼地方不加(不加ByVal就是使用VB預設的ByRef) 

準確的理解傳值和傳地址(指標)的區別,是在VB里正確使用指標的基礎。 

現在一個最簡單的實驗來看這個問題,如下面的程式三: 

【程式三】:註釋:體會ByVal和ByRef 

Sub TestCopyMemory() 

Dim k As Long 

k = 5 

Note:   CopyMemory ByVal VarPtr(k), 40000, 4 

Debug.Print k 

End Sub 

上面標號Note處的語句的目的,是將k賦值為40000,等同於語句k=40000,你可以在"立即"視窗試驗一下,會發現k的值的確成了40000。 

實際上上面這個語句,翻譯成白話,就是從儲存常數40000的臨時變數處拷貝4個位元組到變數k所在的記憶體中。 

現在我們來改變一個Note處的語句,若改成下面的語句: 

Note2:   CopyMemory ByVal VarPtr(k), ByVal 40000, 4 
這句話的意思就成了,從地址40000拷貝4個位元組到變數k所在的記憶體中。由於地址40000所在的記憶體我們無權訪問,作業系統會給我們一個Access Violation記憶體越權訪問錯誤,告訴我們"試圖讀取位置0x00009c40處記憶體時出錯,該記憶體不能為註釋:Read註釋:"。 

我們再改成如下的語句看看。 

Note3:   CopyMemory VarPtr(k), 40000, 4 

這句話的意思就成了,從儲存常數40000的臨時變數處拷貝4個位元組到到儲存變數k所在記憶體地址值的臨時變數處。這不會出出記憶體越權訪問錯誤,但k的值並沒有變。 

我們可以把程式改改以更清楚的休現這種區別,如下面的程式四: 

【程式四】:註釋:看看我們的東西被拷貝到哪兒去了  

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 

程式輸出: 

40000 

40000 

由於NOTE4處使用預設的ByVal,傳遞的是i的地址(也就是指向i的指標),所以常量40000拷貝到了變數i裡,因此i的值成了40000,而k的值卻沒有變化。但是,在NOTE4前有:i=VarPtr(k),本意是要把i本身做為一個指標來使用。這時,我們必須如NOTE5那樣用ByVal來傳遞指標i,由於i是指向變數k的指標,所以最後常量40000被拷貝了變數k裡。 

希望你已經理解了這種區別,在後面問題的討論中,我還會再談到它。 4)、AddressOf 

它用來得到一個指向VB函式入口地址的指標,不過這個指標只能傳遞給API使用,以使得API能回撥VB函式。 

本文不準備詳細討論函式指標,關於它的使用請參考VB文件。 

5)、拿來主義。 

實際上,有了CopyMemory,VarPtr,AddressOf這三把斧頭,我們已經可以將C裡基本的指標操作拿過來了。 

如下面的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;          //宣告結構指標 

void* pv;            //宣告無型別指標 

int (*pfnCastToInt)(void *, void*);//宣告函式指標:  

//指標賦值: 

pc = &c;              //將變數c的地址值賦給指標pc 

pfnCompare = Compare; //函式指標賦值。 

//指標取值: 

c = *pc;              //將指標pc所指處的記憶體值賦給變數c 

//用指標賦值: 

*pc = 註釋:Y註釋:             //將註釋:Y註釋:賦給指標pc所指記憶體變數裡。 

//指標移動: 

pc++; pl--;  

這些對指標操作在VB裡都有等同的東西, 

前面討論ByVal和ByRef時曾說過傳指標和傳地址是一回事,實際上當我們在VB裡用預設的ByRef宣告函式引數時,我們已經就聲明瞭指標。 

如一個C宣告的函式:long Func(char* pc) 

其對應的VB宣告是:Function Func(pc As Byte) As Long 

這時引數pc使用預設的ByRef傳地址方式來傳遞,這和C裡用指標來傳遞引數是一樣。 

那麼怎麼才能象C裡那樣明確地宣告一個指標呢? 

很簡單,如前所說,用一個32位長整數來表達指標就行。在VB裡就是用Long型來明確地宣告指標,我們不用區分是普通指標、無型別指標還是函式指標,通通都可用Long來宣告。而給一個指標賦值,就是賦給它用VarPar得到的另一個變數的地址。具體見程式五。 

【程式五】:同C一樣,各種指標。 

Type POINT 

X As Integer 

Y As Integer 

End Type 

Public Function Compare(elem1 As Long, elem2 As Long) As Long 

註釋: 

End Function 

Function FnPtrToLong(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 = VarPtr(Pt)   註釋:結構指標 

pv = VarPtr(ca(0)) 註釋:位元組陣列指標,可對應任何型別,也就是void* 

pfnCompare = FnPtrToLong(AddressOf Compare) 註釋:函式指標 

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,而呼叫API的代價是比較高的,這就決定了我們在VB裡使用指標不能象在C裡那樣自由和頻繁,我們必須要考慮指標操作的代價,在後面的"指標應用"我們會再變談這個問題。 

程式五中關於函式指標的問題請參考VB文件,無型別指標void*會在下面"關於Any的問題"裡說。 

程式五基本上已經包括了我們能在VB裡進行的所有指標操作,僅此而已。 
下面有一個小測試題,如果現在你就弄懂了上面程咬金的三板斧,你就應該能做得出來。 

上面提到過,VB.NET中沒有VarPtr,我們可以用宣告API的方式來引入MSVBVM60.DLL中的VarPtr。現在的問題如果不用VB的執行時DLL檔案,你能不能自己實現一個ObjPtr。答案在下一節後給出。 
4.指標使用中應注意的問題 

1)、關於ANY的問題 

如果以一個老師的身份來說話,我會說:最好永遠也不要用Any!是的,我沒說錯,是永遠!所以我沒有把它放在程咬金的三板斧裡。當然,這個問題和是不是應該使用指標這個問題一樣會引發一場沒有結果的討論,我告訴你的只是一個觀點,因為有時我們會為了效率上的一點點提高或想偷一點點懶而去用Any,但這樣做需要要承擔風險。 

Any不是一個真正的型別,它只是告訴VB編譯器放棄對引數型別的檢查,這樣,理論上,我們可以將任何型別傳遞給API。 

Any在什麼地方用呢?讓我們來看看,在VB文件裡的是怎麼說的,現在就請開啟MSDN(Visual Studio 6自帶的版本),翻到"Visual Basic文件"->"使用Visual Basic"->"部件工具指南"->"訪問DLL和Windows API"部分,再看看"將 C 語言宣告轉換為 Visual Basic 宣告"這一節。文件裡告訴我們,只有C的宣告為LPVOID和NULL時,我們才用Any。實際上如果你願意承擔風險,所有的型別你都可以用Any。當然,也可以如我所說,永遠不要用Any。     

為什麼要這樣?那為什麼VB官方還要提供Any?是信我的,還是信VB官方的?有什麼道理不用Any? 

如前面所說,VB官方不鼓勵我們使用指標。因為VB所標榜的優點之一,就是沒有危險的指標操作,所以的記憶體訪問都是受VB執行時庫控制的。在這一點上,JAVA語言也有著同樣的標榜。但是,同JAVA一樣,VB要避免使用指標而得到更高的安全性,就必須要克服沒有指標而帶來的問題。VB已經盡最大的努力來使我們遠離指標的同時擁有強型別檢查帶來的安全性。但是作業系統是C寫的,裡面到處都需要指標,有些指標是沒有型別的,就是C程式設計師常說的可怕的void*無型別指標。它沒有型別,因此它可以表示所有型別。如CopyMemory所對應的是C語言的memcpy,它的宣告如下: 
void *memcpy( void *dest, const void *src, size_t count ); 

因memcpy前兩個引數用的是void*,因此任何型別的引數都可以傳遞給他。 

一個用C的程式設計師,應該知道在C函式庫裡這樣的void*並不少見,也應該知道它有多危險。無論傳遞什麼型別的變數指標給上面memcpy的void*,C編譯器都不會報錯或給任何警告。 

在VB裡大多數時候,我們使用Any就是為了使用void*,和在C裡一樣,VB也不對Any進行型別檢查,我們也可以傳遞任何型別給Any,VB編譯器也都不會報錯或給任何警告。 

但程式執行時會不會出錯,就要看使用它時是不是小心了。正因為在C裡很多錯誤是和void*相關的,所以,C++鼓勵我們使用satic_cast<void*>來明確指出這種不安全的型別的轉換,已利於發現錯誤。 

說了這麼多C/C++,其實我是想告訴所有VB的程式設計師,在使用Any時,我們必須和C/C++程式設計師使用void*一樣要高度小心。 

VB裡沒有satic_cast這種東西,但我們可以在傳遞指標時明確的使用long型別,並且用VarPtr來取得引數的指標,這樣至少已經明確地指出我們在使用危險的指標。如程式二經過這樣的處理就成了下面的程式: 

【程式五】:註釋:使用更安全的CopyMemory,明確的使用指標! 

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal Destination As Long, ByVal Source As Long, ByVal Length As Long) 

Sub SwapStrPtr2(sA As String, sB As String) 

Dim lTmp As Long 

Dim pTmp As Long, psA As Long, psB As Long 

pTmp = VarPtr(lTmp): psA = VarPtr(sA): psB = VarPtr(sB) 

CopyMemory pTmp, psA, 4 

CopyMemory psA, psB, 4 

CopyMemory psB, pTmp, 4 

End Sub 

注意,上面CopyMemory的宣告,用的是ByVal和long,要求傳遞的是32位的地址值,當我們將一個別的型別傳遞給這個API時,編譯器會報錯,比如現在我們用下面的語句: 

【程式六】:註釋:有點象【程式四】,但將常量40000換成了值為1的變數. 

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (ByVal Destination As Long, ByVal Source As Long,  Length As Long) 

Sub TestCopyMemory() 

Dim i As Long,k As Long, z As Interger 

k = 5 : z = 1 

i = VarPtr(k) 

註釋:下面的語句會引起型別不符的編譯錯誤,這是好事! 

註釋:CopyMemory i, z, 4 

註釋:應該用下面的 

CopyMemory i, ByVal VarPtr(z), 2 

Debug.Print k 

End Sub 

    編譯會出錯!是好事!這總比執行時不知道錯在哪兒好! 

象程式四那樣使用Any型別來宣告CopyMemory的引數,VB雖然不會報錯,但執行時結果卻是錯的。不信,你試試將程式四中的40000改為1,結果i的值不是我們想要的1,而是327681。為什麼在程式四中,常量為1時結果會出錯,而常量為40000時結果就不錯? 

原因是VB對函式引數中的常量按Variant的方式處理。是1時,由於1小於Integer型的最大值32767,VB會生成一個儲存值1的Integer型的臨時變數,也就是說,當我們想將1用CopyMemroy拷貝到Long型的變數i時,這個常量1是實際上是Integer型臨時變數!VB裡Integer型別只有兩個位元組,而我們實際上拷貝了四個位元組。知道有多危險了吧!沒有出記憶體保護錯誤那只是我們的幸運! 

如果一定要解釋一下為什麼i最後變成了327681,這是因為我們將k的低16位的值5也拷貝到了i值的高16位中去了,因此有5*65536+1=327681。詳談這個問題涉及到VB區域性變數宣告順序,CopyMemory引數的壓棧順序,long型的低位在前高位在後等問題。如果你對這些問題感興趣,可以用本系列第一篇文章所提供的方法(DebugBreak這個API和VC偵錯程式)來跟蹤一下,可以加深你對VB內部處理方式的認識,由於這和本文討論的問題無關,所以就不詳談了。到這裡,大家應該明白,程式三和程式四實際上有錯誤!!!我在上面用常量40000而不用1,不是為了在文章中湊字數,而是因為40000這個常量大於32767,會被VB解釋成我們需要的Long型的臨時變數,只有這樣程式三和程式四才能正常工作。對不起,我這樣有意的隱藏錯誤只是想加深你對Any危害的認識。 

總之,我們要認識到,編譯時就找到錯誤是非常重要的,因為你馬上就知道錯誤的所在。所以我們應該象程式五和程式六那樣明確地用long型的ByVal的指標,而不要用Any的ByRef的指標。 

但用Any已經如此的流行,以至很多大師們也用它。它唯一的魅力就是不象用Long型指標那樣,需要我們自己呼叫VarPtr來得到指標,所有處理指標的工作由VB編譯器來完成。所以在引數的處理上,只用一條彙編指令:push ,而用VarPtr時,由於需要函式呼叫,因此要多用五條彙編指令。五條多餘的彙編指令有時的確能我們冒著風險去用Any。 

VB開發小組提供Any,就是想用ByRef xxx As Any來表達void* xxx。我們也完全可以使用VarPtr和Long型的指標來處理。我想,VB開發小組也曾猶豫過是公佈VarPtr,還是提供Any,最後他們決定還是提供Any,而繼續隱瞞VarPtr。的確,這是個兩難的決定。但是經過我上面的分析,我們應該知道,這個決定並不符合VB所追求的"更安全"的初衷。因為它可能會隱藏型別不符的錯誤,除錯和找到這種執行時才產生的錯誤將花貴更多的時間和精力。 

所以我有了"最好永遠不要用Any"這個"驚人"的結論。 

不用Any的另一個好處是,簡化了我們將C宣告的API轉換成VB宣告的方式,現在它變成了一句話:除了VB內建的可以進行型別檢查的型別外,所以其它的型別我們都應該宣告成Long型。 
2)、關於NULL的容易混淆的問題 

有很多文章講過,一定要記在心裡: 

VbNullChar 相當於C裡的註釋:\0註釋:,在用位元組陣列構造C字串時常用它來做最後1個元素。 

vbNullString 這才是真正的NULL,就是0,在VB6中直接用0也可以。 

只有上面的兩個是API呼叫中會用的。還有Empty、Null是Variant,而Nothing只和類物件有關,一般API呼叫中都不會用到它們。   

另:本文第三節曾提出一個小測驗題,做出來了嗎?現在公佈正確答案: 

【測驗題答案】 

Function ObjPtr(obj as Object) as long 

Dim lpObj As Long 

CopyMemory lpObj, Obj, 4 

ObjectPtr = lpObj 

End Function     

5.VB指標應用 

如前面所說VB裡使用指標不象C裡那樣靈活,用指標處理資料時都需要用CopyMemory將資料在指標和VB能夠處理的變數之間來回拷貝,這需要很大的額外開銷。因此不是所有C裡的指標操作都可以移值到VB裡來,我們只應在需要的時候才在VB裡使用指標。 

1)、動態記憶體分配:完全不可能、可能但不可行,VB標準     

在C和C++裡頻繁使用指標的一個重要原因是需要使用動態記憶體分配,用Malloc或New來從堆疊裡動態分配記憶體,並得到指向這個記憶體的指標。在VB裡我們也可以自己 

用API來實現動態分配記憶體,並且實現象C裡的指標連結串列。 

但我們不可能象C那樣直接用指標來訪問這樣動態分配的記憶體,訪問時我們必須用CopyMemory將資料拷貝到VB的變數內,大量的使用這種技術必然會降低效率,以至於要象C那樣用指標來使用動態記憶體根本就沒有可行性。要象C、PASCAL那樣實現動態資料結構,在VB裡還是應該老老實實用物件技術來實現。 

本文配套程式碼中的LinkedList裡有完全用指標實現的連結串列,它是使用HeapAlloc從堆疊中動態分配記憶體,另有一個呼叫FindFirstUrlCacheEntry這個API來操作IE的Cache的小程式IECache,它使用了VirtualAlloc來動態分配記憶體。但實際上這都不是必須的,VB已經為我們提供了標準的動態記憶體分配的方法,那就是: 

物件、字串和位元組陣列 

限於篇幅,關於物件的技術這裡不講,LinkedList的原始碼裡有用物件實現的連結串列,你可以參考。 

字串可以用Space$函式來動態分配,VB的文件裡就有詳細的說明。 

關於位元組陣列,這裡要講講,它非常有用。我們可用Redim來動態改變它的大小,並將指向它第一個元素的指標傳給需要指標的API,如下: 
dim  ab() As Byte , ret As long 

註釋:傳遞Null值API會返回它所需要的緩衝區的長度。 

ret = SomeApiNeedsBuffer(vbNullString) 

註釋:動態分配足夠大小的記憶體緩衝區 

ReDim ab(ret) As Byte 

註釋:再次把指標傳給API,此時傳位元組陣列第一個元素的指標。 

SomeApiNeedsBuffer(ByVal VarPtr(ab(1)))  

在本文配套程式中的IECache中,我也提供了用位元組陣列來實現動態分配緩衝區的版本,比用VirtualAlloc來實現更安全更簡單。 

2)、突破限制 

下面是一個突破VB型別檢查來實現特殊功能的經典應用,出自Bruce Mckinney的《HardCore Visual Basic》一書。 

將一個Long長整數的低16位作為Interger型提取出來, 

【程式七】 註釋:標準的方法,也是高效的方法,但不容易理解。

Function LoWord(ByVal dw As Long) As Integer     

If dw And &H8000& Then         

LoWord = dw Or &HFFFF0000     

Else         

LoWord = dw And &HFFFF&     

End If 

End Function

【程式八】    註釋:用指標來做效率雖不高,但思想清楚。 

Function LoWord(ByVal dw As Long) As Integer     

CopyMemory ByVal VarPtr(LoWord), ByVal VarPtr(dw), 2 

End Function 

3)、對陣列進行批量操作 
用指標進行大批量陣列資料的移動,從效率上考慮是很有必要的,看下面的兩個程式,它們功能都是將陣列的前一半資料移到後一半中: 

【程式九】:註釋:標準的移動陣列的做法 

Private Sub ShitArray(ab() As MyType) 

Dim i As Long, n As Long 

n = CLng(UBound(ab) / 2) 

For i = 1 To n 

Value(n + i) = Value(i) 

Value(i).data = 0 

Next 

End Sub 

【程式十】:註釋:用指標的做法 

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" _ 

(ByVal dest As Long, ByVal source As Long, ByVal bytes As Long) 

Private Declare Sub ZeroMemory Lib "kernel32" Alias "RtlZeroMemory" _ 

(ByVal dest As Long, ByVal numbytes As Long) 

Private Declare Sub FillMemory Lib "kernel32" Alias "RtlFillMemory" _ 

(ByVal dest As Long, ByVal Length As Long, ByVal Fill As Byte) 

Private Sub ShitArrayByPtr(ab() As MyTpye) 

Dim n As Long 

n = CLng(UBound(ab) / 2) 

Dim nLenth As Long 

nLenth = Len(Value(1)) 

註釋:DebugBreak 

CopyMemory ByVal VarPtr(Value(1 + n)), _ 

ByVal VarPtr(Value(1)), n * nLenth 

ZeroMemory ByVal VarPtr(Value(1)), n * nLenth 

End Sub 

    當陣列較大,移動操作較多(比如用陣列實現HashTable)時程式十比程式九效能上要好得多。 

程式十中又介紹兩個在指標操作中會用到的API: ZeroMemory是用來將記憶體清零;FillMemory用同一個位元組來填充記憶體。當然,這兩個API的功能,也完全可以用CopyMemory來完成。象在C裡一樣,作為一個好習慣,在VB裡我們也可以明確的用ZeroMemory來對陣列進行初始化,用FillMemory在不立即使用的記憶體中填入怪值,這有利於除錯。 

4)、最後的一點 

當然,VB指標的應用決不止這些,還有什麼應用就要靠自己去摸索了。對於物件指標和字串指標的應用我會另寫文章來談,做為本文的結束和下一篇文章《VB字串全攻略》的開始,我在這裡給出交換兩個字串的最快的方法: 
【程式十一】註釋:交換兩個字串最快的方法

Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As Long)  

Sub SwapStrPtr3(sA As String, sB As String) 

Dim lTmp As Long 

Dim pTmp As Long, psA As Long, psB As Long 

pTmp = StrPtr(sA): psA = VarPtr(sA): psB = VarPtr(sB) 

CopyMemory ByVal psA, ByVal psB, 4 

CopyMemory ByVal psB, pTmp, 4 

End Sub

二、總結

a.什麼是Access VBA?

Access VBA是指VBA開發人員在ACCESS[1]程式設計中應用VBA,通過設計VBA程式碼bai來實現所需要的程式功能目標;VBA是應用程式開發語言VASUAL BASIC 的子集。

b、Access VBA與 VB的區別是什麼?

1. VB是設計用於建立標準的應用程式,而VBA是使已有的應用程式(Access、Excel、Word等)自動化;

2. VB具有自己的開發環境,而VBA必須寄生於已有的應用程式;

3. 要執行VB開發的應用程式,使用者不必安裝VB,因為VB開發出的應用程式是可執行檔案(*.EXE),而VBA開發的程式必須依賴於它的"父"應用程式。

c、Access開發軟體必須學好VBA

掌握了VBA,可以規範使用者的操作,控制使用者的操作行為;

掌握了VBA,可以讓操作介面人性化,方便使用者的操作;

掌握了VBA,可以將多個步驟的手工操作通過一步來實現;

掌握了VBA,可以完成一些無法實現的功能。

使用ACCESS如果不會用VBA,那麼就侷限於表、查詢、繫結窗體和簡單報表的簡單應用,更多地體現在自身的使用,在與同事間的資料收集、整理、分析、資訊共享方面基本是處於一個初級階段。

為了能讓自已開發軟體更加得心應手、給軟體使用者更方便與人性化的操作,我們就需要學好VBA知識。

d、怎樣去學習VBA?

1.去書店購買相關的Access書籍,在購買時注意書籍的章節,要有關VBA程式碼的頁數多一點的;

2.利用免費的網路資源,例如百度搜索,通過搜尋相關的關鍵詞,檢索出相關的文章;

3.訪問專業的技術網站、論壇,例如Access軟體網,該網站由上海盟威軟體有限公司[1]提供,有大量的VBA原始碼示例可供學習。

4.結合工作實踐去學習,利用VBA解決工作上的問題,達到提高工作效率目標,從而越學越有興趣。

改變自己,從現在做起-----------久館