這篇文章有點長,步驟有點煩雜,覺得累的話請先喝杯水再來。

在 C/C++ 裡面,有些函式提供了很人性化的介面機制,叫 function pointer - 函式指標,只要在 MSDN 上原型裡面,參數看到是 PROC 結尾的,幾乎都是函式指標。只要是在 API 裡面出現的函式指標,那跟 struct 沒什麼二樣,有一定的函式規格,還要再去查這個函式指標的原型長怎樣。

我們以 EnumWindow 為例,這個 API 函式主要是在列舉目前 Windows 上的可見視窗,它的原型如下

BOOL WINAPI EnumWindows(
  __in  WNDENUMPROC lpEnumFunc,
  __in  LPARAM lParam
);

注意到了,第一個參數就是所謂的函式指標,這裡的用法有點步驟,等一下直接看範例碼會比較快
第二個參數 lParam 提供一些相關資訊。

剛說過了,API 裡面出現了函式指標了,於是我們再上 MSDN 去查 WNDENUMPROC lpEnumFunc 它的規格長怎樣,原型如下

BOOL CALLBACK EnumWindowsProc(
  __in  HWND hwnd,
  __in  LPARAM lParam
);

嗯,這個看起來簡單很多,第一個 HWND hwnd 是視窗 handle,第二個 lParam ,這又是提供另一些資料,我們不會用到。但要注意,這裡有傳回值是 BOOL。

好了,接下來用 autoit 去做會有一些步驟..
這裡先弄清楚,我們真正要用的是 EnumWindows(A) 這個函式,
但 EnumWindows 必須先自己寫一個 "符合標準,但是要自己寫的" 
CALLBACK EnumWindowsProc (B) 函式,等於是要分二層,
先把裡面的 (B) 處理好,才能讓 (A) 去使用,於是要先處理的,是 CALLBACK 函式 - EnumWindowsProc

// ---------------------------------------------------------------------
// 步驟一  針對 EnumWindowsProc 寫一個自己的處理函式

EnumWindowsProc 這 function 型態輸入引數是 hwnd, lParam,傳回 BOOL我們 function 也不去動它,自己先寫一個 myEnumWindowProc,一開始會長得像這樣

; 自己的 CALLBACK 函式
Function myEnumWindowProc($hwnd, $lParam)

EndFunc

再來,剛說過了,這個函式的引數第一個是視窗的 handle ,第二個參數我們不在意,我們這自己寫的程式寫簡單一點,只要去秀出這個視窗的抬頭文字是什麼。 autoit 裡面,有視窗 handle 要取得它的文字是用 WinGetTitle,於是我們函式就變成這樣

; 自己的 CALLBACK 函式
Func myWindowProc($hWnd, $lParam)
     MsgBox(0, "", WinGetTitle($hWnd))
     return 1
EndFunc

嗯,這裡要跟各位說,上面這個看起來好像沒問題,其實問題很大。因為 EnumWindows 這個函式有用到另一個函式 -  EnumWindowsProc ,它會一直不斷不斷不斷不斷... 地一直找下一個視窗,直到myWindowProc 傳回0為止。意思是如果我上面一直傳回 1 的話, EnumWindowsProc 就會找不停,可能會找到當為止。所以必須再給它適合的終止條件

; 自己的 CALLBACK 函式
Func myWindowProc($hWnd, $lParam)
     If WinGetTitle($hWnd) <> "" And BitAnd(WinGetState($hWnd), 2) Then
          $res = MsgBox(1, "", WinGetTitle($hWnd))
          If $res = 2 Then Return 0   ; 取消點選,返回 0 結束列出動作
    EndIf
    Return 1    ; 返回 1 繼續列出

EndFunc

// ---------------------------------------------------------------------
// 步驟二  為自己新增的 CALLBACK  函式進行註冊

要使用上述自己的 CALLBACK 函式之前,記得用AutoIt 向 dll 註冊函式,註冊的時候再看一次 API 的原型

BOOL CALLBACK EnumWindowsProc(
  __in  HWND hwnd,
  __in  LPARAM lParam
);

傳回 BOOL(對應用 autoit 可以用 int 帶過),第一個是 hwnd,第二個是 lParam。於是我們使用 DllCallbackRegister 函式為 CALLBACK 系列的 API 進行註冊

$FuncHandle = DllCallbackRegister ("myEnumWindowProc", "int", "hwnd;lparam") 
$FuncHandle = DllCallbackRegister ("自己寫好的參數名稱", "API 傳回形態", "API 參數1;API參數2;...")

 於是現在整段程式碼就變這樣

; 註冊自己的 CALLBACK 函式
$FuncHandle = DllCallbackRegister ("myEnumWindowProc", "int", "hwnd;lparam") 

; .....
; 自己的 CALLBACK 函式

Func myWindowProc($hWnd, $lParam)
     If WinGetTitle($hWnd) <> "" And BitAnd(WinGetState($hWnd), 2) Then
          $res = MsgBox(1, "", WinGetTitle($hWnd))
          If $res = 2 Then Return 0   ; 取消點選,返回 0 結束列出動作
    EndIf
    Return 1    ; 返回 1 繼續列出

EndFunc

 

// ---------------------------------------------------------------------
// 步驟三  使用 DllCall 呼叫 EnumWindows API

CALLBACK 函式都寫好、註冊完了,接下來總算可以叫 EnumWindows API 幫我們做事了。再看一次 EnumWindows 原型

BOOL WINAPI EnumWindows(
  __in  WNDENUMPROC lpEnumFunc,
  __in  LPARAM lParam
);

傳回 BOOL (autoit 用 it),參數一是我們剛剛寫好、註冊好的 $FundHandle,這裡要注意,之前有說過,只要是「指標」,我們的資料型態都用 "ptr",而變數一律要再調用 "DllCallbackGetPtr" 去取得該資料的位址(因為是指標,是傳送位址值),這裡也是一樣,只要是 CALLBACK ,就是函式指標,所以我們要傳的是 "一個函式的指標"。參數二直接用 lParam, 這個放什麼都沒差,直接塞一個垃圾也行。寫出來就像下面這樣

; 註冊自己的 CALLBACK 函式
$FuncHandle = DllCallbackRegister ("myEnumWindowProc", "int", "hwnd;lparam") 

; 向 Dll 呼叫 EnumWindows,CALLBACK 函式使用自己寫的
DllCall("user32.dll", "int", "EnumWindows", _
           "ptr", DllCallbackGetPtr($FuncHandle), "lparam", 0 )


; 自己的 CALLBACK 函式

Func myWindowProc($hWnd, $lParam)
     If WinGetTitle($hWnd) <> "" And BitAnd(WinGetState($hWnd), 2) Then
          $res = MsgBox(1, "", WinGetTitle($hWnd))
          If $res = 2 Then Return 0   ; 取消點選,返回 0 結束列出動作
    EndIf
    Return 1    ; 返回 1 繼續列出

EndFunc

// ---------------------------------------------------------------------
// 步驟四  最後再把已註冊的 CALLBACK 函式釋放掉

最後一個步驟是用 DllCallbackFree ,把我們剛剛註冊好的 $FuncHandle 釋放掉。整段完整原始碼如下 

#include <GUIConstantsEx.au3>

; 註冊自己的 CALLBACK 函式

$FuncHandle = DllCallbackRegister ("myEnumWindowProc", "int", "hwnd;lparam") 

; 向 Dll 呼叫 EnumWindows,CALLBACK 函式使用自己寫的
DllCall("user32.dll", "int", "EnumWindows", _
           "ptr", DllCallbackGetPtr($FuncHandle), "lparam", 0 )

; 釋放 callback 函數
DllCallbackFree($FuncHandle)


; 自己的 CALLBACK 函式
Func myWindowProc($hWnd, $lParam)
     If WinGetTitle($hWnd) <> "" And BitAnd(WinGetState($hWnd), 2) Then
          $res = MsgBox(1, "", WinGetTitle($hWnd))
          If $res = 2 Then Return 0   ; 取消點選,返回 0 結束列出動作
    EndIf
    Return 1    ; 返回 1 繼續列出

EndFunc

這部份比較煩雜,如果有問題的話,可以先去看前面幾篇文章,這篇再看個二、三次應該就可以了。

edisonx 發表在 痞客邦 PIXNET 留言(1) 人氣()


留言列表 (1)

發表留言
  • 求教!!
  • 抱歉,請問我該如何使用以下的編碼呢??主要的功能是,因為autoit好像只能用在window的視窗,如果是其他程式可能無法顯示該視窗的資訊(contorl ID),所以我找遍網路上的資料,只到以下這這段,但不知道如何使用,還麻煩大大指教一下:

    #include <Array.au3>
    #include <WinAPI.au3>
    $hWin = WinGetHandle("yourtitle")
    Var_GetAllWindowsControls($hWin)

    Func Var_GetAllWindowsControls($hCallersWindow)
    ;~ $giSubFunctionCounter += 1
    ; Get all list of controls
    $sClassList = WinGetClassList($hCallersWindow)

    ; Create array
    $aClassList = StringSplit($sClassList, @CRLF, 2)

    ; Sort array
    _ArraySort($aClassList)
    _ArrayDelete($aClassList, 0)

    ; Loop
    $iCurrentClass = ""
    $iCurrentCount = 1
    $iTotalCounter = 1

    For $i = 0 To UBound($aClassList) - 1
    If $aClassList[$i] = $iCurrentClass Then
    $iCurrentCount += 1
    Else
    $iCurrentClass = $aClassList[$i]
    $iCurrentCount = 1
    EndIf

    $hControl = ControlGetHandle($hCallersWindow, "", "[CLASSNN:" & $iCurrentClass & $iCurrentCount & "]")
    $text = StringRegExpReplace(ControlGetText($hCallersWindow, "", $hControl),"[\n\r]","{@CRLF}")
    $aPos = ControlGetPos($hCallersWindow, "", $hControl)
    $sControlID = _WinAPI_GetDlgCtrlID($hControl)

    If IsArray($aPos) Then
    ;~ If $gbPerformLog Then Var_SetLogAndActOnState ( 1, $gbLogLevel_Rtrn, "Var_GetAllWindowsControls()", "Func=[Var_GetAllWindowsControls]: ControlCounter=[" & $iTotalCounter & "] ControlID=[" & $sControlID & "] Handle=[" & $hControl & "] ClassNN=[" & $iCurrentClass & $iCurrentCount & "] XPos=[" & $aPos[0] & "] YPos=[" & $aPos[1] & "] Width=[" & $aPos[2] & "] Height=[" & $aPos[3] & "] Text=[" & $text & "].", False, False)
    ConsoleWrite("Func=[Var_GetAllWindowsControls]: ControlCounter=[" & $iTotalCounter & "] ControlID=[" & $sControlID & "] Handle=[" & $hControl & "] ClassNN=[" & $iCurrentClass & $iCurrentCount & "] XPos=[" & $aPos[0] & "] YPos=[" & $aPos[1] & "] Width=[" & $aPos[2] & "] Height=[" & $aPos[3] & "] Text=[" & $text & "]." & @crlf)
    Else
    ;~ If $gbPerformLog Then Var_SetLogAndActOnState ( 1, $gbLogLevel_Rtrn, "Var_GetAllWindowsControls()", "Func=[Var_GetAllWindowsControls]: ControlCounter=[" & $iTotalCounter & "] ControlID=[" & $sControlID & "] Handle=[" & $hControl & "] ClassNN=[" & $iCurrentClass & $iCurrentCount & "] XPos=[winclosed] YPos=[winclosed] Width=[winclosed] Height=[winclosed] Text=[" & $text & "].", False, False)
    EndIf
    If Not WinExists($hCallersWindow) Then ExitLoop
    $iTotalCounter+=1
    Next
    ;~ $giSubFunctionCounter -= 1
    EndFunc
    Edited 22 May 2013 by jdelaney

您尚未登入,將以訪客身份留言。亦可以上方服務帳號登入留言

請輸入暱稱 ( 最多顯示 6 個中文字元 )

請輸入標題 ( 最多顯示 9 個中文字元 )

請輸入內容 ( 最多 140 個中文字元 )

請輸入左方認證碼:

看不懂,換張圖

請輸入驗證碼