麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 學院 > 開發設計 > 正文

VisualBasic變態用法之函數指針

2019-11-18 17:54:50
字體:
來源:轉載
供稿:網友
一、函數指針

  AddressOf得到一個VB內部的函數指針,我們可以將這個函數指針傳遞給需要回調這個函數的API,它的作用就是讓外部的程序可以調用VB內部的函數。

  但是VB里函數指針的應用,遠不象C里應用那么廣泛,因為VB文檔里僅介紹了如何將函數指針傳遞給API以實現回調,并沒指出函數指針諸多神奇的功能,因為VB是不鼓勵使用指針的,函數指針也不例外。

  首先讓我們對函數指針的使用方式來分個類。

  1、回調。這是最基本也是最重要的功能。比如VB文檔里介紹過的子類派生技術,它的核心就是兩個API:SetWindowLong和CallWindow
  函數指針的使用不外乎上面四種方式。但在實際使用中卻是靈活多變的。比如在C 里繼承和多態,在COM里的接口,都是一種叫vTable的函數指針表的巧妙應用。使用函數指針,可以使程序的處理方式更加高效、靈活。

  VB文檔里除了介紹過第一方式外,對其它方式都沒有介紹,并且還明確指出不支持“Basic到Basic”的函數指針(也就是上面說的第二種方式),實際上,通過一定的HACK,上面四種方式均可以實現。今天,我們就來看看如何來實現第二種方式,因為實現它相對來說比較簡單,我們先從簡單的入手。至于如何在VB內調用外部的函數指針,如何在VB里通過處理vTable接口函數指針跳轉表來實現各種函數指針的巧妙應用,由于這將涉及COM內部原理,我將另文詳述。

  其實VB的文檔并沒有說錯,VB的確不支持“Basic到Basic”的函數指針,但是我們可以繞個彎子來實現,那就是先從"Basic到API",然后再用第一種方式"外部調用內部的函數指針"來從"API到BASIC",這樣就達到了第二種方式從"Basic到Basic"的目的,這種技術我們可以稱之為"強制回調",只有VB里才會有這種古怪的技術。

  說得有點繞口,但是仔細想想窗口子類派生技術里CallWindowProc,我們可以用CallWindowProc來強制外部的
操作系統調用我們原來的保存的窗口函數指針,同樣我們也完全可以用它來強制調用我們內部的函數指針。

  呵呵,前面說過要少講原理多講招式,現在我們就來開始學習招式吧!

  考慮我們在VB里來實現和C里一樣支持多關鍵字比較的qsort。完整的源代碼見本文配套代碼,此處僅給出函數指針應用相關的代碼。

->'當然少不了的CopyMemory,不用ANY的版本。
DeclareSubCopyMemoryLib"kernel32"Alias_
"RtlMoveMemory"(ByValdestAsLong,ByValsourceAsLong,_
ByValnumBytesAsLong)

'嘿嘿,看下面是如何將CallWindowProc的聲明做成Compare聲明的。
DeclareFunctionCompareLib"user32"Alias_
"CallWindowProcA"(ByValpfnCompareAsLong,ByValpElem1AsLong,_
ByValpElem2AsLong,ByValunused1AsLong,_
ByValunused2AsLong)AsInteger
'注:ByValxxxxxAsLong,還記得吧!這是標準的指針聲明方法。

'聲明需要比較的數組元素的結構
PublicTypeTEmployee
 NameAsString
 SalaryAsCurrency
EndType

'再來看看我們的比較函數
'先按薪水比較,再按姓名比較
FunctionCompareSalaryName(Elem1AsTEmployee,_
     Elem2AsTEmployee,_
     unused1AsLong,_
     unused2AsLong)AsInteger
 DimRetAsInteger
 Ret=Sgn(Elem1.Salary-Elem2.Salary)
 IfRet=0Then
  Ret=StrComp(Elem1.Name,Elem2.Name,vbTextCompare)
 EndIf
 CompareSalaryName=Ret
EndFunction

'先按姓名比較,再按薪水比較
FunctionCompareNameSalary(Elem1AsTEmployee,_
     Elem2AsTEmployee,_
     unused1AsLong,_
     unused2AsLong)AsInteger
 DimRetAsInteger
 Ret=StrComp(Elem1.Name,Elem2.Name,vbTextCompare)
 IfRet=0Then
  Ret=Sgn(Elem1.Salary-Elem2.Salary)
 EndIf
 CompareNameSalary=Ret
EndFunction->

  最后再看看我們來看看我們最終的qsort的聲明。

->Subqsort(ByValArrayPtrAsLong,ByValnCountAsLong,_
ByValnElemSizeAsInteger,ByValpfnCompareAsLong)->

  上面的ArrayPtr是需要排序數組的第一個元素的指針,nCount是數組的元素個數,nElemSize是每個元素大小,pfnCompare就是我們的比較函數指針。這個聲明和C庫函數里的qsort是極為相似的。

  和C一樣,我們完全可以將Basic的函數指針傳遞給Basic的qsort函數。

  使用方式如下:

->DimEmployees(1To10000)AsTEmployee
'假設下面的調用對Employees數組進行了賦值初始化。
CallInitArray()
'現在就可以調用我們的qsort來進行排序了。
Callqsort(VarPtr(Employees(1)),UBound(Employees),_
LenB(Employees(1)),AddressOfCompareSalaryName)
'或者先按姓名排,再按薪水排
Callqsort(VarPtr(Employees(1)),UBound(Employees),_
LenB(Employees(1)),AddressOfCompareNameSalary)->

  聰明的朋友們,你們是不是已經看出這里的奧妙了呢?作為一個測驗,你能現在就給出在qsort里使用函數指針的方法嗎?比如現在我們要通過調用函數指針來比較數組的第i個元素和第j個元素的大小。

  沒錯,當然要使用前面聲明的Compare(其實就是CallWindowProc)這個API來進行強制回調。

  具體的實現如下:

->Subqsort(ByValArrayPtrAsLong,ByValnCountAsLong,_
ByValnElemSizeAsInteger,ByValpfnCompareAsLong)
 DimiAsLong,jAsLong
 '這里省略快速排序算法的具體實現,僅給出比較兩個元素的方法。
 IfCompare(pfnCompare,ArrayPtr (i-1)*nElemSize,_
   ArrayPtr (j-1)*nElemSize,0,0)>0Then
   '如果第i個元素比第j個元素大則用CopyMemory來交換這兩個元素。
 EndIF
EndSub->

  招式介紹完了,明白了嗎?我再來簡單地講解一下上面Compare的意思,它非常巧妙地利用了CallWindowProc這個API。這個API需要五個參數,第一個參數就是一個普通的函數指針,這個API能夠強馬上回調這個函數指針,并將這個API的后四個Long型的參數傳遞給這個函數指針所指向的函數。這就是為什么我們的比較函數必須要有四個參數的原因,因為CallWindowProc這個API要求傳遞給的函數指針必須符合WndProc函數原形,WndProc的原形如下:

->LRESULT(CALLBACK*WNDPROC)(HWND,UINT,WPARAM,LPARAM);->

  上面的LRESULT、HWND、UINT、WPARAM、LPARAM都可以對應于VB里的Long型,這真是太好了,因為Long型可以用來作指針嘛!

  再來看看工作流程,當我們用AddressOfCompareSalaryName做為函數指針參數來調用qsort時,qsort的形參pfnCompare被賦值成了實參CompareSalaryName的函數指針。這時,調用Compare來強制回調pfnCompare,就相當于調用了如下的VB語句:

->CallCompareSalaryName(ArrayPtr (i-1)*nElemSize,_
ArrayPtr (j-1)*nElemSize,0,0)->

  這不會引起參數類型不符錯誤嗎?CompareSalaryName的前兩個參數不是TEmployee類型嗎?的確,在VB里這樣調用是不行的,因為VB的類型檢查不會允許這樣的調用。但是,實際上這個調用是API進行的回調,而VB不可能去檢查API回調的函數的參數類型是一個普通的Long數值類型還是一個結構指針,所以也可以說我們繞過了VB對函數參數的類型檢查,我們可以將這個Long型參數聲明成任何類型的指針,我們聲明成什么,VB就認為是什么。所以,我們要小心地使用這種技術,如上面最終會傳遞給CompareSalaryName函數的參數"ArrayPtr (i-1)*nElemSize"只不過是一個地址,VB不會對這個地址進行檢查,它總是將這個地址當做一個TEmployee類型的指針,如果不小心用成了"ArrayPtr i*nElemSize",那么當i是最后一個元素時,我們就會引起內存越權訪問錯誤,所以我們要和在C里處理指針一樣注意邊界問題。

  函數指針的巧妙應用這里已經可見一斑了,但是這里介紹的方法還有很大的局限性,我們的函數必須要有四個參數,更干凈的做法還是在VC或Delphi里寫一個DLL,做出更加符合要求的API來實現和CallWindowProc相似的功能。我跟蹤過CallWindowProc的內部實現,它要做許多和窗口消息相關的工作,這些工作在我們這個應用中是多余的。其實實現強制回調API只需要將后幾個參數壓棧,再call第一個參數就行了,不過幾條匯編指令而已。

  正是因為CallWindowProc的局限性,我們不能夠用它來調用外部的函數指針,以實現上面說的第三種函數指針調用方式。要實現第三種方式,MattCurland大師提供了一個噩夢一般的HACK方式,我們要在VB里憑空構造一個IUnknown接口,在IUnknown接口的vTable原有的三個入口后再加入一個新入口,在新入口里插入機器代碼,這個機器代碼要處理掉this指針,最后才能調用到我們給的函數指針,這個函數指針無論是內部的還是外部的都一樣沒問題。在我們深入討論COM內部原理時我會再來談這個方法。

  另外,排序算法是個見仁見智的問題,我本來想,在本文提供一個最通用性能最好的算法,這種想法雖好,但是不可能有在任何情況下都“最好”的算法。本文提供的用各種指針技術來實現的快速排序方法,應該比用對象技術來實現同樣功能快不少,內存占用也少得多。可是就是這個已經經過了我不少優化的快速排序算法,還是比不了ShellSort,因為ShellSort實現上簡單。從算法的理論上來講qsort應該比ShellSort平均性能好,但是在VB里這不一定(可見本文配套代碼,里面也提供了VBPJ一篇專欄的配套代碼ShellSort,非常得棒,本文的思想就取自這個ShellSort)。

  但是應當指出無論是這里的快速排序還是ShellSort,都還可以大大改進,因為它們在實現上需要大量使用CopyMemroy來拷貝數據(這是VB里使用指針的缺點之一)。其實,我們還有更好的方法,那就是Hack一下VB的數組結構,也就是COM自動化里的SafeArray,我們可以一次性的將SafeArray里的各個數組元素的指針放到一個long型數組里,我們無需CopyMemroy,我們僅需交換Long型數組里的元素就可以達到實時地交換SafeArray數組元素指針的目的,數據并沒有移動,移動的僅僅是指針,可以想象這有快多。
->


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 国产精品高潮99久久久久久久 | 中文字幕在线观看视频一区 | 极品美女一级毛片 | 国产午夜精品一区二区三区不卡 | 在线日韩av电影 | 亚洲一区二区网址 | 黄视频免费在线观看 | 久久av免费观看 | 国产一级毛片国语版 | 羞羞电影在线观看www | 亚州精品国产 | 久久久婷婷一区二区三区不卡 | 精品国产一区二区久久 | 色综合久久久久久久久久久 | 嗯~啊~用力~高h | 在线男人天堂 | 欧美日韩成人一区二区 | 午夜色视频在线观看 | 欧美亚洲国产一区二区三区 | 小情侣嗯啊哦视频www | 久久久一区二区 | 免费毛片小视频 | 亚洲国产精品一 | 久久国产一二三 | 久久免费视频8 | 日韩蜜桃视频 | 草草视频免费观看 | 一本大道av | 国产精品成人一区二区三区吃奶 | av免费在线免费观看 | 中文字幕在线免费 | 国产精品一区二区免费在线观看 | 久久精品性视频 | 懂色av懂色aⅴ精彩av | 欧美一级成人 | 国产视频精品在线 | 免费网站看毛片 | 中国久久久 | 国产精品6区 | 国产日产精品一区四区介绍 | gogo全球大胆高清人露出91 |