乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      Matthew Curland的VB函數(shù)指針調用

       玉雪龍山999 2012-02-14

      Matthew Curland的VB函數(shù)指針調用

      發(fā)布時間:2008-04-03 10:37:54  來源:編程愛好者網(wǎng)站  作者:編程愛好者網(wǎng)站  點擊:258

      Matthew Curland簡介:
          Visual Studio開發(fā)小組成員,參與開發(fā)了VB的IntelliSense和Object Browser。他是VB資深專家,對VB有非常深入的研究,堪稱VB大師。所著《Advanced Visual Basice》是闡述VB高級編程技巧的一本好書。
          本文英文原著可見2000年2月份《Visual Basic Programmer’s Journal》(VB程序員月刊)里的《Call Function Pointers》,這是他發(fā)表的妙文之一,他的書里的第11章和本文同名,本文應該是這一章節(jié)的精華。

          之所以推薦此文,是因為它綜合運用了VB里的不少技術。我們可從中看到Matt大師對VB的深刻理解,而各位技術的綜合運用正體現(xiàn)了他深厚的功力。

      本文原文:http://www./premier/mgznarch/vbpj/2000/02feb00/mc0200/mc0200.asp
      (要先注冊成premier用戶)
      本文配套代碼:
      http://www./free/mgznarch/vbpj/code/2000/02feb00/vb0002mc_p.zip


      關鍵字:函數(shù)指針,COM、對象、接口,vTalbe,VB匯編,動態(tài)DLL調用。
      級別:高級
      要求:了解VB對象編程,了解匯編。

                                調用函數(shù)指針
          通過使用函數(shù)指針,我們能夠動態(tài)地在代碼中插入不同行為的函數(shù),從而使代碼擁有動態(tài)改變自身行為的能力。

      作者:Matther Curland
      要求:使用本文的示例代碼,你需要VB5或VB6的專業(yè)版或企業(yè)版。
          從Visual Basic 5.0開始Basic語言引入了一個重要的特性:AddressOf運算符。這個運算符能夠讓VB程序員直接體會到將自己的函數(shù)指針送出去的快感。比如我們在VB里就能夠得到系統(tǒng)字體的列表,我們能夠通過標準的API調用來進行子類化。一句話,我們終于可以象文檔里所說的那樣來使用Win32 API了。
          不過,這個新玩具只能給我們帶來短暫的快感,因為這個禮物并不完整。我們可以送出函數(shù)指針,但卻沒人能將函數(shù)指針送給我們。事實上,我們甚至不能給我們自己送函數(shù)指針,這使我們不能夠體驗送禮的真正樂趣(譯者:呵呵,光送禮卻不能收禮的確沒趣)。AddressOf讓我們看到了廣袤天地的一角,但是VB卻不讓我們?nèi)娴靥剿魉?,因為VB根本就不讓我們調用函數(shù)指針,我們只能提供函數(shù)指針(譯者:可以先將函數(shù)指針送給API,然后讓API回調自已的函數(shù)指針來完成函數(shù)指針調用的功能,但這還是要先把禮物送給別人)。其實,我們能夠自己來實現(xiàn)調用函數(shù)指針的功能,我們可以手工將一個對COM接口的vTable綁定調用變成一個函數(shù)指針調用。最妙的是:我們能夠在純VB里寫出調用函數(shù)指針的代碼,不需要任何輔助的DLL。

          告訴編譯器函數(shù)指針是什么樣子,是使VB能夠調用任何函數(shù)的關鍵。將參數(shù)類型和返回值類型交給VB編譯器,讓編譯器將我們的函數(shù)調用編譯到我們的程序里,這樣程序才能在運行時知道怎樣去定位函數(shù)。在程序被編譯后,一個函數(shù)就是內(nèi)存里一串匯編字節(jié)流,通過CPU解釋執(zhí)行而形成我們的程序。調用一個函數(shù)指針,首先需要程序獲得指向這個函數(shù)字節(jié)流的指針,再通過x86匯編指令call將當前指令指針(譯注:即x86匯編里的IP寄存器)轉到函數(shù)所在的字節(jié)流上。在函數(shù)完成后,再用ret指令返回給調用此函數(shù)的程序來繼續(xù)操作。

          我下面將要提到的方法,利用了VB自己的函數(shù)調用方式,所以我先來解釋一下VB是怎樣來實現(xiàn)函數(shù)調用的。VB內(nèi)部使用三種函數(shù)指針,但是,在本質上,不論VB是如何來定位這幾類函數(shù)指針,調用它們的方法卻是一樣的。VB編譯器必須知道準確的函數(shù)原型才能生成調用函數(shù)的代碼。

          第一類,最常見的函數(shù)指針類型,就是VB用來調用函數(shù)的普通指針,這樣的函數(shù)定義在標準模塊內(nèi)(或類模塊里的友元函數(shù)和私有函數(shù))。調用友元函數(shù)和私有函數(shù)時,調用指令定位在當前指令指針的一個偏移地址處,或者先跳到一個記錄著函數(shù)位置的查找表里,再跳到函數(shù)內(nèi)(譯者:即先"Call 絕對地址"跳到一個跳轉表內(nèi),表里的每個入口都是一個"Jmp"到函數(shù))。這些函數(shù)都在同一個工程內(nèi),聯(lián)結器總是將所有的模塊聯(lián)結在一起,所以總是知道在內(nèi)存何處能夠找到VB內(nèi)部函數(shù),因此轉移控制到內(nèi)部函數(shù)時,其運行時開銷是很少的。

      VB對某些函數(shù)指針的調用卻困難得多
          對于另兩類函數(shù)指針,VB必須在運行時進行額外的工作才能夠找出它們。
          第二類,VB調用一個COM對象接口里的方法。我們可能認為建立COM對象的工作是相當復雜的,如果完全用VB來為我們建造COM的所有組成部分的話,但事實上并不是這樣。按照COM的二進制標準,一個COM對象是一個指針,這個指針指向一個結構,這個特定結構的第一個元素是一個指向函數(shù)指針數(shù)組的指針。這個函數(shù)指針數(shù)組(又叫虛擬函數(shù)表,簡稱vTable)里的前三個指針,一定是標準QueryInterface,AddRef,Release函數(shù)。vTable里接下來的函數(shù)符合給定的COM對象接口定義里的函數(shù)定義(見圖一)




      圖一:
      函數(shù)指針代理是怎么工作的?click here

          當VB通過一個對象類型的變量來調用一個COM對象的方法或屬性時,這個變量里存放著對這個COM對象接口的引用。VB要定位函數(shù)時,首先要通過COM引用的第一個元素來獲得指向vTalbe的指針,然后才能在vTable里定位函數(shù)指針。對一個vTable調用來說,編譯器提供了COM引用和函數(shù)指針在vTable里的偏移量。這樣函數(shù)指針才能在運行時被動態(tài)地選出來。這種雙向間接的方式??兩種指針都必須被計算(譯注:指向vTalbe的指針和vTable里的函數(shù)指針都必須在運行時才確定)??使得vTable調用比同一個工程內(nèi)的直接調用慢得多,因為直接調用不需要任何在運行時才能進行的指針間接指定。

          VB對待同一個工程里的類的公有方法和對待外部COM對象里方法完全一樣,都需要查找vTable,這就是為什么在同一個對象內(nèi)調用一個友元函數(shù)會比調用一個公有函數(shù)快得多的原因。但是,查找vTable是COM的基礎,它使得VB能夠使用從外部庫里載入的COM對象,也是象Implements這樣的編程概念的實現(xiàn)基礎。動態(tài)載入不可能通過靜態(tài)聯(lián)結來實現(xiàn),查找vTable的花費是使用動態(tài)載入必須付出的代價。

          通過Object型變量來進行的后期綁定調用不同于vTable綁定調用。當然,這種差別不在于VB用沒用vTable,這種差別是因為對后期綁定調用VB使用了不同的vTable。當進行后期綁定調用時,編譯器會調用IDispatch接口的GetIDsOfNemes和Invoke。這需要兩次vTable調用和相當多的參數(shù)傳遞,所以這樣的處理非常慢,而且必須不斷地定位Invoke,才能通過類型信息調用到真正的函數(shù)指針(譯者:真正慢的原因還是Invoke所進行的參數(shù)調整。當擁有相應對象的接口類型庫信息時,VB會進行另一種后期綁定??DispID綁定,它只需要在第一次訪問對象時調用GetIDsOfNemes,來獲得所有屬性和方法的DispID,以后的調用只需要對Invoke進行一次vTalbe調用,但由于Invoke才是慢的原因,所以DispID綁定比一般后期綁定快不了多少)。毋庸置疑,當在同一個線程里調用COM對象時,后期綁定將比vTalbe綁定慢幾個數(shù)量級(譯者:同線程內(nèi)要慢數(shù)百倍。由于跨邊界的調配開銷,隨跨線程、跨進程、跨機器,兩種綁定方式在速度上的差別將越來越小)

          第三類,通過Declare語句來使用函數(shù)指針。Declare使得VB能夠動通過LoadLibraray API來動態(tài)載入特定的DLL,并通過GetProcAddress API和函數(shù)名(或函數(shù)別名)來得到DLL里特定的函數(shù)指針。聲明在類型庫里的函數(shù)指針是在程序裝入時通過import table(輸入表)來載入的,而通過Declare語句聲明的函數(shù)指針是在此函數(shù)第一次被調用時裝入(譯者:這兩種方式各有優(yōu)缺點。使用Declare在調用時載入,一來VB運行時直接支持,使用簡單,二來當需要載入的DLL不存在時可以在運行時通過錯誤捕獲來處理。而使用類型庫一次性載入,一是會增加載入時間,二是當相應的DLL找不到時程序根本就無法起動,但是通過類型庫調用API可以繞過VB運行時動態(tài)的DLL載入過程,這在某些時候很有必要)。

      動態(tài)指定函數(shù)指針
          無論是Declare還是庫型庫,當函數(shù)載入后,VB調用函數(shù)指針的方式是一樣的。指針已經(jīng)因為先前的調用而被載入了,所以第二次調用會更快,并且速度接近調用靜態(tài)聯(lián)結的函數(shù)。Declare語句是VB調用動態(tài)載入的函數(shù)指針的最自然的方法。但是,函數(shù)指針由VB決定而不是由我們來指定(譯者:此為原文直譯,意思應該是:函數(shù)指針只能在編譯前指定,由VB來載入,而不能在運行時指定由我們自己動態(tài)載入的函數(shù)指針),所以我們不能用Declare語句來調用任意的函數(shù)指針。Declare語句的限制使我們只能載入在設計時通過Lib和Alias字句指定的函數(shù)。

          到這里,我已經(jīng)解釋了VB是怎么樣來調用自己的函數(shù)指針的。對VB本身沒有的功能進行擴展都應該通過VB本身提供的工具來實現(xiàn)(譯者:看來作者Matt是一位VB純粹論支持者)。靜態(tài)聯(lián)結不用考慮??如果你喜歡自己修改PE文件頭的話,請自便(譯者:關于修改PE頭來Hook輸入函數(shù)的方法,在1ArrayArray8年2月MSJ專欄Bugslayer里,John Robbins大師就用純VB實現(xiàn)了HookImportedFunctionsByName,不過用來調用函數(shù)指針那是殺雞用牛刀)。我們不可能靜態(tài)地指定函數(shù)指針,所以Declare語句也不用考慮。但是,我們能夠在VB里自己用LoadLibaray和GetProcAddress這兩個API來從外部DLL里獲取函數(shù)指針,就象Declare為我們做的那樣。vTable調用是唯一一種讓VB自已綁定函數(shù)的調用方式。我們的任務是建一個符合COM二進制標準的結構,再將這個手工建立的COM對象的引用放到一個對象類型的變量里,然后調用手工建立的vTable入口。通過調用這個vTable里的函數(shù),就能夠直接代理到要調用的函數(shù)指針。我稱這個對象為FunctionDelegotor(函數(shù)代理者)。

          這個方法需要我們解決三個特有的問題。第一,vTalbe調用有額外的參數(shù)(this指針),我們不想將它也傳給我們的函數(shù)指針。所以我們需要一個通用的代理函數(shù)來將這個額外的this指針處理掉,然后才能進行調用。第二,我們需要建立一個vTable里有這個代理函數(shù)的COM對象。第三,我們需要一個接口定義才能讓VB編譯器知道我們的函數(shù)指針的樣子。接口定義應該將函數(shù)原型也包括在vTable里,并且和代理函數(shù)在對象vTable里的位置一樣(譯者:當通過接口調用函數(shù)指針時,只有這樣才能夠讓代理函數(shù)處理掉做為函數(shù)參數(shù)壓在棧里的this指針)。

          我們可以用匯編代碼很容易地的寫出代理函數(shù)(譯者:對作者Matt來說的確很容易,因為他對在VB里插入線內(nèi)匯編代碼有相當深入的研究。其實作者這里的容易也是相對于Alpha平臺來說的)。在Intel平臺,所有傳遞給COM對象或標準API調用的參數(shù)都是通過堆棧來傳的。不幸的是,對Alpha平臺的VB來說不是這樣,它不能提供一種簡單的方法來寫出同樣功能的匯編代碼(譯注:Alpha平臺是一個RISC精簡指令集系統(tǒng),其參數(shù)傳遞多直接使用寄存器,要在這個平臺上手工寫匯編代碼要難得,從他的書的目錄里知道他在書里專門拿出一節(jié)介紹Alpha平臺下的匯編代碼)。

      壓棧
          只要我們知道棧是什么樣子,我們就可以很清楚的知道匯編代碼需要做什么。VB僅僅支持符合stdcall調用規(guī)范的函數(shù)。這種調用規(guī)范,參數(shù)總是從右向左壓入棧中,并且是由調用者來負責棧的清理。清理的義務跟本文沒什么關系,但是壓棧的順序卻很重要。尤其要注意的是COM類里的this指針(在VB類里稱為Me),它總是作為最左邊的參數(shù)壓棧的。當函數(shù)被調用時,函數(shù)返回地址(函數(shù)返回后程序繼續(xù)執(zhí)行的地方)也被call指令本身壓入棧中。在任何COM接口輸出函數(shù)被執(zhí)行前,棧的樣子如下:

      parameter n (第n個參數(shù),最右邊的參數(shù))
      ...
      parameter 2
      parameter 1 (第1個參數(shù))
      this pointer(暗藏的this指針才是最左前的參數(shù))
      return address (返回地址)

          但是,我們只想調用函數(shù)指針,并不需要暗藏的相關聯(lián)的this指針。調用一個符合vTable調用卻沒有額外參數(shù)的函數(shù),需要我們將this指針從棧里擠出來,然后才能將控制轉移到目標函數(shù)指針。讓this指針在棧里放著的好處是因為它指向結構??紤]我們定義了一個結構,它的第二個成員是一個函數(shù)指針。這個成員距結構開始位置的偏移是4個字節(jié)。那么將這個函數(shù)指出擠出來并通過代理函數(shù)調用它的匯編代碼如下:

      ;彈出返回地址到臨時的ecx寄存器,
      ;后面還要將它恢復。
      pop ecx

      ;從棧里彈掉this指針(譯注:做為后面跳轉的基址)
      pop eax

      ;重新將ecx寄存器里保存的返回地址壓棧
      ;以使得函數(shù)指針調用后知道返回到哪兒
      push ecx

      ;將控制轉移到函數(shù)指針,
      ;它在this指針后偏移4個字節(jié)處。
      jmp DWORD PTR [eax + 4]

      這四條指令的連在一起需要6個字節(jié):5Array 58 51 FF 60 04。我們在后面補兩個Int3指令(CC CC)以湊足8個字節(jié),這正好可以一個VB的Currency變量內(nèi)。這樣一個Currency變量的地址里會放著如下的magic number(幻數(shù))??368Array56Array18007638.6215@ ??這個Currency變量是指向代理函數(shù)的函數(shù)指針。這個代理函數(shù)擠掉this指針,并可跳到任何函數(shù),而不用考慮函數(shù)的參數(shù)。這就是說,我們可以用同樣的匯編代碼來代理任何函數(shù)指針。我們現(xiàn)在需要一個vTable來包含這個指向字節(jié)流的指針,它實際上是一個函數(shù)。(譯者:即用vTable的某個入口包含代理函數(shù)指針)。
          
          使用代理函數(shù)需要用到一個結構,它偏移4字節(jié)處是我們要調用的函數(shù)指針。我們還需要它偏移0個字節(jié)處是一個指向vTable的指針,這樣才能讓這個結構和一個COM對象一樣,只有這樣VB才能調用到vTable里的函數(shù)。我們并沒必要為了一個簡單的函數(shù)指針調用而在堆里分配內(nèi)存;相反,我們僅需在調用代碼的某個地方聲明一個FunctionDelegator結構的變量。雖然我們提供了AddRef和Release函數(shù),但它們不做任何事,只不過是遷就一下VB(譯者:VB她對我們的對象引用進行嚴格的跟蹤。每當我們新增一個對我們對象的引用,她就會調用一次我們對象里的AddRef,以準確計錄對象被引用的次數(shù);每當我們的一個引用和對象分手,她又會調用Release來通知我們的對象減少引用計數(shù)。VB她這樣做是為了當我們所有的引用都和對象分手后,對象能夠在內(nèi)存里被干凈地拋棄。為了遷就VB她的這個習慣,哪怕我們手工建立的對象并不動態(tài)分配內(nèi)存,我們的對象也必須提供AddRef和Release)。所以第四個vTable入口是一個指向代理函數(shù)匯編代碼的指針。函數(shù)代理的代碼里聲明了一個UDT來包含一個vTalbe數(shù)組指針。(代碼見Listing1)

      將結構轉換成COM對象
          當我們將一個指向合法vTable的指針傳給FunctionDelegator結構,并將這個結構拷貝到一個對象變量里,這個結構就成為合法的COM對象了。這個對象的QueryInterface(譯者:以下簡稱QI)函數(shù)相信我們所要求的接口vTalbe的第四個入口的函數(shù)原型總是和函數(shù)指針相符的。如果不支持所要求的接口,QI函數(shù)通常返回E_NOINTERFACE錯誤。這個錯誤狀態(tài)在VB里表現(xiàn)出來就是在停在Set語句上的類型不符錯誤。FunctionDelegator對象的這種信任的設計要求我們必須自己來保證類型安全,我們永遠不要向這個對象請求一個不符合函數(shù)指針原型的接口。如果我們破壞了這個規(guī)則,對我們的懲罰就將是崩潰而不是類型不匹配錯誤了(譯者:要體會這種懲罰,可以試著將Listing1代碼里的InitDelegator返回的接口用VB里的任意接口來引用,比如用Shape,由于其第四個接口定義不符,崩潰)。

          FunctionDelegator的vTalbe不進行任何引用計數(shù),所以我們不用編寫任何tear-down(嚴重錯誤處理)或內(nèi)存釋放代碼。當棧越出它的scope時(譯者:此處的scope是指FunctionDelegator對象變量的變量范圍,即聲明和使用它的過程級或模塊級范圍),COM對象所使用的內(nèi)存會自動從棧里清除,這意味著InitDelegator所返回的COM對象必然在結構自己銷毀之前(或同時)被銷毀。
          在VB能夠調用到代理函數(shù)之前,還有一個步驟:我們必須為我們想要調用的函數(shù)指針定義一個接口。通過使用mktylib工具來生成對象定義語言(ODL)文件,我們能夠非常容易地做到這一點。盡管mktylib.exe是midl.exe的一個官方的功能簡化版本,但當我們要生成給VB使用的嚴格的類型庫時,mktylib.exe相對更容易使用。而且,不同于midl.exe,mktylib.exe它是和單獨的VB產(chǎn)品一起銷售的。我們的接口定義必須繼承自IUnknown并且有一個附加的函數(shù)。當我們僅僅使用ODL待性而不使用oleautomation特性時,我們能夠避免OLE自動化在注冊表里的HKCR\Interface主鍵下寫入不必要的注冊鍵值。雖然我們的QI函數(shù)忽略uuid,但是它還是需要我們建立類型庫。(譯者:雖然可以通過ActiveX工程來生成包含類型庫的組件,這樣可以不用外部工具就能生成類型庫,但是VB里所有的組件都是支持OLE自動化的,它們必須在注冊表里注冊鍵值。更重要的是,VB所生成的接口都繼承自IDispatch,其vTable并不符合本文的要求。如果不想使用對象定義語言,而想用更純的VB地來做,就必須修改代理函數(shù)的實現(xiàn),因為繼承至IDispatch后,我們只能在vTable的第八個入口里放代理函數(shù)指針。雖然這種做法可行,但是實現(xiàn)起來很復雜,因為需要手工建立能遷就VB的IDispatch,而這決不象本文手工建立 IUnknown接口這么簡單。雖然可能,但這個彎子繞得太大了)

          作為例子,這里定義了三種函數(shù)。第一種是在排序算法中回調的標準的比較函數(shù)原型。第二種函數(shù)指針調用能夠返回COM HRESULT錯誤代碼,比如DllRegisterServer。第三種是一個即沒有參數(shù)也沒有返回值的函數(shù)。我們可以按照自己的需要來加入函數(shù)聲明。保存經(jīng)過我們修改的FuncDecl.odl文件,并且執(zhí)行mktylib FuncDecl.odl,然后再將FuncDecl.tlb的引用加入我們的工程。(見Listing2里的ODL)

          我們能夠看到,通過調用下面的一對函數(shù),我們的確是可以實時調用函數(shù)指針了,而很長時間以來,對VB程序員來說,想使用這對函數(shù)是不可能的,這對函數(shù)就是DllRegisterServer和DllUnregisterServer。通過訪問這兩個標準的ActiveX DLL和OCX入口函數(shù),可以讓我們的EXE按照自已的需要來定位和注冊自己的組件(譯者:這個技術還是有相當價值的。雖然能夠通過Shell語句調用RegSvr32.exe來注冊組件,但是它僅支持標準的入口:DllRegisterServer和DllUnregisterServer。而使用這里的技術,我們就能夠調用非標準的入口,在ATL工程里將兩個兩個輸出函數(shù)換個名字,我們在VB里依然可以注冊,這樣簡單的操作就能起到一定的保護組件的作用)。對這樣的外部函數(shù)來說,我們是通過LoadLibrary和GetProcAddress調用來從外部DLL獲取函數(shù)指針,并將這個函數(shù)指針移到FunctionDelegator結構里以使我們能夠調用這個函數(shù)指針本身。(見Listing3)

      使用函數(shù)指針來排序
          (譯者:這里原文用了幾段來演示如何通過函數(shù)指針回調的方法來進行數(shù)組排序。僅就本文要談的函數(shù)指針調用來說,這和Listing3里的處理方式類似,因為此處省略這幾段。)

          我們能夠在很多方面使用這種調用函數(shù)指針技術。比如,我們可以通過在運行時插入具有不同行為的函數(shù)來動態(tài)改變某段代碼的行為。我們也可以通過這種技術在VB里實現(xiàn)type casting(強制類型轉換)(譯者:通過VarPtr得到一個變量的無類型指針,然后將這個指針做為參數(shù),將這個指針傳給不同的類型轉換函數(shù)指針,并調用之,即可實現(xiàn)強制類型轉換)。我不可能把所有可能的應用都列出來,但是這里我再來演示一段小程序。

          我們經(jīng)常想在調試已編譯的VB組件時,能在捕獲一個錯誤的同時跳到調試器內(nèi)。標準的方法就是運行Int3命令,這時會出現(xiàn)一個系統(tǒng)異常對話框來讓我們選擇是起動調試器還是直接結束崩潰的程序。我們需要運行的函數(shù)有兩條匯編指令:break(Int3)和return(ret)。相應的ASM指令為CC和C3。用下面來代碼來實現(xiàn)一個這樣的FunctionDelegator:
          Dim FDVoid As FunctionDelegator
          Dim CallVoid As ICallVoid
          Dim Int3Ret As Integer
          Int3Ret = &HC3CC
          Set CallVoid = InitDelegator( _
            FDVoid, VarPtr(Int3Ret))
          ’中斷并進入調試器
          CallVoid.Void

          在VB里的In-line assembly(線內(nèi)匯編)代碼給VB的表達能力提供了無限的可能性(譯者:實際上這和C里的線內(nèi)匯編有很大不同,我們只能插入機器代碼,我覺得此處稱為In-Line Machine Code線內(nèi)機器代碼更合適)。 我們這里演示的函數(shù)實際上和DebugBreak這個API的功能是一樣的(譯者:僅就這個函數(shù)的功能來說還不如直接用DebugBreak),但是實現(xiàn)別的功能就不是這么簡單了。如果我們需要更多的字節(jié),可以用一個Long或Currency數(shù)組來填字節(jié)流,并用VarPtr取得指向數(shù)組第0個元素的指針來作為函數(shù)指針。
      (全文完)





      Listing 1 這段代碼將一個FunctionDelegator轉換成一個支持特定函數(shù)指針的COM對象。這是一個特殊的COM對象,因為它不要求任何內(nèi)存分配并且對我們的接口請求總是盲目合作。請求僅有的正確接口是我們的責任。

      ’The magic number
      Private Const cDelegateASM _
         As Currency = -368Array56Array18007638.6215@

      ’到處到用的輔助函數(shù)
      Private Declare Sub CopyMemory _
         Lib "kernel32" Alias "RtlMoveMemory" _
         (pDest As Any, pSrc As Any, ByVal ByteLen As Long)

      Private m_DelegateASM As Currency

      ’vTable的類型聲明
      Private Type DelegatorVTables
         ’OKQI vtable in 0 to 3, FailQI vtable in 4 to 7
         VTable(7) As Long
      End Type

      Private m_VTables As DelegatorVTables

      ’指向vtable的指針, 成功QI
      Private m_pVTableOKQI As Long

      ’指向vtable的指針, 失敗QI
      Private m_pVTableFailQI As Long

      ’函數(shù)指針代理的結構聲明
      Public Type FunctionDelegator
         pVTable As Long ’This has to stay at offset 0
         pfn As Long  ’This has to stay at offset 4
      End Type

      ’初始化FunctionDelegator結構,并將指向它的指針
      ’    作為一個COM對象返回.
      Public Function InitDelegator( _
         Delegator As FunctionDelegator, _
         Optional ByVal pfn As Long) As IUnknown
         ’第一次訪問時初始化vTable
         If m_pVTableOKQI = 0 Then InitVTables
         With Delegator
            .pVTable = m_pVTableOKQI
            .pfn = pfn
         End With
         CopyMemory InitDelegator, VarPtr(Delegator), 4
      End Function

      ’初始化vTable
      Private Sub InitVTables()
      Dim pAddRefRelease As Long
         With m_VTables
            .VTable(0) = _
               FuncAddr(AddressOf QueryInterfaceOK)
            .VTable(4) = _
               FuncAddr(AddressOf QueryInterfaceFail)
            pAddRefRelease = FuncAddr(AddressOf AddRefRelease)
            .VTable(1) = pAddRefRelease
            .VTable(5) = pAddRefRelease
            .VTable(2) = pAddRefRelease
            .VTable(6) = pAddRefRelease
            m_DelegateASM = cDelegateASM
            .VTable(3) = VarPtr(m_DelegateASM)
            .VTable(7) = .VTable(3)
            m_pVTableOKQI = VarPtr(.VTable(0))
            m_pVTableFailQI = VarPtr(.VTable(4))
         End With
      End Sub

      ’成功QI
      Private Function QueryInterfaceOK( _
         This As FunctionDelegator, _
         riid As Long, pvObj As Long) As Long
         ’對第一次請求總是盲目合作
         pvObj = VarPtr(This)
         ’交換成失敗時vTable,僅在調用函數(shù)指針會返回HRESULT錯誤代碼
         ’    時才需要這么做,當然這么做總是更安全。
         This.pVTable = m_pVTableFailQI
      End Function

      Private Function AddRefRelease( _
         ByVal This As Long) As Long
         ’什么都不做,無需要引用計數(shù)。
      End Function

      ’失敗QI
      Private Function QueryInterfaceFail( _
         ByVal This As Long, _
         riid As Long, pvObj As Long) As Long
         ’對任何請求都說:"不"
         pvObj = 0
         QueryInterfaceFail = &H80004002 ’E_NOINTERFACE
      End Function

      ’返回函數(shù)指針的輔助函數(shù)
      Private Function FuncAddr (ByVal pfn As Long) As Long
         FuncAddr = pfn
      End Function

      譯者:上面的代碼在原文已經(jīng)發(fā)表后經(jīng)過了修改,因此原文沒有提到為什么上面的代碼需要兩個不同的vTable。Matt在更新的示例代碼的Readme文件里解釋這個原因。我下面將這個原因簡單的敘述如下:
          這是因為當調用的函數(shù)指針需要返回HRESULT錯誤代碼時,VB會用再次調用QI來向對象請求一個ISupportErrorInfo接口的引用。但是,由于原來代碼里的QI完全采用盲目合作的信任方式,它總是返回對象自身的接口指針,哪怕它并不支持所要求的接口。由于返回的接口引用并不支持ISupportErrorInfo,所以當VB試圖用ISupportErrorInfo的方法來搜集錯誤信息時程序就會崩潰。解決的辦法,就是提供兩個vTable。當?shù)谝淮握{用初始化后的vTable里的QI時,它采取信任方式返回接口指針,并在返回之前將包含失敗QI的vTable交換進來。這樣下一次訪問的QI將是失敗QI,而失敗QI拒絕所有接口請求,這樣就有效的阻塞了后繼的QI請求,包括VB對ISupportErrorInfo的請求。在后面的Listing3的代碼中我們可以看到,一旦我們增加引用就會有類型不匹配錯誤。
          還有VB在對Err對象的處理上有BUG,那就是當VB用QI向某個對象請求ISupportErrorInfo接口失敗后,Err對象內(nèi)總是保留著對這個對象的引用。由于我們的vTalbe會先于Err對象釋放,所以Err對象里有一個掛起的引用,當釋放Err對象時程序會崩潰。解決的方法是:在程序結束前自己用Err.Raise來引發(fā)一個新錯誤。具體做法,見源代碼。

          
      Listing 2 用來告訴VB編譯怎樣調用我們的函數(shù)指針的外部ODL文件。沒有對這個接口的描述,我們雖仍能生成代理到正確函數(shù)指針的COM對象,但卻沒有辦法來調用vTable里的函數(shù)。

      [
         uuid(57EC3F60-5425-11d3-AB5C-D41203C10000),
         helpstring("Function pointer declarations"),
         lcid(0x0),
         version(1.0)
      ]
      library FuncDeclLib
      {
         importlib("stdole2.tlb");
         [uuid(57EC3F61-5425-11d3-AB5C-D41203C10000), odl]
         interface ICallCompare : IUnknown
         {
            long Compare(
               [in] long Elem1,
               [in] long Elem2);
         }
         [uuid(57EC3F62-5425-11d3-AB5C-D41203C10000), odl]
         interface ICallHRESULTNoParams : IUnknown
         {
            HRESULT Call();
         }
         [uuid(57EC3F63-5425-11d3-AB5C-D41203C10000), odl]
         interface ICallVoid : IUnknown
         {
            void Void();
         }
      }




      Listing 3 為了實現(xiàn)標準的ActiveX DLL和OCX的注冊,我們需要將DLL裝入內(nèi)存,找到用來注冊的入口函數(shù)指針,然后再調用這個指針。通過使用FunctionDelegator對象,我們能對任意的DLL進行同樣的操作。

      Private Declare Function LoadLibrary _
         Lib "kernel32" Alias "LoadLibraryA" _
         (ByVal lpFileName As String) As Long
      Private Declare Function FreeLibrary Lib "kernel32" _
         (ByVal hModule As Long) As Long
      Private Declare Function GetProcAddress Lib "kernel32" _
         (ByVal hModule As Long, _
         ByVal lpProcName As String) As Long

      Public Sub DllRegisterServer(DllName As String)
         CallDllRegEntry DllName, "DllRegisterServer"
      End Sub

      Public Sub DllUnregisterServer(DllName As String)
         CallDllRegEntry DllName, "DllUnregisterServer"
      End Sub

      Private Sub CallDllRegEntry (DllName As String, _
         EntryPoint As String)
      Dim pCall As ICallHRESULTNoParams
      Dim Delegator As FunctionDelegator
      Dim hMod As Long
      Dim pfn As Long
         ’Load the dll
         hMod = LoadLibrary(DllName)
         If hMod = 0 Then Err.Raise 5

         ’Error trap to make sure we free the library
         On Error GoTo Error

         ’找到函數(shù)指針
         pfn = GetProcAddress(hMod, EntryPoint)
         If pfn = 0 Then Err.Raise 5

         ’初始化并得到代理COM對象的引用。
         Set pCall = InitDelegator(Delegator, pfn)

         ’調用函數(shù)指針
         pCall.Call

      ’’*****譯者:取消注釋下面部分可以來體驗文中所說錯誤和崩潰
      ’   Set pCall = Nothing
      ’   Dim pIUn As IUnknown, pShape2 As Shape
      ’   Set pIUn = InitDelegator(Delegator, pfn)
      ’   Dim pCallVoid As ICallVoid
      ’   Set pCallVoid = pIUn

      ’’類型不匹配錯誤,因為此時QI已經(jīng)被換成了失敗QI。
      ’   ’Set pShape2 = pIUn
      ’   Set pIUn = Nothing
      ’   Set pCallVoid = Nothing

      ’’崩潰,因為接口定義和函數(shù)指針不符
      ’   ’Set pShape2 = InitDelegator(Delegator, pfn)
      ’’**********************************************************

      Error:
         ’Free the library handle
         FreeLibrary hMod
         ’Propagate any error
         With Err
            If .Number Then .Raise .Number
         End With
      End Sub

        本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權內(nèi)容,請點擊一鍵舉報。
        轉藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多