Office中國(guó)論壇/Access中國(guó)論壇

 找回密碼
 注冊(cè)

QQ登錄

只需一步,快速開始

返回列表 發(fā)新帖
查看: 2898|回復(fù): 6
打印 上一主題 下一主題

[模塊/函數(shù)] 【轉(zhuǎn)載 / 資料】Matthew Curland的VB函數(shù)指針調(diào)用

[復(fù)制鏈接]
跳轉(zhuǎn)到指定樓層
1#
發(fā)表于 2005-8-28 08:51:00 | 只看該作者 回帖獎(jiǎng)勵(lì) |倒序?yàn)g覽 |閱讀模式
Matthew Curland簡(jiǎn)介:

    Visual Studio開發(fā)小組成員,參與開發(fā)了VB的IntelliSense和Object Browser。他是VB資深專家,對(duì)VB有非常深入的研究,堪稱VB大師。所著《Advanced Visual Basice》是闡述VB高級(jí)編程技巧的一本好書。

    本文英文原著可見2000年2月份《Visual Basic Programmer's Journal》(VB程序員月刊)里的《Call Function Pointers》,這是他發(fā)表的妙文之一,他的書里的第11章和本文同名,本文應(yīng)該是這一章節(jié)的精華。



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



本文原文:http://www.devx.com/premier/mgznarch/vbpj/2000/02feb00/mc0200/mc0200.asp

(要先注冊(cè)成premier用戶)

本文配套代碼:

http://www.devx.com/free/mgznarch/vbpj/code/2000/02feb00/vb0002mc_p.zip





關(guān)鍵字:函數(shù)指針,COM、對(duì)象、接口,vTalbe,VB匯編,動(dòng)態(tài)DLL調(diào)用。

級(jí)別:高級(jí)

要求:了解VB對(duì)象編程,了解匯編。



                          調(diào)用函數(shù)指針

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



作者:Matther Curland

要求:使用本文的示例代碼,你需要VB5或VB6的專業(yè)版或企業(yè)版。

    從Visual Basic 5.0開始Basic語(yǔ)言引入了一個(gè)重要的特性:AddressOf運(yùn)算符。這個(gè)運(yùn)算符能夠讓VB程序員直接體會(huì)到將自己的函數(shù)指針?biāo)统鋈サ目旄。比如我們(cè)赩B里就能夠得到系統(tǒng)字體的列表,我們能夠通過標(biāo)準(zhǔn)的API調(diào)用來(lái)進(jìn)行子類化。一句話,我們終于可以象文檔里所說(shuō)的那樣來(lái)使用Win32 API了。

    不過,這個(gè)新玩具只能給我們帶來(lái)短暫的快感,因?yàn)檫@個(gè)禮物并不完整。我們可以送出函數(shù)指針,但卻沒人能將函數(shù)指針?biāo)徒o我們。事實(shí)上,我們甚至不能給我們自己送函數(shù)指針,這使我們不能夠體驗(yàn)送禮的真正樂趣(譯者:呵呵,光送禮卻不能收禮的確沒趣)。AddressOf讓我們看到了廣袤天地的一角,但是VB卻不讓我們?nèi)娴靥剿魉,因(yàn)閂B根本就不讓我們調(diào)用函數(shù)指針,我們只能提供函數(shù)指針(譯者:可以先將函數(shù)指針?biāo)徒oAPI,然后讓API回調(diào)自已的函數(shù)指針來(lái)完成函數(shù)指針調(diào)用的功能,但這還是要先把禮物送給別人)。其實(shí),我們能夠自己來(lái)實(shí)現(xiàn)調(diào)用函數(shù)指針的功能,我們可以手工將一個(gè)對(duì)COM接口的vTable綁定調(diào)用變成一個(gè)函數(shù)指針調(diào)用。最妙的是:我們能夠在純VB里寫出調(diào)用函數(shù)指針的代碼,不需要任何輔助的DLL。



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



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



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



VB對(duì)某些函數(shù)指針的調(diào)用卻困難得多

    對(duì)于另兩類函數(shù)指針,VB必須在運(yùn)行時(shí)進(jìn)行額外的工作才能夠找出它們。

    第二類,VB調(diào)用一個(gè)COM對(duì)象接口里的方法。我們可能認(rèn)為建立COM對(duì)象的工作是相當(dāng)復(fù)雜的,如果完全用VB來(lái)為我們建造COM的所有組成部分的話,但事實(shí)上并不是這樣。按照COM的二進(jìn)制標(biāo)準(zhǔn),一個(gè)COM對(duì)象是一個(gè)指針,這個(gè)指針指向一個(gè)結(jié)構(gòu),這個(gè)特定結(jié)構(gòu)的第一個(gè)元素是一個(gè)指向函數(shù)指針數(shù)組的指針。這個(gè)函數(shù)指針數(shù)組(又叫虛擬函數(shù)表,簡(jiǎn)稱vTable)里的前三個(gè)指針,一定是標(biāo)準(zhǔn)QueryInterface,AddRef,Release函數(shù)。vTable里接下來(lái)的函數(shù)符合給定的COM對(duì)象接口定義里的函數(shù)定義(見圖一)









圖一:

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



    當(dāng)VB通過一個(gè)對(duì)象類型的變量來(lái)調(diào)用一個(gè)COM對(duì)象的方法或?qū)傩詴r(shí),這個(gè)變量里存放著對(duì)這個(gè)COM對(duì)象接口的引用。VB要定位函數(shù)時(shí),首先要通過COM引用的第一個(gè)元素來(lái)獲得指向vTalbe的指針,然后才能在vTable里定位函數(shù)指針。對(duì)一個(gè)vTable調(diào)用來(lái)說(shuō),編譯器提供了COM
分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏 分享分享 分享淘帖 訂閱訂閱
2#
 樓主| 發(fā)表于 2005-8-28 08:51:00 | 只看該作者
動(dòng)態(tài)指定函數(shù)指針

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



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



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



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



壓棧

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



parameter n (第n個(gè)參數(shù),最右邊的參數(shù))

...

parameter 2

parameter 1 (第1個(gè)參數(shù))

this pointer(暗藏的this指針才是最左前的參數(shù))

return address (返回地址)



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



;彈出返回地址到臨時(shí)的ecx寄存器,

;后面還要將它恢復(fù)。

pop ecx



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

pop eax



;重新將ecx寄存器里保存的返回地址壓棧

;以使得函數(shù)指針調(diào)用后知道返回到哪兒

push ecx



;將控制轉(zhuǎn)移到函數(shù)指針,

;它在this指針后偏移4個(gè)字節(jié)處。

jmp DWORD PTR [eax + 4]



這四條指令的連在一起需要6個(gè)字節(jié):59 58 51 FF 60 04。我們?cè)诤竺嫜a(bǔ)兩個(gè)Int3指令(CC CC)以湊足8個(gè)字節(jié),這正好可以一個(gè)VB的Currency變量?jī)?nèi)。這樣一個(gè)Currency變量的地址里會(huì)放著如下的magic number(幻數(shù))——368956918007638.6215@ ——這個(gè)Currency變量是指向代理函數(shù)的函數(shù)指針。這個(gè)代理函數(shù)擠掉this指針,并可跳到任何函數(shù),而不用考慮函數(shù)的參數(shù)。這就是說(shuō),我們可以用同樣的匯編代碼來(lái)代理任何函數(shù)指針。我們現(xiàn)在需要一個(gè)vTable來(lái)包含這個(gè)指向字節(jié)流的指針
3#
 樓主| 發(fā)表于 2005-8-28 08:52:00 | 只看該作者
Listing 1 這段代碼將一個(gè)FunctionDelegator轉(zhuǎn)換成一個(gè)支持特定函數(shù)指針的COM對(duì)象。這是一個(gè)特殊的COM對(duì)象,因?yàn)樗灰笕魏蝺?nèi)存分配并且對(duì)我們的接口請(qǐng)求總是盲目合作。請(qǐng)求僅有的正確接口是我們的責(zé)任。



'The magic number

Private Const cDelegateASM _

   As Currency = -368956918007638.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ù)指針代理的結(jié)構(gòu)聲明

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結(jié)構(gòu),并將指向它的指針

'    作為一個(gè)COM對(duì)象返回.

Public Function InitDelegator( _

   Delegator As FunctionDelegator, _

   Optional ByVal pfn As Long) As IUnknown

   '第一次訪問時(shí)初始化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

   '對(duì)第一次請(qǐng)求總是盲目合作

   pvObj = VarPtr(This)

   '交換成失敗時(shí)vTable,僅在調(diào)用函數(shù)指針會(huì)返回HRESULT錯(cuò)誤代碼

   '    時(shí)才需要這么做,當(dāng)然這么做總是更安全。

   This.pVTable = m_pVTableFailQI

End Function



Private Function AddRefRelease( _

   ByVal This As Long) As Long

   '什么都不做,無(wú)需要引用計(jì)數(shù)。

End Function



'失敗QI

Private Function QueryInterfaceFail( _

   ByVal This As Long, _

   riid As Long, pvObj As Long) As Long

   '對(duì)任何請(qǐng)求都說(shuō):"不"

   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)過了修改,因此原文沒有提到為什么上面的代碼需要兩個(gè)不同的vTable。Matt在更新的示例代碼的Readme文件里解釋這個(gè)原因。我下面將這個(gè)原因簡(jiǎn)單的敘述如下:

    這是因?yàn)楫?dāng)調(diào)用的函數(shù)指針需要返回HRESULT錯(cuò)誤代碼時(shí),VB會(huì)用再次調(diào)用QI來(lái)向?qū)ο笳?qǐng)求一個(gè)ISupportErrorInfo接口的引用。但是,由于原來(lái)代碼里的QI完全采用盲目合作的信任方式,它總是返回對(duì)象自身的接口指針,哪怕它并不支持所要求的接口。由于返回的接口引用并不支持ISupportErrorInfo,所以當(dāng)VB試圖用ISupportErrorInfo的方法來(lái)搜集錯(cuò)誤信息時(shí)程序就會(huì)崩潰。解決的辦法,就是提供兩個(gè)vTable。當(dāng)?shù)谝淮握{(diào)用初始化后的vTable里的QI時(shí),它采取信任方式返回接口指針,并在返回之前將包含失敗QI的vTable交換進(jìn)來(lái)。這樣下一次訪問的QI將是失敗QI,而失敗QI拒絕所有接口請(qǐng)求,這樣就有效的阻塞了后繼的QI請(qǐng)求,包括VB對(duì)ISupportErrorInfo的請(qǐng)求。在后面的Listing3的代碼中我們可以看到,一旦我們?cè)黾右镁蜁?huì)有類型不匹配錯(cuò)誤。

    還有VB在對(duì)Err對(duì)象的處理上有BUG,那就是當(dāng)VB用QI向某個(gè)對(duì)象請(qǐng)求ISupportErrorInfo接口失敗后,Err對(duì)象內(nèi)總是保留著對(duì)這個(gè)對(duì)象的引用。由于我們的vTalbe會(huì)先于Err對(duì)象釋放,所以Err對(duì)象里有一個(gè)掛起的引用,當(dāng)釋放Err對(duì)象時(shí)程序會(huì)崩潰。解決的方法是:在程序結(jié)束前自己用Err.Raise來(lái)引發(fā)一個(gè)新錯(cuò)誤。具體做法,見源代碼。



   

Listing 2 用來(lái)告訴VB編譯怎樣調(diào)用我們的函數(shù)指針的外部ODL文件。沒有對(duì)這個(gè)接口的描述,我們雖仍能生成代理到正確函數(shù)指針的COM對(duì)象
4#
 樓主| 發(fā)表于 2005-8-28 08:54:00 | 只看該作者
How VB Allocates Fixed-Size Arrays <TR align=left bgColor=#ffffcc>You might think the entire array is being created as a local variable on the stack when you allocate a fixed-sized array in VB (I did). This is only a half-truth. The array descriptor, a structure that describes an array抯 size and type, is placed on the stack like any other local variable. However, the array抯 data is allocated on the heap. If you want a fully stack-allocated array, you need to embed your fixed-size array in a user-defined type (UDT) and use a local variable typed as the UDT, not as a fixed-size array. The reason for these allocation semantics is that the 64K size restriction on embedded fixed-size arrays does not apply to nonembedded fixed-size arrays, but the 64K limit does apply to the total size of local variables. Similar semantics apply to a module-level fixed-size array, except that embedded fixed-size arrays are allocated with the module instead of on the stack. You should take the precaution of defining all vtables as fixed-size arrays in a UDT to protect against the case where an object is still pointing at a function delegator structure when VB is tearing the process down. During teardown, VB clears all heap-allocated variables (objects, arrays, strings) in all modules before releasing the memory allocated for each specific module. Your tear-down code is dependent on the ordering of your modules in the VBP file if your vtable is heap-allocated, and you don抰 have to worry about custom vtables vaporizing beneath your objects or disappearing during tear-down if you define any custom vtable in an embedded fixed-size array.
5#
 樓主| 發(fā)表于 2005-8-28 08:55:00 | 只看該作者
本文的附圖:









我花了整整5個(gè)小時(shí),從網(wǎng)上找到的本文的VB源碼:







[此貼子已經(jīng)被作者于2005-8-28 1:33:01編輯過]

本帖子中包含更多資源

您需要 登錄 才可以下載或查看,沒有帳號(hào)?注冊(cè)

x
6#
發(fā)表于 2005-8-29 16:06:00 | 只看該作者
提示: 作者被禁止或刪除 內(nèi)容自動(dòng)屏蔽

點(diǎn)擊這里給我發(fā)消息

7#
發(fā)表于 2005-8-30 02:17:00 | 只看該作者
好文!!
您需要登錄后才可以回帖 登錄 | 注冊(cè)

本版積分規(guī)則

QQ|站長(zhǎng)郵箱|小黑屋|手機(jī)版|Office中國(guó)/Access中國(guó) ( 粵ICP備10043721號(hào)-1 )  

GMT+8, 2024-10-23 08:31 , Processed in 0.092033 second(s), 32 queries .

Powered by Discuz! X3.3

© 2001-2017 Comsenz Inc.

快速回復(fù) 返回頂部 返回列表