如何迭代多个 Word 实例(使用 AccessibleObjectFromWindow)

How to iterate over multiple Word instances (with AccessibleObjectFromWindow)

我需要遍历所有 Word 实例,无论是否由用户、自动化、zumbis 等打开

我将描述到目前为止的所有步骤: 我看到并实施了我得到的解决方案 here;

       Do
            For Each objWordDocument In objWordApplication.Documents
               OpenDocs(iContadorDocs - 1) = objWordDocument.Name
               OpenDocs(iContadorDocs) = objWordDocument.path
               iContadorDocs = iContadorDocs + 2
               ReDim Preserve OpenDocs(iContadorDocs)
            Next objWordDocument
            iWordInstances = iWordInstances + 1
            objWordApplication.Quit False
            Set objWordApplication = Nothing
            Set objWordApplication = GetObject(, "Word.Application")
       Loop While Not objWordApplication Is Nothing

它有效,但是:

  1. 为了迭代所有单词实例,我们必须 GetObject 并关闭它,循环直到没有更多打开的实例,然后重新打开我关心的所有实例

    • 这需要很多时间 & R/W 周期 & 磁盘访问

    • 当然必须在 Word 之外完成,因为它可能会先关闭代码 运行ning 实例,或者在循环中间...

所以,在谷歌搜索之后,我看到了一些直接访问进程的示例,here and here for VB。

我设法获得了所有 Winword.exe 个实例的 PID,主要是对 VBForums:

处的代码进行了一些调整

仅显示修改后的代码:

   Do
        If LCase(VBA.Left$(uProcess.szExeFile, InStr(1, uProcess.szExeFile, Chr(0)) - 1)) = LCase(ProcessName) Then
            ProcessId = uProcess.th32ProcessID
            Debug.Print "Process name: " & ProcessName & "; Process ID: " & ProcessId
        End If
   Loop While ProcessNext(hSnapShot, uProcess)

对于上面的代码运行,我们需要PROCESSENTRY32结构,它包括进程名称(szExeFile)和进程ID字段(th32ProcessID);这个代码是@VBnet/Randy Birch

所以,现在我有了实例 PID 这个词;下一步是什么?

这样做之后,我尝试了解如何将这些 PID 实例传递给 GetObject 函数。

这时候我碰到了这个 Python , that opened my eyes to the AccessibleObjectFromWindow 从 windows 句柄创建对象。

我翻了很多地方,最有用的是这些here, here and here,可以得到这段代码:

Private 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
Private Declare Function IIDFromString Lib "ole32" _
        (ByVal lpsz As Long, ByRef lpiid As GUID) As Long
Private Declare Function AccessibleObjectFromWindow Lib "oleacc" _
        (ByVal hWnd As Long, ByVal dwId As Long, ByRef riid As GUID, _
         ByRef ppvObject As Object) As Long

Private Type GUID
    Data1 As Long
    Data2 As Integer
    Data3 As Integer
    Data4(7) As Byte
End Type
Private Const S_OK As Long = &H0
Private Const IID_IDispatch As String = "{00020400-0000-0000-C000-000000000046}"
Private Const OBJID_NATIVEOM As Long = &HFFFFFFF0

Sub testWord()
Dim i As Long
Dim hWinWord As Long
Dim wordApp As Object
Dim doc As Object
    'Below line is finding all my Word instances
    hWinWord = FindWindowEx(0&, 0&, "OpusApp", vbNullString)
    While hWinWord > 0
        i = i + 1
        '########Successful output
        Debug.Print "Instance_" & i; hWinWord
        '########Instance_1 2034768 
        '########Instance_2 3086118 
        '########Instance_3 595594 
        '########Instance_4 465560 
        '########Below is the problem
        If GetWordapp(hWinWord, wordApp) Then
            For Each doc In wordApp.documents
                Debug.Print , doc.Name
            Next
        End If
        hWinWord = FindWindowEx(0, hWinWord, "OpusApp", vbNullString)
    Wend
End Sub

Function GetWordapp(hWinWord As Long, wordApp As Object) As Boolean
Dim hWinDesk As Long, hWin7 As Long
Dim obj As Object
Dim iid As GUID

    Call IIDFromString(StrPtr(IID_IDispatch), iid)
    hWinDesk = FindWindowEx(hWinWord, 0&, "_WwF", vbNullString)
   '########Return 0 for majority of classes; only for _WwF it returns other than 0
    hWin7 = FindWindowEx(hWinDesk, 0&, "_WwB", vbNullString)
   '########Return 0 for majority of classes; only for _WwB it returns other than 0
    If AccessibleObjectFromWindow(hWin7, OBJID_NATIVEOM, iid, obj) = S_OK Then
   '########Return -2147467259 and does not get object...
        Set wordApp = obj.Application
        GetWordapp = True
    End If
End Function

错误在上面的代码中注释(########);但恢复时,我识别了所有实例,但无法检索对象。 对于 Excel,行:

hWinDesk = FindWindowEx(hWinXL, 0&, "XLDESK", vbNullString)
hWin7 = FindWindowEx(hWinDesk, 0&, "EXCEL7", vbNullString)

有效,因为我得到的不是零,而是 hWinDesk = 1511272 和 332558,在我得到 Excel 对象之后。

EXCEL7对应的WordWindowsclass是_WwG(不过上面给了0),XLMAIN对应的Wordclass名字是OpusApp。 Word对应的XLDESK是什么?

所以,我需要帮助才能发现它;或者您是否知道如何捕获 VBA 中的 COM 对象并知道它是 PID? MS 本身建议我查看 Office 200 docs;我会这样做,但如果有人以前这样做过...

事实上,现在我对这两种方法都感兴趣,当然,最后一种方法已经实现了 99%,所以,我更喜欢。

TIA

P.S。当然,在实现时,所有对象都会closed/nothing、错误处理等...

编辑 1: 根据@Comintern 的建议,这是 Spy++ 的输出:

有趣的是,我在Excel输出中只能定位到其中的两个字符串:XLMAIN和XLDESK,而在EXCEL7中根本找不到,Excel对象被成功捕获。对于Word,我测试了所有的字符串(_WwC,_WwO,),但只有

?FindWindowEx(hWinWord, 0&, "_WwF", vbNullString)
 1185896 
?FindWindowEx(hWinDesk, 0&, "_WwB", vbNullString)
 5707422 

按顺序得到一个句柄;但无济于事,因为

 ?AccessibleObjectFromWindow(hWin7, OBJID_NATIVEOM, iid, obj)
-2147467259 

有什么想法吗?方向?

在按照@Comintern 的建议更加熟悉 Spy++ 之后,我追踪到:

这是实际的 Window 订单; OpusApp 下面的 windows 都是它的 children

但要了解为什么它现在可以正常工作,我们必须右键单击下面的每个 _Ww[A_Z]:

对于_WwF:

为其 children _WwB:

终于达到目标了!!!! _WwG:

使用这种方法,显然我们必须在代码中添加另一层:

  Function GetWordapp(hWinWord As Long, wordApp As Object) As Boolean
        Dim hWinDesk As Long, hWin7 As Long, hFinalWindow As Long
        Dim obj As Object
        Dim iid As GUID

        Call IIDFromString(StrPtr(IID_IDispatch), iid)
        hWinDesk = FindWindowEx(hWinWord, 0&, "_WwF", vbNullString)
        hWin7 = FindWindowEx(hWinDesk, 0&, "_WwB", vbNullString)
        hFinalWindow = FindWindowEx(hWin7, 0&, "_WwG", vbNullString)
        If AccessibleObjectFromWindow(hFinalWindow, OBJID_NATIVEOM, iid, obj) = S_OK Then
            Set wordApp = obj.Application
            GetWordapp = True
        End If
    End Function

我不明白但现在不介意的是为什么 2 个不同实例的结果重复: Debug.print 结果:

   Instance_1 1972934 
                  x - fatores reumaticos.docx
                  FormGerenciadorCentralPacientes.docm
    Instance_2 11010524 
                  x - fatores reumaticos.docx
                  FormGerenciadorCentralPacientes.docm
    Instance_3 4857668 

但是为了解决这个问题,我将修改@PGS62 的marvel solution;正在恢复:

Private Function GetWordInstances() As Collection
    Dim AlreadyThere As Boolean
    Dim wd As Application
    Set GetWordInstances = New Collection
    ...code...
    For Each wd In GetWordInstances 
                If wd Is WordApp.Application Then
                    AlreadyThere = True
                    Exit For
                End If
            Next
            If Not AlreadyThere Then
                GetWordInstances.Add WordApp.Application
            End If
      ...code...
End Function

而且,瞧,大众所有 Word 实例的迭代,无需关闭和重新打开!!!

感谢社区,感谢其他线程中的所有想法,感谢@Comintern 的重要建议。

我可以验证你的代码。

这是关于 Word 的 windows 句柄的图表,它的句柄响应辅助功能界面查询(黄色,注释中显示 TypeName),哪个可以转换为 Word.Application(在浅绿色)

Original article is here (Disclaimer that's my blog)

你的问题让我开始寻找一个通用案例,因此在那篇博客中 post 可以找到也可以访问 PowerPoint 实例的代码(当然还有 Excel)。感谢挑战。