三、 程咬金的三板斧
是的,程式二基本上就已經讓我們看到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的值並沒有變。
|