附加到现有 Excel 实例,即使 VBA 由 Excel 实例打开

Attach To Existing Excel Instance even if VBA is Open by the Excel Instance

这是关于为这个问题选择的答案:
How to iterate through instance of Excel c#

此代码非常适合获取 Excel 的所有实例,除非其中一个实例打开了 VBA 编辑器。代码在尝试为 EXCEL7 查找 class 时中断。这是工作簿 subwindow 的 class。调试时我确定在枚举子windows时,找不到EXCEL7子window。 类 喜欢 VbaWindow 而不是出现。我什至试图为这个 vba window class 获取带有 window 句柄的 Excel.Window 但它失败了。我怎样才能仍然获得方法 AccessibleObjectFromWindow 来引用 Excel.Window,然后我可以使用它来引用应用程序。这是我修改后的方法(我已经有了 Excel 进程 ID...为了便于阅读,省略了所有其他声明):

internal static Excel.Application GetExcelInstance(int procID)
{
    EnumChildCallback cb;
    Process p = Process.GetProcessById(procID);

    if (p != null)
    {
        if ((int)p.MainWindowHandle > 0)
        {
            int childWindow = 0;
            cb = new EnumChildCallback(EnumChildProc);
            EnumChildWindows((int)p.MainWindowHandle, cb, ref childWindow);

            if (childWindow > 0)
            {
                const uint OBJID_NATIVEOM = 0xFFFFFFF0;
                // GUIDs used by the OLE Automation Protocol:
                // https://msdn.microsoft.com/en-us/library/cc237842.aspx
                Guid IID_IDispatch = new Guid("{00020400-0000-0000-C000-000000000046}");
                Excel.Window window = null;
                int res = AccessibleObjectFromWindow
                          (
                              childWindow,
                              OBJID_NATIVEOM,
                              IID_IDispatch.ToByteArray(),
                              ref window
                          );

                if (res >= 0)
                {
                    return window.Application;
                }
            }
        }
    }

    return null;
}

// If VBA is open this method will fail when enumerating
// all child windows of the excel process
// EXCEL7 will not be found in child windows but other windows
// will be found like the window for class "VbaWindow"
private static bool EnumChildProc(int hwndChild, ref int lParam)
{
    StringBuilder buf = new StringBuilder(128);
    GetClassName(hwndChild, buf, 128);

    // More info on excel classes:
    // http://www.mrexcel.com/forum/excel-questions/54007-worksheet-class-findwindow-api.html
    if (buf.ToString() == "EXCEL7")
    {
        lParam = hwndChild;
        return false;
    }

    return true;
}

我遇到了完全相同的问题。我通过以下方式解决了它:

  1. 获取所有 Excel 进程 ID 的列表
  2. 遍历所有顶级 windows 并检查其进程 ID 是否在 Excel ID 列表中。如果是这样,则将其添加到另一个列表。 (使用 EnumWindows 和 GetWindowThreadProcessID)
  3. 然后遍历这个 Excel hwnd 列表,基本上做你之前做的事情,即搜索他们的 child windows 找到 class名称是 EXCEL7

下面是一些示例代码:

public class ExcelInstances
{
    HashSet<int> _ExcelProcessIDs;
    List<int> _ExcelTopLevelWindowHwnds;
    List<Excel.Application> _XLInstances;

    public Excel.Application[] GetExcelInstances()
    {
        _XLInstances = new List<Excel.Application>();
        _ExcelProcessIDs = new HashSet<int>();
        _ExcelTopLevelWindowHwnds = new List<int>();

        foreach (Process p in Process.GetProcessesByName("EXCEL")) _ExcelProcessIDs.Add(p.Id); //find all process ids related to Excel

        int hwnd = 0;
        var cb = new WinAPI.WindowEnumProc(GetAllExcelTopLevelWindowHwnds);
        WinAPI.EnumWindows(cb, ref hwnd);

        foreach (var hwnd2 in _ExcelTopLevelWindowHwnds)
        {
            var excelHwnd = 0;
            var cb2 = new WinAPI.WindowEnumProc(GetExcelWorkbooksFromExcelWindowHandles);
            WinAPI.EnumChildWindows(hwnd2, cb2, ref excelHwnd);
        }

        return _XLInstances.ToArray();
    }

    private bool GetAllExcelTopLevelWindowHwnds(int hwnd, ref int lParam)
    {
        int id = 0;
        WinAPI.GetWindowThreadProcessId(hwnd, ref id);

        if (_ExcelProcessIDs.Contains(id))
        {
            if (hwnd > 0)
            {
                _ExcelTopLevelWindowHwnds.Add(hwnd);
            }
        }

        return true;
    }

    private bool GetExcelWorkbooksFromExcelWindowHandles(int hwndChild, ref int lParam)
    {
        int id = 0;
        WinAPI.GetWindowThreadProcessId(hwndChild, ref id);

        StringBuilder buf = new StringBuilder(128);
        WinAPI.GetClassName(hwndChild, buf, 128);
        string clsName = buf.ToString();

        if (clsName == "EXCEL7")
        {
            lParam = hwndChild;
            var wb = UsefulStaticMethods.GetActiveWorkbookFromExcelHandle(hwndChild);
            if (wb != null) _XLInstances.Add(wb.Parent);
        }

        return true;
    }
}