如何迭代多个 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
它有效,但是:
为了迭代所有单词实例,我们必须 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)。感谢挑战。
我需要遍历所有 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
它有效,但是:
为了迭代所有单词实例,我们必须 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
我翻了很多地方,最有用的是这些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)。感谢挑战。