背景: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(, , "SAP_FRONTEND_SESSION", vbNullString) ‘类名通过spy++捕获
SendMessage button_F8, WM_KEYDOWN, VK_F8,
SendMessage button_F8, WM_KEYUP, VK_F8,

  这段代码执行完后,程序就相当于给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 *
Dim strLen As String
Dim bWnd As Long
Dim bWndback As Long
Dim a As Integer
strLen = Len(myStr)
mGetWindow =
Do
'获取子窗口标志
bWnd = FindWindowEx(, bWndback, cName, vbNullString)
'List1.AddItem bWnd
If bWnd <> Then
'获取子窗口标题
GetWindowText bWnd, myStr, strLen
myStr = Trim(myStr)
If InStrRev(myStr, fText) Then
mGetWindow = bWnd
Exit Do
Else
Sleep
End If
End If
bWndback = bWnd
Loop
End Function sessionHwnd = mGetWindow("SAP_FRONTEND_SESSION", "SAP 轻松访问")
'得到左下角消息文本对应句柄,该句柄在SAP登录后不会变化
EnumChildWind = -
Do
hwdChild = FindWindowEx(sessionHwnd, hwdChild, vbNullString, vbNullString) '水平滚动条的句柄
EnumChildWind = EnumChildWind +
Loop Until EnumChildWind = '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, )
'提取实际的类名
sCN = Left(sCN, iLEN)
Loop Until sCN <> "AfxWnd110" '从上往下,最后一个类名为AfxWnd110的窗口往下两个窗口就是我们要找的sap消息窗口
msgHwnd = GetWindow(hwdChild, GW_HWNDNEXT) '得到sap消息窗口的句柄,全局变量

  上面的代码中,GetWindow函数第二个参数GW_HWNDNEXT 代表:寻找下一个具有相同关系的窗口,或寻找下一个具有相同关系的顶级窗口。

通过上面的代码,我们终于得到了SAP的左下角消息句柄,紧接着,我们的程序在导表过程中等待时长就可以这样判断:

   Do While InStr(msg01, "宸") =
SendMessage msgHwnd, WM_GETTEXT, , ByVal msg01
Sleep
Loop

  之所以用Instr函数,是因为”已传输N个字节“的N值每次不同,需要模糊匹配字符串。脚本每隔500毫秒就取出msgHwnd的文本,与预设的”已传输..."作模糊匹配,而代码中的""则是因为win7下sap的字符集解码问题,我们可以不用解决字符集的解码,只需在脚本的DO While判断条件中将错就错,即可。

  经过一番测试和验证,终于解决了该自动化脚本针对SAP不同主题的兼容性问题,大功告成!无数次的经验告诉我们,只会用sap元素的脚本录制与回放功能,对于自动化工作是远远不够的,很多时候,我们要把它和windows底层的user32 api 函数结合来使用,才能解决大多数实际问题。上面的代码只是解决问题的其中一个思路,供借鉴,一起学习!

使用windows api函数捕获SAP session的左下角消息句柄的更多相关文章

  1. Windows API 函数列表 附帮助手册

    所有Windows API函数列表,为了方便查询,也为了大家查找,所以整理一下贡献出来了. 帮助手册:700多个Windows API的函数手册 免费下载 API之网络函数 API之消息函数 API之 ...

  2. C#调用Windows API函数截图

    界面如下: 下面放了一个PictureBox 首先是声明函数: //这里是调用 Windows API函数来进行截图 //首先导入库文件 [System.Runtime.InteropServices ...

  3. Windows API函数大全(完整)

    Windows API函数大全,从事软件开发的朋友可以参考下 1. API之网络函数 WNetAddConnection 创建同一个网络资源的永久性连接 WNetAddConnection2 创建同一 ...

  4. [windows菜鸟]Windows API函数大全(完整)

    Windows API函数大全,从事软件开发的朋友可以参考下 1. API之网络函数 WNetAddConnection 创建同一个网络资源的永久性连接 WNetAddConnection2 创建同一 ...

  5. 用Windows API函数(CreateFile/ReadFile/WriteFile/CloseHandle)完成文件拷贝程序(初级版)

    文件拷贝程序 程序类型:Console 参数:源文件名   目的文件名 要求:1.只能使用Windows API函数(CreateFile/ReadFile/WriteFile/CloseHandle ...

  6. VBS调用Windows API函数

    Demon's Blog 忘记了,喜欢一个人的感觉 Demon's Blog  »  程序设计  »  VBS调用Windows API函数 « 用VBS修改Windows用户密码 在VB中创建和使用 ...

  7. Python调用Windows API函数编写录音机和音乐播放器

    功能描述: 1)使用tkinter设计程序界面: 2)调用Windows API函数实现录音机和音乐播放器. . 参考代码: ​ 运行界面: ​

  8. WINDOWS API 函数(超长,值得学习)

    一.隐藏和显示光标 函数: int ShowCursor ( BOOL bShow );  参数 bshow,为布尔型,bShow的值为False时隐藏光标,为True时显示光标:该函数的返回值为整型 ...

  9. Windows API函数大全(精心总结)

    WindowsAPI函数大全(精心总结)    目录 1. API之网络函数... 1 2. API之消息函数... 1 3. API之文件处理函数... 2 4. API之打印函数... 5 5. ...

随机推荐

  1. XCode4 App Store提交小结

    本文建立在你的应用程序已开发完成的基础上 本文以理清流程为主 本文的内容以Distribution为准,但是所附的参考资料也有对Ad Hoc的说明 三种证书(Development.Distribut ...

  2. wepy —— 组件之间通信

    一.props 1.静态传值 —— 父组件向子组件传递常量数据 // 父组件 <coma fruitName="橘子"></coma> // 子组件 // ...

  3. 安装pip3遇到:E: Unmet dependencies. Try 'apt-get -f install' with no packages (or specify a solution).

    安装pip3遇到:E: Unmet dependencies. Try 'apt-get -f install' with no packages (or specify a solution). 具 ...

  4. 在SAE上使用Flask插件

    因为我之前学习的时候使用的是虚拟环境,下载的所有需要用到的插件都在flask这个文件夹里面,SAE上Flask的版本和我本地用的版本对不上,导致有时候import都不对,于是我就把本地的环境直接放到S ...

  5. HTML静态网页--表单验证和事件

    1.表单验证<form></form> (1).非空验证(去空格) (2).对比验证(跟一个值对比) (3).范围验证(根据一个范围进行判断) (4).固定格式验证:电话号码, ...

  6. 2019-9-9-dotnet-获取本机-IP-地址方法

    title author date CreateTime categories dotnet 获取本机 IP 地址方法 lindexi 2019-09-09 15:56:33 +0800 2019-0 ...

  7. yii框架不输出头文件和尾文件

    控制器: public function actionCat(){ return $this->renderPartial('cat');} 在进行页面输出渲染的时候. 1.render 输出父 ...

  8. axis2 wsdl2java工具

    wsdl2java工具使用方法描述: C:\Users\Administrator>wsdl2java -h Using AXIS2_HOME: E:\Apache_Projects\axis2 ...

  9. Codeforces Beta Round #4 (Div. 2 Only) D. Mysterious Present(LIS)

    传送门 题意: 现在我们有 n 个信封,然后我们有一张卡片,并且我们知道这张卡片的长和宽. 现给出这 n 个信封的长和宽,我们想形成一个链,这条链的长度就是这条链中所含有的信封的数量: 但是需要满足① ...

  10. php 上传文件并对上传的文件进行简单验证(错误信息,格式(防伪装),大小,是否为http上传)

    <body> <?php /** *验证错误 *如果有错,就返回错误,如果没错,就返回null */ function check($file) { //1:验证是否有误 if($f ...