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

How to reference the COM objects of all the running Excel application instances, including hidden and without workbooks?

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


我知道我可以使用 Windows API 来查找每个 Excel 工作簿 window(其中包含 window class name EXCEL7), 获取它们的句柄以与 AccessibleObjectFromWindow function, then dispatch and get the application COM 对象一起使用。

尽管这仅适用于至少有一个工作簿可见的 Excel 应用程序实例。我怎样才能同时获得隐藏的 Excel 应用程序实例 and/or 没有工作簿?

Excel 应用程序实例 window(具有 window class 名称 XLMAIN)未检索到任何可访问对象。

我正在寻找一个解释,有或没有伪代码,或任何编程语言的代码,只要我能够自己理解和实现它(在Python)。

我想在 Python 中实现这个,尽管我问了这个问题而不要求答案是关于 Python 因为一般的解释(不必特定于编程语言)会可能够了。

查看 GetObject VB 函数的源代码后 win32com.client 模块在 Python 中实现,我注意到它调用了 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 function lead me to the objbase.h header's functions where I found the 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 (which contains the win32com module as well as the documentation of the pythoncom module) has helped me a lot and together with the Windows API documentation 我对 COM 的了解更多了。

对于任何想用另一种编程语言执行此操作的人来说,查看上面代码中使用的内容应该很简单。以下是可以提供帮助的主要事项列表:GetRunningObjectTable function, CreateBindCtx function, IMoniker::GetDisplayName method, IRunningObjectTable::GetObject method, IUnknown::QueryInterface method, IDispatch::GetTypeInfo method and ITypeInfo::GetTypeAttr method.


针对没有 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()