新聞| | PChome| 登入
2007-05-18 18:05:25 | 人氣882| 回應0 | 上一篇 | 下一篇
推薦 0 收藏 0 轉貼0 訂閱站台

將函數指標傳遞到 DLL 程序和型態程式庫中

如果您熟悉 C 語言,那麼應該熟悉函數指標。若您不熟悉,則此觀念值得加以說明。函數指標是一種約定,您可以用它將一個自訂函數的位址作為引數傳遞到另一個已宣告要使用的函數中。利用函數指標,您可以呼叫如 EnumWindows 等函數,來列出系統中已開啟的視窗,或利用 EnumFontFamilies 來列出目前的所有字型。利用函數指標,您還可以存取 Win32 API 中的其它許多函數,這是Visual Basic Basic以前沒有提供的功能。

在 Visual Basic 中,使用函數指標仍然有一些限制。詳細資訊,請參閱本內容後面的〈Limitations and Risks with Function Pointers〉。

學習函數指標

函數指標的用法最好是用範例來說明。首先,請看一看 Win32 API 中的 EnumWindows 函數:

Declare Function EnumWindows lib "user32" _
(ByVal lpEnumFunc as Long, _
ByVal lParam as Long ) As Long

EnumWindows 是一個列舉函數,它能夠列出系統中每一個開啟視窗的物件代碼。EnumWindows 的工作模式是重覆地呼叫傳遞給它當做第一個引數的函數 (lpEnumFunc)。每當 EnumWindows 呼叫函數時,EnumWindows 都會傳遞一個開啟視窗的物件代碼。

當您在程式碼中呼叫 EnumWindows 時,您就傳遞了一個使用者自訂函數作為它的第一個引數,以處理一系列的值。例如,您可以撰寫一個可在清單方塊中加入值的函數,或將 hWnd 值轉換成視窗的名稱,或執行其它任何動作。

為了指定傳遞的引數是一個使用者自訂函數,您可以在函數名稱的前面加上 AddressOf 關鍵字。第二個引數可以是任何合適的值。例如,如果要把 MyProc 函數作為傳遞的引數,可以如下所示呼叫 EnumWindows:

x = EnumWindows(AddressOf MyProc, 5)

在呼叫程序時,指定的自訂函數稱為回呼函數 (callback function)。回呼函數 (通常簡稱為「回呼」) 能夠對程序所提供的資料執行指定的動作。

回呼函數的引數必須具有特定的型態,由使用回呼函數的 API所決定。至於需要什麼引數,以及如何呼叫它們,請參閱 API 文件。

使用 AddressOf 關鍵字

任何要呼叫 Visual Basic 函數指標的程式碼都必須放在標準的 .BAS 模組中-您不可以將其放在物件類別模組中,也不能放到表單上。當您用 AddressOf 關鍵字來呼叫已宣告的函數時,必須注意下列事項:

  • AddressOf 必須直接放在引數清單中某個引數的前面;該引數可以是使用者自訂的 Sub、函數或屬性的名稱。

  • 以 AddressOf 關鍵字所呼叫的 Sub、函數、屬性等,必須和相關的宣告及程序放在同一個專案中。

  • AddressOf 只能用於使用者自訂的 Sub、函數或屬性,無法用於以 Declare 陳述式所宣告的外部函數,也無法用於型態程式庫中的函數。

  • 您可以在宣告的 Sub、Function 或使用者自訂型態定義中,將函數指標傳遞給 As Any 或 As Long 型態的引數。

附註 您可用 Visual C++ (或類似的工具) 編譯,在 DLL 中建立您自己的回呼函數原形 (prototype)。要配合 AddressOf 使用的話, 您的原形必須使用 __stdcall 呼叫慣例。預設的呼叫慣例 (_cdecl) 將無法搭配 AddressOf 順利執行工作。

在變數中存放函數指標

有時在把函數指標傳遞到 DLL 之前,需要將它存放在一個中間變數中。如果您要將函數指標從一個 Visual Basic 函數傳遞到另一個函數,這個方法也很有用。例如,當呼叫 RegisterClass 之類的函數時,需透過引數傳遞一指標,指向一個結構 (WndClass),而該結構的其中一個元素為一函數指標,那麼這種做法就是必要的。

要將一個函數指標指定給結構中的一個元素,需要撰寫一個包裝函數 (wrapper)。例如,以下的 FnPtrToLong 是一個包裝函數,使用它可以將函數指標放入任何結構中:

Function FnPtrToLong (ByVal lngFnPtr As Long) As Long
   FnPtrToLong = lngFnPtr
End Function

要使用該函數時,首先要宣告型態,然後呼叫 FnPtrToLong。再以 AddressOf 加上回呼函數的名稱作為函數的引數。

Dim mt as MyType
mt.MyPtr = FnPtrToLong(AddressOf MyCallBackFunction)

子物件類別

子物件類別技術可讓您截取傳給控制項或表單的視窗訊息。透過截取這些訊息,您可以撰寫自己的程式碼,來改變或者延伸物件的行為。子物件類別技術相當複雜,如果要全面討論將超出本書的範圍。以下的範例簡要說明了這項技術。

重要事項 當 Visual Basic 在中斷模式時,不允許呼叫 vtable 方法或 AddressOf 函數。為了安全起見,Visual Basic 將傳回 0 給 AddressOf 函數的呼叫者,而沒有真的呼叫該函數。在子物件類別的情況下,這意味著 WindowProc 傳回 0 到 Windows。但 Windows 要求它的許多訊息傳回非 0 的值,因此傳回的常數 0 可能將導致 Windows 與 Visual Basic 之間的死結,進而強迫您停止處理程序的動作。

以下的範例程式包括一個簡單的表單,其中只有兩個命令按鈕。此程式碼的目的是截取傳送到表單的 Windows 訊息,並在「立即顯示」視窗中列印出這些訊息的值。

程式碼的第一部份是宣告,包括 API 函數宣告,常數宣告和變數宣告:

Declare Function CallWindowProc Lib "user32" Alias _
"CallWindowProcA" (ByVal lpPrevWndFunc As Long, _
   ByVal hwnd As Long, ByVal Msg As Long, _
   ByVal wParam As Long, ByVal lParam As Long) As Long

Declare Function SetWindowLong Lib "user32" Alias _
"SetWindowLongA" (ByVal hwnd As Long, _
ByVal nIndex As Long, ByVal dwNewLong As Long) As Long

Public Const GWL_WNDPROC = -4
Global lpPrevWndProc As Long
Global gHW As Long

接著,使用兩個常式來銜接到訊息流。第一個程序 (Hook) 呼叫了 SetWindowLong 函數,它使用了 GWL_WNDPROC 索引來建立視窗物件類別的子物件類別,視窗物件類別是用來建立視窗的。然後它使用 AddressOf 關鍵字和回呼函數 (WindowProc) 來截取訊息,並在「立即顯示」視窗中列印訊息的值。第二個程序 (Unhook) 則關閉了子物件類別,同時回呼函數也將被原本的 Windows 程序所取代。

Public Sub Hook()
   lpPrevWndProc = SetWindowLong(gHW, GWL_WNDPROC, _
   AddressOf WindowProc)
End Sub

Public Sub Unhook()
   Dim temp As Long
   temp = SetWindowLong(gHW, GWL_WNDPROC, _
   lpPrevWndProc)
End Sub

Function WindowProc(ByVal hw As Long, ByVal uMsg As _
Long, ByVal wParam As Long, ByVal lParam As Long) As _
Long
   Debug.Print "Message: "; hw, uMsg, wParam, lParam
   WindowProc = CallWindowProc(lpPrevWndProc, hw, _
   uMsg, wParam, lParam)
End Function

最後,表單的程式碼設定了 hWnd 的起始值,而按鈕部份的程式碼則只是單純地呼叫以下兩個副程式:

Private Sub Form_Load()
   gHW = Me.hwnd
End Sub

Private Sub Command1_Click()
   Hook
End Sub

Private Sub Command2_Click()
   Unhook
End Sub

函數指標的限制與風險

使用函數指標是有風險的。每當您呼叫 DLL 時,將會失去 Visual Basic 開發環境的穩定性,使用函數指標的危險性就更大了,因為它很容易導致應用程式失敗,並遺失您的結果。在設計程式時,請經常地儲存和備份您的程式和資料。以下列出了使用函數指標時的一些注意事項:

  • 偵錯。如果您的應用程式在中斷模式下引發一個回呼函數,那麼程式可以正常執行,但是中斷點和逐步設定將被忽略。如果回呼函數產生了異常,您可以偵測到它並傳回目前值。在中斷模式下,如果堆疊中有回呼函數,那麼重設是不允許的。

  • Thunksthunking是 Windows用來使程式碼可重置的方法。如果您在中斷模式下刪除了回呼函數,它的 thunk 被修改傳回 0。這個值通常是正確的,但也有意外的情況。如果您在中斷模式下先刪除了一個回呼函數,然後又再次將其加入,那麼有些被呼叫者可能對新的函數位址一無所知。Thunk 不會使用在 .exe 中,因為指標將直接傳遞到輸入點。

  • 以錯誤的署名 (signature) 傳遞函數。如果您傳遞的回呼函數,其引數個數與呼叫者所指定的不一致,或是在呼叫某個引數時,錯誤地使用了 ByRef 或 ByVal,則應用程式可能會失敗。因此在傳遞函數時,署名一定要正確。

  • 將函數傳遞給已不存在的 Windows 程序。在以子物件類別建立視窗時,需要將一個函數指標傳遞給Windows的 Windows 程序 (WindowProc)。但是,在 IDE 中執行您的應用程式呼叫 WindowProc 時,其內部的函數可能已經被破壞了。這可能導致一般性保護錯誤,並關閉 Visual Basic 開發環境。

  • 不支援「Basic 到 Basic」的函數指標。指向 Visual Basic 函數的指標無法在 Visual Basic本身之間傳遞。目前,只支援從 Visual Basic指向 DLL 函數的指標。

  • 回呼程序中資料夾包含錯誤。回呼程序中的任何錯誤都不傳回給最初呼叫它的外部程序是很重要的。您可以在回呼程序的一開始加上 On Erroe Resume Next 陳述式來達成此項功能。

台長: Kenny
人氣(882) | 回應(0)| 推薦 (0)| 收藏 (0)| 轉寄
全站分類: 教育學習(進修、留學、學術研究、教育概況) | 個人分類: 程式設計 |
此分類下一篇:使用圖片物件
此分類上一篇:使用者繪製控制項

是 (若未登入"個人新聞台帳號"則看不到回覆唷!)
* 請輸入識別碼:
請輸入圖片中算式的結果(可能為0) 
(有*為必填)
TOP
詳全文