黃易群俠傳M脫機外掛應用程式黃易神行
2155
4

[分享] VB指針(四)

wen0116 發表於 2009-3-25 14:15:58 | 只看該作者 回帖獎勵 |倒序瀏覽 |
四、指標使用中應注意的問題

  1、關於ANY的問題

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

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

  Any在什麼地方用呢?讓我們來看看,在VB文檔堛漪O怎麼說的,現在就請打開MSDN(Visual Studio 6自帶的版本),翻到"Visual Basic文檔"->"使用Visual Basic"->"部件工具指南"->"訪問DLLWindows API"部分,再看看" C 語言聲明轉換為 Visual Basic 聲明"這一節。文檔塈i訴我們,只有C的聲明為LPVOIDNULL時,我們才用Any。實際上如果你願意承擔風險,所有的類型你都可以用Any。當然,也可以如我所說,永遠不要用Any

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

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

void *memcpy( void *dest, const void *src, size_t count );

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

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

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

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

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

  VB堥S有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的聲明,用的是ByVallong,要求傳遞的是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 Longk 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型的最大值32767VB會生成一個存儲值1Integer型的臨時變數,也就是說,當我們想將1CopyMemroy拷貝到Long型的變數i時,這個常量1是實際上是Integer型臨時變數!VBInteger類型只有兩個位元組,而我們實際上拷貝了四個位元組。知道有多危險了吧!沒有出記憶體保護錯誤那只是我們的幸運!

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

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

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

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

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

  不用Any的另一個好處是,簡化了我們將C聲明的API轉換成VB聲明的方式,現在它變成了一句話:除了VB內置的可以進行類型檢查的類型外,所以其他的類型我們都應該聲明成Long型。

  2、關於NULL的容易混淆的問題

  有很多文章講過,一定要記在心堙G

  VbNullChar 相當於C'',在用位元組陣列構造C字串時常用它來做最後1個元素。

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

  只有上面的兩個是API調用中會用的。還有EmptyNullVariant,而Nothing只和類物件有關,一般API調用中都不會用到它們。

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

  【測驗題答案】

Function ObjPtr(obj as Object) as long
 Dim lpObj As Long
 CopyMemory lpObj, Obj, 4
 ObjectPtr = lpObj
End Function

 五、VB指針應用

  如前面所說VB堥洏峆針不象C堥獐侔F活,用指標處理資料時都需要用CopyMemory將資料在指標和VB能夠處理的變數之間來回拷貝,這需要很大的額外開銷。因此不是所有C堛澈標操作都可以移值到VB堥荂A我們只應在需要的時候才在VB堥洏峆標。

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

  在CC++媕W繁使用指標的一個重要原因是需要使用動態記憶體分配,用MallocNew來從堆疊堸妧A分配記憶體,並得到指向這個記憶體的指標。在VB塈畯怳]可以自己

  用API來實現動態分配記憶體,並且實現象C堛澈針鏈表。

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

  本文配套代碼中的LinkedList埵釦馴用指標實現的鏈表,它是使用HeapAlloc從堆疊中動態分配記憶體,另有一個調用FindFirstUrlCacheEntry這個API來操作IECache的小程式IECache,它使用了VirtualAlloc來動態分配記憶體。但實際上這都不是必須的,VB已經為我們提供了標準的動態記憶體分配的方法,那就是:

  物件、字串和位元組陣列

  限於篇幅,關於物件的技術這堣講,LinkedList的源代碼埵野峈咱騛窶{的鏈表,你可以參考。

  字串可以用Space$函數來動態分配,VB的文檔奡N有詳細的說明。

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

dim ab() As Byte , ret As long
'傳遞NullAPI會返回它所需要的緩衝區的長度。
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字串全攻略》的開始,我在這媯馴X交換兩個字串的最快的方法:

  【程式十一】

'交換兩個字串最快的方法
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
收藏收藏 分享分享 讚 幹 分享分享 FB分享
回覆

使用道具 舉報


Goldenwolf 當前離線
UID
1166302
熱心
75 值
嘉獎
0 次
違規
0 次
在線時間
17 小時
經驗
75 點
積分
426
精華
0
最後登錄
2009-10-8
閱讀權限
25
註冊時間
2009-3-28
論壇幣
259 幣
聯合幣
0 枚
幸運鑽
0 顆
招待卷
0 點
查看詳細資料
Rank: 3
Goldenwolf 2009-4-1 10:50:53
指標好複雜喔 有分安全和危險 感謝大大分享!!
回覆

使用道具 舉報

may05005 當前離線
UID
1208553
熱心
42 值
嘉獎
0 次
違規
0 次
在線時間
15 小時
經驗
32 點
積分
146
精華
0
最後登錄
2018-8-6
閱讀權限
20
註冊時間
2009-5-7
論壇幣
50 幣
聯合幣
6 枚
幸運鑽
0 顆
招待卷
0 點
查看詳細資料
Rank: 2Rank: 2
may05005 2009-5-12 16:28:51
研讀完,但好難懂哦,
感謝大大的分享!!
回覆

使用道具 舉報

jaydiy 當前離線
UID
1140322
熱心
50 值
嘉獎
0 次
違規
0 次
在線時間
17 小時
經驗
54 點
積分
54
精華
0
最後登錄
2009-12-16
閱讀權限
20
註冊時間
2009-2-22
論壇幣
533 幣
聯合幣
0 枚
幸運鑽
0 顆
招待卷
0 點
查看詳細資料
Rank: 2Rank: 2
jaydiy 2009-8-2 03:17:35
最近剛在研究VB...
感謝大大的分享~
幫助很大~謝謝您...YCT65B
回覆

使用道具 舉報

隨火 當前離線
UID
1294953
熱心
214 值
嘉獎
0 次
違規
0 次
在線時間
6 小時
經驗
14 點
積分
14
精華
0
最後登錄
2011-4-5
閱讀權限
10
註冊時間
2009-8-20
論壇幣
78 幣
聯合幣
0 枚
幸運鑽
0 顆
招待卷
0 點
查看詳細資料
Rank: 1
5
隨火 2009-9-1 10:29:27
哦原來是這樣阿謝謝你無私的分享熱情的教學
回覆

使用道具 舉報

您需要登錄後才可以回帖 登錄 | 註冊


手機版 | Archiver | 外掛聯合國

GMT+8, 2024-9-21 01:18 , Processed in 0.064195 second(s), 17 queries , Memcache On.

版權說明:
  本站不會製作、經銷、代理外掛程式。僅免費提供外掛程式下載前之掃毒及掃木馬等安全檢測驗證,協助會員遠離盜號危險程式。本站所有資料均來自網際網路收集整理,說明文字暨下載連結轉載自原程 式開發站。站上出現之公司名稱、遊戲名稱、程式等,商標及著作權,均歸各公司及程式原創所有,本站程式所有權歸外掛聯合國所有。本程式所有權歸外掛聯合國所有.......

回頂部
第二步?
第三步?