如何引用所有正在运行的Excel应用程序实例包括隐藏的和没有工作簿的的COM对象?

问题描述

我如何获得对每个正在运行的Excel应用程序实例的引用的完整列表(无论其工作簿数量和可见性状态如何)?


我知道我可以使用Windows API查找每个Excel工作簿窗口(其窗口类名称EXCEL7),获取其句柄以与AccessibleObjectFromWindow函数一起使用,然后调度并获取应用程序COM对象。

尽管仅适用于至少可见一个工作簿的Excel应用程序实例。我还如何获取隐藏的和/或没有工作簿的Excel应用程序实例?

Excel应用程序实例窗口(其窗口类名称XLMAIN)不会检索任何可访问的对象。

我正在寻找一种解释,无论是否使用伪代码或任何编程语言的代码,只要我自己能够理解并实现(使用Python)即可。

解决方法

我想用Python来实现这一点,尽管我问这个问题时并不需要答案是关于Python,因为一般的解释(不必专门针对编程语言)就足够了。

查看了win32com.client模块在​​Python中实现的GetObject VB函数的源代码之后,我注意到它调用了Moniker函数:

def Moniker(Pathname,clsctx = pythoncom.CLSCTX_ALL):
  """
    Python friendly version of GetObject's moniker functionality.
  """
  moniker,i,bindCtx = pythoncom.MkParseDisplayName(Pathname)
  dispatch = moniker.BindToObject(bindCtx,None,pythoncom.IID_IDispatch)
  return __WrapDispatch(dispatch,Pathname,clsctx=clsctx)

MkParseDisplayName函数将我引向objbase.h header's functions,在那里我发现了一个我不知道的GetRunningObjectTable函数。

一段时间后,搜索有关它的多个代码段,并尝试将它们组合在一起以执行我想要的操作而不会引发错误,并确保它仅获取Excel应用程序实例(我在代码中添加了Microsoft Word,以展示如何将其与其他COM对象一起使用),而无需重复,我将下面的代码放在一起。

from pythoncom import CreateBindCtx as create_bind_context,GetRunningObjectTable as get_running_object_table,IID_IDispatch as dispatch_interface_iid
from win32com.client import Dispatch as dispatch

running_object_table = get_running_object_table()
bind_context = create_bind_context()
excel_application_class_clsid = '{00024500-0000-0000-C000-000000000046}'
word_application_class_clsid = '{000209FF-0000-0000-C000-000000000046}'
excel_application_clsid = '{000208D5-0000-0000-C000-000000000046}'
word_application_clsid = '{00020970-0000-0000-C000-000000000046}'
excel_applications = []

for moniker in running_object_table:
  name = moniker.GetDisplayName(bind_context,None)
  if all(clsid not in name for clsid in [excel_application_class_clsid,word_application_class_clsid]):
    continue
  unknown_com_interface = running_object_table.GetObject(moniker)
  dispatch_interface = unknown_com_interface.QueryInterface(dispatch_interface_iid)
  dispatch_clsid = str(dispatch_interface.GetTypeInfo().GetTypeAttr().iid)
  if dispatch_clsid not in [excel_application_clsid,word_application_clsid]:
    continue
  com_object = dispatch(dispatch=dispatch_interface)
  excel_application = com_object.Application
  if id(excel_application) not in [id(excel_application) for excel_application in excel_applications]:
    excel_applications.append(excel_application)

input(excel_applications)

if检查是我发现要过滤掉我不想要的东西的方法,尽管我不确定这是否是一个好方法。

pywin32 package's documentation(包含win32com模块和documentation of the pythoncom module)对我有很大帮助,并且与Windows API documentation一起我对COM有了很多了解。 / p>

对于任何想用另一种编程语言进行操作的人来说,应该很容易看到上面的代码中使用了什么。以下是一些主要的帮助列表:GetRunningObjectTable functionCreateBindCtx functionIMoniker::GetDisplayName methodIRunningObjectTable::GetObject methodIUnknown::QueryInterface methodIDispatch::GetTypeInfo method和{{3} }。


特定于没有Word的Excel实例的功能:

from pythoncom import (
  CreateBindCtx         as create_bind_context_com_interface,IID_IDispatch         as dispatch_com_interface_iid,GetRunningObjectTable as get_running_object_table_com_interface,)
from win32com.client import (
  Dispatch as dispatch,)

def get_excel_instances():
  '''
  Returns a list of the running Microsoft Excel application
  instances as component object model (COM) objects.
  '''
  running_object_table_com_interface = get_running_object_table_com_interface()
  bind_context_com_interface = create_bind_context_com_interface()
  excel_application_class_clsid = '{00024500-0000-0000-C000-000000000046}'
  excel_application_clsid = '{000208D5-0000-0000-C000-000000000046}'
  excel_instance_com_objects = []
  for moniker_com_interface in running_object_table_com_interface:
    display_name = moniker_com_interface.GetDisplayName(bind_context_com_interface,None)
    if excel_application_class_clsid not in display_name:
      continue
    unknown_com_interface = running_object_table_com_interface.GetObject(moniker_com_interface)
    dispatch_com_interface = unknown_com_interface.QueryInterface(dispatch_com_interface_iid)
    dispatch_clsid = str(object=dispatch_com_interface.GetTypeInfo().GetTypeAttr().iid)
    if dispatch_clsid != excel_application_clsid:
      continue
    excel_instance_com_object = dispatch(dispatch=dispatch_com_interface)
    excel_instance_com_objects.append(excel_instance_com_object)
  return excel_instance_com_objects

excel_instances = get_excel_instances()
input()