使用windows api函數捕獲SAP session的左下角消息句柄

  背景:SAP session的左下角消息很是有用,咱們在作SAP的自動化腳本時能夠設法讀到這個消息的內容,做爲程序後續動做的判斷條件。以下圖:數據庫

      

  好比小爬以前給財務的同事製做了一個批量導出SAP各種報表的腳本工具:基於公司IT團隊用ABAP編寫的這幾張表,SAP每次執行完導表動做,數據傳輸過程,左下角消息爲「Transferring package1 of 1...」,當表格數據完整導出後,則顯示「已傳輸N個字節」。咱們的腳本能夠去捕獲「已傳輸...」這個消息,來判斷報表內容是否已經完整導出,來決定是否要導出下一張報表。windows

  事實上,經過原生的「腳本錄製與回放」,咱們能夠獲得這個消息:語法爲:sapMessage=session.findById("wnd[0]/sbar").text。很是簡單實用!實際編寫腳本過程當中遇到的問題是,當咱們的腳本動態地順序往下執行到等待報表出來的過程,控制權交到了導出的excel文件,咱們的sapMessage=session.findById("wnd[0]/sbar").text沒法得到執行,若是該消息文本剛好做爲腳本中循環退出的條件,則腳本程序由於循環沒法退出,致使界面卡死。api

  我想到的解決方法是,前期的參數輸入等都經過腳本錄製功能生成代碼,到了點擊「執行(F8)」這個動做,改由window api sendMessage控制,那麼後續的控制權都在VB腳本下,咱們再經過window api來捕獲sap的窗口消息,而非sap原生提供的「session.findById("wnd[0]/sbar").text」方法。網絡

  咱們須要捕獲 sap session的窗口句柄,而後對其發送快捷鍵「F8」便可。在VBA中能夠這樣寫:session

Public Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal Hwnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Public Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Public Const WM_KEYUP = &H101 '釋放
Public Const WM_KEYDOWN = &H100 '按下
Public Const VK_F8 = &H77
button_F8 = FindWindowEx(0, 0, "SAP_FRONTEND_SESSION", vbNullString) ‘類名經過spy++捕獲
SendMessage button_F8, WM_KEYDOWN, VK_F8, 0
SendMessage button_F8, WM_KEYUP, VK_F8, 0

  這段代碼執行完後,程序就至關於給SAP發送了」F8「快捷鍵,執行導表。考慮到網絡、數據庫壓力等緣由,每次導表須要的時間並不肯定,咱們須要拿到報表已經完整導出的標記,而後關閉SAP自動打開的報表(若是每次SAP自動打開的excel報表不關閉,一次導出的報表數量過多時,電腦和程序都有可能卡死),方能執行下一次導表動做。函數

  如今重點來了,如何捕獲SAP左下角的消息」已傳輸N個字節「來做爲程序判斷的條件。工具

小爬一開始使用spy++來捕獲該消息句柄,如圖:oop

  惋惜每次執行SAP程序,該類名是動態變化的,因此基於類名來Findwindow顯然定位不到它。學習

小爬接着觀察spy++:測試

  它老是出現」SAP 輕鬆訪問「這個主session窗口句柄的第7個子窗口,因而咱們能夠循環使用7次FindwindowEX,就能找到該消息窗口的句柄msgHwnd,如:

Public Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal Hwnd1 As Long, ByVal hWnd2 As Long, ByVal lpsz1 As String, ByVal lpsz2 As String) As Long
Public Function mGetWindow(ByVal cName As String, ByRef fText As String) As Long
    Dim myStr As String * 100
    Dim strLen As String
    Dim bWnd As Long
    Dim bWndback As Long
    Dim a As Integer
    strLen = Len(myStr)
    mGetWindow = 0
    Do        
        '獲取子窗口標誌
        bWnd = FindWindowEx(0, bWndback, cName, vbNullString)
        'List1.AddItem bWnd
        If bWnd <> 0 Then
        '獲取子窗口標題
            GetWindowText bWnd, myStr, strLen
                myStr = Trim(myStr)            
            If InStrRev(myStr, fText) Then
                mGetWindow = bWnd
                Exit Do            
            Else
                Sleep 200
            End If          
        End If
     bWndback = bWnd
    Loop
End Function

    sessionHwnd = mGetWindow("SAP_FRONTEND_SESSION", "SAP 輕鬆訪問")
    '獲得左下角消息文本對應句柄,該句柄在SAP登陸後不會變化
    EnumChildWind = -1
    Do
    hwdChild = FindWindowEx(sessionHwnd, hwdChild, vbNullString, vbNullString) '水平滾動條的句柄
        EnumChildWind = EnumChildWind + 1
    Loop Until EnumChildWind = 6    'sap左下角消息控件位於主界面下的child索引號老是爲6(從0開始)
    msgHwnd = hwdChild

  小爬給同事用的腳本工具穩定地管了一年多。上個月財務月結,卻陸續有同事告訴我工具無法使用,動不大就卡死了。可這一年時間,公司的SAP版本(74.0)還有excel版本(excel2010)以及系統版本(win7 X64)都沒有發生變化。小爬思前想後,未果!

  近期,小爬捋了捋思路,仔細比對,終於發現其中端倪。原來是時間久了,不少同事用膩了原生的sap 主題和配色、字體等,而後選擇了其餘主題。您可能問了,主題變了,也會影響頁面元素?整個捕獲的邏輯不都是基於id、索引這些相對固定的東西定位的嗎?

  還真就巧了,通過SPY++探測窗口句柄發現,這個消息句柄在改動主題後的sap內,是位於sap session的第6個子窗口,而非先前的第7個。

  小爬本想編寫嚴格的操做手冊,讓用戶改回默認的SAP主題和字體,去解決此問題。可是考慮到每一個用戶審美不一樣,且長時間只用一個主題確實會審美疲勞進而影響工做效率。小爬決定仍是克難攻堅,使該腳本能兼容不一樣的SAP主題。

  仔細觀察SPY++,知道「SAP 輕鬆訪問」的窗口句柄後,定位到「Afx Wnd110」類名的窗口,順序FindwindowEx,當類名再也不是「Afx Wnd110"後的第二個子窗口,就是咱們要找的」sap左下角消息窗口「,這個邏輯在不一樣SAP主題下恆成立。代碼層面該如何實現呢?

 

  想要基於類名判斷,那如何獲得窗口的類名呢?這就須要涉及GetClassName 這個api函數了:

Public Declare Function GetClassName Lib "user32" Alias "GetClassNameA" (ByVal hwnd As Long, ByVal lpClassName As String, ByVal nMaxCount As Integer) As Integer
Public Declare Function GetWindow Lib "user32" (ByVal hwnd As Long, ByVal wCmd As Long) As Long
Public Const GW_HWNDNEXT = &H2 '尋找下一個具備相同關係的窗口,或尋找下一個具備相同關係的頂級窗口
Public sessionHwnd As Long, hwdChild As Long, msgHwnd As Long
sessionHwnd = mGetWindow("SAP_FRONTEND_SESSION", "SAP 輕鬆訪問")

    '獲得左下角消息文本對應句柄,該句柄在SAP登陸後不會變化

    hwdChild = FindWindowEx(sessionHwnd, hwdChild, vbNullString, vbNullString) '獲得第一個子窗口句柄
    Do
        hwdChild = FindWindowEx(sessionHwnd, hwdChild, vbNullString, vbNullString) '水平滾動條的句柄
         '得到實際的類名字符長度
        iLEN = GetClassName(hwdChild, sCN, 256)
         '提取實際的類名
        sCN = Left(sCN, iLEN)
    Loop Until sCN <> "AfxWnd110"    '從上往下,最後一個類名爲AfxWnd110的窗口往下兩個窗口就是咱們要找的sap消息窗口
    msgHwnd = GetWindow(hwdChild, GW_HWNDNEXT)  '獲得sap消息窗口的句柄,全局變量

  上面的代碼中,GetWindow函數第二個參數GW_HWNDNEXT 表明:尋找下一個具備相同關係的窗口,或尋找下一個具備相同關係的頂級窗口。

經過上面的代碼,咱們終於獲得了SAP的左下角消息句柄,緊接着,咱們的程序在導表過程當中等待時長就能夠這樣判斷:

   Do While InStr(msg01, "") = 0
        SendMessage msgHwnd, WM_GETTEXT, 128, ByVal msg01
        Sleep 500
    Loop

  之因此用Instr函數,是由於」已傳輸N個字節「的N值每次不一樣,須要模糊匹配字符串。腳本每隔500毫秒就取出msgHwnd的文本,與預設的」已傳輸..."做模糊匹配,而代碼中的""則是由於win7下sap的字符集解碼問題,咱們能夠不用解決字符集的解碼,只需在腳本的DO While判斷條件中將錯就錯,便可。

  通過一番測試和驗證,終於解決了該自動化腳本針對SAP不一樣主題的兼容性問題,大功告成!無數次的經驗告訴咱們,只會用sap元素的腳本錄製與回放功能,對於自動化工做是遠遠不夠的,不少時候,咱們要把它和windows底層的user32 api 函數結合來使用,才能解決大多數實際問題。上面的代碼只是解決問題的其中一個思路,供借鑑,一塊兒學習!

相關文章
相關標籤/搜索