VBGood网站全文搜索 Google

搜索VBGood全站网页(全文搜索)

VB爱好者乐园(VBGood)

 找回密码
 立即注册
搜索
查看: 17333|回复: 23

[原创] 最近关于MSAA的研究汇总-以提取QQ消息为例

[复制链接]
发表于 2016-1-8 15:55:56 | 显示全部楼层 |阅读模式
本帖最后由 sky定格 于 2016-1-8 16:05 编辑

最近看朋友用c#实现的提取QQ聊天窗口里的消息的提取,大为惊讶(我以前也看过那个.用spy++看就一个句柄弄不到里面的消息)
问了他说是用的UI Automation实现的,然后我上网查vb相关调用方法.没找到相关资料倒是误打误撞的看到了一些vb.net 关于msaa的案例(我到现在还没搞清楚他们有什么区别...)
以下是百度的解释:
MSAA的全称是Microsoft Active Accessibility。这是类似DCOM技术。技术模型是这样的,UI程序可以暴露出一个Interface,方便另一个程序对其进行控制。 MSAA技术的初衷是为了方便残疾人使用Windows 程序。比如盲人看不到窗口,但是盲人可以通过一个USB读屏器连接到电脑上, 读屏器通过UI程序暴露出来的这个Interface,就可以获取程序信息,通过盲文或者其它形式传递给盲人。MSAA提供了如此方便的功能, UI自动化测试自然可以借用这项技术。
言归正传 我们先来用工具了解下这东西   个人感觉是很强大的 桌面上有几个文件 文件名什么的 是不是选中的是不是隐藏的这些都能看到取到
用到的工具是inspect.exe   ACCexplorer2.0  两个都可以 都是微软的 个人喜欢后者虽然功能没前面的一个多奈何他有汉化版英语差没办法
使用上其实感觉和spy++差不多

选中QQ聊天界面 就可以看到聊天消息全在里面了.
我们用到的dll是oleacc.dll系统自带的.里面大概有十来个函数吧 用函数查看器看的. 我目前就用了两个其他的都没用过也就没研究具体作用了
这两个我感觉也是比较实用的两个  AccessibleObjectFromWindow  和   AccessibleChildren 他们的作用我大概说下 都是自己的理解说错勿喷
AccessibleObjectFromWindow  
声明如下:
Private Declare Function AccessibleObjectFromWindow Lib "oleacc" (ByVal hwnd As Long, ByVal dwId As Long, riid As Type_GUID, ppvObject As Object) As Long
功能是  根据句柄取得这个句柄的一个IAccessible对象里面包含了这个句柄的所有ui元素
hwnd   输入    句柄  特别注意,AccessibleObjectFromWindow只能返回Hwnd句柄指向窗口的顶级窗口
dwId   输入     &HFFFFFFFC (一个常量)
riid    输入   Guid("618736E0-3C3D-11CF-810C-00AA00389B71")
vb6里写成
Private Type Type_GUID
    Data1          As Long
    data2          As Integer
    Data3          As Integer
    Data4(0 To 7) As Byte
End Type
Dim rd As Type_GUID
    With rd
        .Data1 = &H618736E0
        .data2 = &H3C3D
        .Data3 = &H11CF
        .Data4(0) = &H81
        .Data4(1) = &HC
        .Data4(2) = &H0
        .Data4(3) = &HAA
        .Data4(4) = &H0
        .Data4(5) = &H38
        .Data4(6) = &H9B
        .Data4(7) = &H71
    End With
ppvObject  输出   查找到的对象保存在这里面
AccessibleObjectFromWindow如果获得了正确的Com对象,则返回值为0。如果返回值不为0,则说明获得Com对象过程中出错。

第二个AccessibleChildren
Private Declare Function AccessibleChildren Lib "oleacc" (ByVal paccContainer As IAccessible, ByVal iChildStart As Long, ByVal cChildren As Long, rgvarChildren As Variant, pcObtained As Long) As Long
功能是取刚才取到的对象的子对象(这个从工具上就可以看出 他和句柄那个差不多都是一个父子关系这样的  层层进入就能取到想要的消息了)
paccContainer  输入 父对象
iChildStart  输入   从哪个个开始(都写0 就对了全取了)
cChildren   输入   使用  父对象.accChildCount  取得父对象包含子对象的个数
rgvarChildren  输出  返回父对象的所有子对象(是一个变体形式的数组返回两种状态)  下标从0开始
pcObtained  输出    返回子对象个数


然后说下IAccessible 对象的常用方法   accParent  取父对象   accChildCount取子对象个数   accState  accValue  accName  accRole  分别对应 工具上的 状态 值 名称 以及窗口类型
accName  和accValue   就不细说基本上取的就是他们两个的值
accState  accRole  我经常会用来做判断是不是自己需要的那个子对象的依据具体指代表含义可以用inspect工具查看 是一个十六进制的整数  这里贴出百度到的别人代码里的role
{1: u'TitleBar', 2: u'MenuBar', 3: u'ScrollBar', 4: u'Grip', 5: u'Sound', 6: u'Cursor', 7: u'Caret', 8: u'Alert', 9: u'Window', 10: u'Client', 11: u'PopupMenu', 12: u'MenuItem', 13: u'Tooltip', 14: u'Application', 15: u'Document', 16: u'Pane', 17: u'Chart', 18: u'Dialog', 19: u'Border', 20: u'Grouping', 21: u'Separator', 22: u'ToolBar', 23: u'StatusBar', 24: u'Table', 25: u'ColumnHeader', 26: u'RowHeader', 27: u'Column', 28: u'Row', 29: u'Cell', 30: u'Link', 31: u'HelpBalloon', 32: u'Character', 33: u'List', 34: u'ListItem', 35: u'Outline', 36: u'OutlineItem', 37: u'PageTab', 38: u'PropertyPage', 39: u'Indicator', 40: u'Graphic', 41: u'Text', 42: u'EditableText', 43: u'PushButton', 44: u'CheckBox', 45: u'RadioButton', 46: u'ComboBox', 47: u'DropDown', 48: u'ProgressBar', 49: u'Dial', 50: u'HotKeyField', 51: u'Slider', 52: u'SpinBox', 53: u'Diagram', 54: u'Animation', 55: u'Equation', 56: u'DropDownButton', 57: u'MenuButton', 58: u'GridDropDownButton', 59: u'WhiteSpace', 60: u'PageTabList', 61: u'Clock', 62: u'SplitButton', 63: u'IPAddress'}
accState 的话正常是0 其他的各种状态工具看以看到值
刚刚说了AccessibleChildren  返回的子对象 有两种类型 比如
  1. lRet = AccessibleObjectFromWindow(hwnd , &HFFFFFFFC, rd, udtIA)'取hwnd的IAccessible对象
  2. lCount = udtIA.accChildCount'取子对象个数
  3. lRet = AccessibleChildren(udtIA, 0, lCount, udtChildIA(0), lNewCount)
  4.       For i = 0 To lNewCount - 1'循环取到的子对象
  5.           If (Not udtChildIA) <> True Then'排除掉没有子对象的情况
  6.             If IsObject(udtChildIA(i)) = True Then'刚才说了子对象有两种返回类型一个是直接的IAccessible对象 一个是id号  我们先处理直接返回的
  7. Set tmpLoad = udtChildIA(i)
  8. debug.print tmpLoad.accName(&H0&)'打印子对象名称  accvalue(&H0&) accrole(&H0&)... 同理
  9. else'下面是id序号的
  10. debug.print udtIA.accName(udtChildIA(i))'其他同理
  11. endif
  12. endif
  13. next i
  14. '
复制代码
这个手打的代码有点乱
然后还有个.accDoDefaultAction   我没用过说是好像可以完成默认动作的 点击什么的可以吧 还有一些选中什么的  在微软官网搜IAccessible 能看到所有的方法的不过没多少解释
..写到这感觉基本上都能写出来了..觉得还是不上代码了  不然都是代码扒拉走就完事也没人去继续研究了.附上一个厉遍所有节点查找指定类型 状态的子对象好了  微软官网扒拉的没有处理id不为0的情况 不过QQ消息是能厉遍出来的.(个人觉得还是吧所有节点位置写出来速度快点 大约10多毫秒不过要做兼容xp 7  节点不一样 厉遍的话好像是300ms)

Public Declare Function SafeArrayGetDim Lib "oleaut32.dll" (ByRef saArray() As Any) As Long
Private Declare Function AccessibleObjectFromWindow Lib "oleacc" (ByVal hwnd As Long, ByVal dwId As Long, riid As Type_GUID, ppvObject As Object) As Long
Private Declare Function AccessibleChildren Lib "oleacc" (ByVal paccContainer As IAccessible, ByVal iChildStart As Long, ByVal cChildren As Long, rgvarChildren As Variant, pcObtained As Long) As Long
Private Const CHILDID_SELF As Long = &H0&
Private Const OBJID_CLIENT As Long = &HFFFFFFFC
Private Type Type_GUID
    Data1          As Long
    data2          As Integer
    Data3          As Integer
    Data4(0 To 7) As Byte
End Type
Dim QQtxt As String '临时存储
Type QQmsg
    myname As String '昵称
    mynum As String 'QQ号
    mydate As Date '时间
    txt As String
    myzhu As String
End Type
Public shuaMSBB As Boolean '判断刷新消息函数是否允许完毕
Public rsmsg As New ADODB.Recordset, r As New ADODB.Recordset

Public Const STATE_SYSTEM_UNAVAILABLE As Long = &H1&
Public Const STATE_SYSTEM_INVISIBLE As Long = &H8000&
Public Const STATE_SYSTEM_SELECTED As Long = &H2&
Public Enum RoleNumber'定义了一些常见的类型常量
    ROLE_SYSTEM_CLIENT = &HA&
    ROLE_SYSTEM_PANE = &H10&
    ROLE_SYSTEM_GROUPING = &H14&
    ROLE_SYSTEM_TOOLBAR = &H16&
    ROLE_SYSTEM_PAGETAB = &H25&
    ROLE_SYSTEM_PROPERTYPAGE = &H26&
    ROLE_SYSTEM_GRAPHIC = &H28&
    ROLE_SYSTEM_STATICTEXT = &H29&
    ROLE_SYSTEM_Text = &H2A&
    ROLE_SYSTEM_PAGETABLIST = &H3C&
    ssss = &H22&
End Enum
Private Enum NavigationDirection
    NAVDIR_FIRSTCHILD = &H7&
End Enum


Private Function AccOBJ(ByVal HD As Long) As IAccessible '句柄取对象
    'On Error GoTo ToExit '打开错误陷阱
    '------------------------------------------------
    Dim udtIA       As IAccessible
    Dim tg           As Type_GUID
    Dim lRet    As Long
    Dim child() As Variant
    With tg
        .Data1 = &H618736E0
        .data2 = &H3C3D
        .Data3 = &H11CF
        .Data4(0) = &H81
        .Data4(1) = &HC
        .Data4(2) = &H0
        .Data4(3) = &HAA
        .Data4(4) = &H0
        .Data4(5) = &H38
        .Data4(6) = &H9B
        .Data4(7) = &H71
    End With
    lRet = AccessibleObjectFromWindow(HD, OBJID_CLIENT, tg, AccOBJ) '拿到接口
End Function

Public Function GetAccessible(Element As IAccessible, RoleWanted As Long, NameWanted As String, Optional GetClient As Boolean) As IAccessible '厉遍查找 (对象,类型,名称,最后个参数我也没搞懂什么意思)
    Dim ChildrenArray() As Variant
    Dim child As IAccessible
    Dim ndxChild As Long
    Dim ReturnElement As IAccessible
    If Element.accRole(CHILDID_SELF) = RoleWanted And Element.accname(CHILDID_SELF) = NameWanted Then '筛选出name  role 相同的对象
        Set ReturnElement = Element
    ElseIf Element.accState(CHILDID_SELF) = &H0& Then '过滤掉隐藏窗口
        ChildrenArray = GetChildren(Element) '取子对象
        If (Not ChildrenArray) <> True Then
            For ndxChild = LBound(ChildrenArray) To UBound(ChildrenArray)
                If TypeOf ChildrenArray(ndxChild) Is IAccessible Then '判断子对象是不是返回的一个IAccessible对象
                    Set child = ChildrenArray(ndxChild)
                    Set ReturnElement = GetAccessible(child, RoleWanted, NameWanted)
                    If Not ReturnElement Is Nothing Then Exit For
                End If  'Child is IAccessible
                DoEvents
            Next ndxChild
        End If ' there are children
    End If ' still looking
    If GetClient Then
        'Set ReturnElement = ReturnElement.accNavigate(NAVDIR_FIRSTCHILD, CHILDID_SELF)
    End If
    Set GetAccessible = ReturnElement
End Function
Private Function GetChildren(Element As IAccessible) As Variant() '取子对象
    On Error GoTo ToExit '打开错误陷阱
    '------------------------------------------------
    Const FirstChild As Long = 0&
    Dim NumChildren As Long
    Dim NumReturned As Long
    Dim ChildrenArray() As Variant
    If Not Element Is Nothing Then
        NumChildren = Element.accChildCount
        If NumChildren > 0 Then
            ReDim ChildrenArray(NumChildren - 1)
            AccessibleChildren Element, FirstChild, NumChildren, ChildrenArray(0), NumReturned '浏览关系树(接口对象,开始,子对象保存地址,可用子对象总数)
        End If
        GetChildren = ChildrenArray
    End If
    '------------------------------------------------
    Exit Function
    '----------------
ToExit:
    Debug.Print "错误发生时间:"; Format(Now, "YYYY-MM-DD HH:MM:SS")
    Debug.Print "错误 的 类型:"; err.Number
    Debug.Print "错误 的 信息:"; err.Description
    Debug.Print "错误函数名称:GetChildren"
    Debug.Print "错误模块名称:API"
End Function
上张刚做好的 取消息和刷新列表的图


点评

好帖子没人赞一下么~~  发表于 2016-1-8 18:53

评分

参与人数 7威望 +62 人气 +15 收起 理由
自动化软件开发 + 1 很给力
sq03 + 5 + 1 很给力
mr009 + 5 + 1 感谢分享! 能有个示例就更好了
acme_pjz + 10 + 3 精品文章
JuncoJet + 16 + 3 很给力
Jen + 13 + 3 水平不错!
startbin321 + 13 + 3 很给力

查看全部评分

本帖被以下淘专辑推荐:

发表于 2016-1-8 18:58:02 | 显示全部楼层
能传个例子就更好了,顶下
回复 支持 反对

使用道具 举报

发表于 2016-1-8 19:46:27 | 显示全部楼层
原来QQ窗口竟然是有句柄的,一直以为是纯GDI实现的说,控件都是画上去的。
回复 支持 反对

使用道具 举报

发表于 2016-1-8 20:48:11 | 显示全部楼层
终于有更新了
回复 支持 反对

使用道具 举报

发表于 2016-1-10 00:57:40 | 显示全部楼层
楼主,效果不错啊。细读了一下,使用 MSAA 真是不错的。


http://www.cnblogs.com/lichmama/p/3824888.html
上篇写到了获取IE8浏览器URL的一般方法,那这篇就写下chrome的URL怎么获取。事实上,早期的chrome版本可以通过跟IE8差不多方式获取到URL信息。但是,现在chrome的控件都是DirectUI画出来的,所有就没有一般意义上hwnd可以取。网上搜索了下,大多数都倾向于使用MSAA(Microsoft Active Accessibility)
回复 支持 反对

使用道具 举报

发表于 2016-1-10 01:28:54 | 显示全部楼层
貌似可以做一个远程强开视频的程序牢。加上word宏病毒或者0day技术,妥妥的。
回复 支持 反对

使用道具 举报

发表于 2016-1-10 02:59:55 | 显示全部楼层
accqq.gif

点评

请教: 我获取qq聊天窗口消息记录, 用 AccessibleChildren(objAcc, 0&, kidscount, kids(0), realcount) 获得子节点, 等到 objAcc.accName(CHILDID_SELF) = "消息" 时, kids(0) 的类型 是一个lo   发表于 2020-1-24 14:34
不明觉厉  发表于 2016-1-15 18:47

评分

参与人数 1威望 +1 人气 +1 收起 理由
sky定格 + 1 + 1 很给力

查看全部评分

回复 支持 反对

使用道具 举报

发表于 2016-1-10 03:03:16 | 显示全部楼层
还发现个问题 窗口好像最小化的时候,UI会被释放掉,包括窗口标题也会消失的说。
回复 支持 反对

使用道具 举报

发表于 2016-1-12 00:33:36 | 显示全部楼层
很强啊,得QQ消息的,留个记号先.
回复 支持 反对

使用道具 举报

 楼主| 发表于 2016-1-22 10:35:57 | 显示全部楼层
startbin321 发表于 2016-1-8 18:58
能传个例子就更好了,顶下

这有几个网站你可以看看

https://social.msdn.microsoft.com/Forums/zh-CN/62938e12-5cad-4de7-92e9-00314813d31a/publish-date-property-in-word?forum=worddev
https://social.msdn.microsoft.com/Forums/zh-CN/53e564f6-2957-4e34-b5f6-5385e7538e77/working-example-of-getchartelement-in-powerpoint-2007-vba?forum=officegeneral
https://social.msdn.microsoft.com/Search/zh-CN?query=AccessibleChildren&pgArea=header&emptyWatermark=true&ac=4
https://msdn.microsoft.com/en-us/library/dd317977(v=vs.85).aspx函数表
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

文字版|手机版|小黑屋|VBGood  

GMT+8, 2022-7-4 23:34

VB爱好者乐园(VBGood)
快速回复 返回顶部 返回列表