三、 程咬金的三板斧  
  
  是的,程式二基本上就已經讓我們看到VB指標技術的模樣了。總結一下,在VB堨峆標技術我們需要掌握三樣東西:CopyMemory, 
 
  
 
VarPtr/StrPtr/ObjPtr, AdressOf. 三把斧頭,程咬金的三板斧,在VBHack的工具。 
  
  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(通用保護錯誤) 。 
 
  
所以,在進行本系列文章堛犒篘蝞氶A請注意隨時保存你的程式檔,在VB集成環境中將"工具"->"選項"中的"環境"選項卡堛"啟動程式時"設為"保存改變", 
 
  
並記住在"立即"視窗中執行危險代碼之前一定要保存我們的工作成果。 
 
  
  
  2、VatPtr/StrPtr/ObjPtr 
  
  它們是VB提供給我們的好寶貝,它們是VBA函數庫中的隱藏函數。為什麼要隱藏?因為VB開發小組,不鼓勵我們用指標嘛。 
  
  實際上這三個函數在VB運行時庫MSVBVM60.DLL(或MSVBVM50.DLL)中是同一個函數VarPtr(可參見我在本系列第一篇文章堣雯衁漱隤k)。 
  
  其庫型庫定義如下: 
  
[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.NET堥S有這幾個函數,但只要還能調用API,我們就可以試試上面的幾個聲明,這樣在VB.NET塈畯怳@樣可以進行指標操作。 
 
  
但是請注意,如果通過API調用來使用VarPtr,整個程式二SwapPtr將比原來使用內置VarPtr函數時慢6倍。) 
  
  如果你喜歡刨根問底,那麼下面就是VarPtr函數在C和組合語言堛獐豸l: 
  
  在C媦豸l是這樣的: 
  
long VarPtr(void* pv){ 
 return (long)pv; 
}  
  
  所對就的彙編代碼就兩行: 
  
mov eax,dword ptr [esp+4] 
ret 4 '彈出棧堸捊う滬並返回。  
  
  之所以讓大家瞭解VarPtr的具體實現,是想告訴大家它的開銷並不大,因為它們不過兩條指令,即使加上參數賦值、壓棧和調用指令, 
 
  
整個獲取指標的過程也就六條指令。當然,同樣的功能在C語言堙A由於語言的直接支援,僅需要一條指令即可。但在VB堙A 
 
  
它已經算是最快的函數了,所以我們完全不用擔心使用VarPtr會讓我們失去效率!速度是使用指標技術的根本要求。 
  
  一句話,VarPtr返回的是變數所在處的記憶體位址,也可以說返回了指向變數記憶體位置的指標,它是我們在VB堻B理指標最重要的武器之一。 
  
  3、ByVal和ByRef 
  
  ByVal傳遞的參數值,而ByRef傳遞的參數的地址。在這堙A我們不用去區別傳指標/傳位址/傳引用的不同, 
 
  
在VB堙A它們根本就是一個東西的三種不同說法,即使VB的文檔堣]有地方在混用這些術語(但在C++堛瑤T要區分指針和引用) 
  
  初次接觸上面的程式二SwapPtr的朋友,一定要搞清在堶悸CopyMemory調用中,在什麼地方要加ByVal, 
 
  
什麼地方不加(不加ByVal就是使用VB缺省的ByRef),準確的理解傳值和傳地址(指標)的區別,是在VB堨蕭T使用指標的基礎。 
  
  現在一個最簡單的實驗來看這個問題,如下面的程式三: 
  
  【程式三】: 
  
'體會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的值並沒有變。 
  |