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


  背景:SAP session的左下角消息非常有用,我們在做SAP的自動化腳本時可以設法讀到這個消息的內容,作為程序后續動作的判斷條件。如下圖:

      

  比如小爬之前給財務的同事制作了一個批量導出SAP各類報表的腳本工具:基於公司IT團隊用ABAP編寫的這幾張表,SAP每次執行完導表動作,數據傳輸過程,左下角消息為“Transferring package1 of 1...”,當表格數據完整導出后,則顯示“已傳輸N個字節”。我們的腳本可以去捕獲“已傳輸...”這個消息,來判斷報表內容是否已經完整導出,來決定是否要導出下一張報表。

  事實上,通過原生的“腳本錄制與回放”,我們可以得到這個消息:語法為:sapMessage=session.findById("wnd[0]/sbar").text。非常簡單實用!實際編寫腳本過程中遇到的問題是,當我們的腳本動態地順序往下執行到等待報表出來的過程,控制權交到了導出的excel文件,我們的sapMessage=session.findById("wnd[0]/sbar").text無法獲得執行,如果該消息文本恰好作為腳本中循環退出的條件,則腳本程序因為循環無法退出,導致界面卡死。

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

  我們需要捕獲 sap session的窗口句柄,然后對其發送快捷鍵“F8”即可。在VBA中可以這樣寫:

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++來捕獲該消息句柄,如圖:

  可惜每次執行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 函數結合來使用,才能解決大多數實際問題。上面的代碼只是解決問題的其中一個思路,供借鑒,一起學習!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM