0

我正在研究来自 WatiN 的以下代码,它处理启动和附加到 Internet Explorer:

    private static IEBrowser CreateIEPartiallyInitializedInNewProcess(Uri uri)
    {
        var m_Proc = CreateIExploreInNewProcess(uri);
        var helper = new AttachToIeHelper();

        var action = new TryFuncUntilTimeOut(TimeSpan.FromSeconds(Settings.AttachToBrowserTimeOut))
        {
            SleepTime = TimeSpan.FromMilliseconds(500)
        };

        var ie = action.Try(() =>
        {
            m_Proc.Refresh();
            var mainWindowHandle = m_Proc.MainWindowHandle;

            // return mainWindowHandle != IntPtr.Zero ? GetIWebBrowser2Directly(mainWindowHandle) : null;

            return mainWindowHandle != IntPtr.Zero
                ? helper.FindIEPartiallyInitialized(new AttributeConstraint("hwnd", mainWindowHandle.ToString()))
                : null;
        });

        if (ie != null) return ie._ieBrowser; 
        // if (ie != null) return new IEBrowser(ie);

        throw new BrowserNotFoundException("IE", "Timeout while waiting to attach to newly created instance of IE.", Settings.AttachToBrowserTimeOut);
    }

WatiN 所做的是它启动 Internet Explorer 并等待它得到它的 .MainWindowHandle (这是在 Internet Explorer 中显示内容的“窗口”的句柄)。一旦它获得了这个窗口句柄,它就会得到一个在用户桌面上启动并运行的所有 IWebBrowser2 窗口的列表,并尝试将进程的 .MainWindowHandle 与一个(如果有的话)窗口句柄相匹配。 IWebBrowser2 集合。

这种方法最重要的问题是 IWebBrowser2.HWND 属性(需要与 .MainWindowHandle 进行比较)可能非常有问题、不稳定和喜怒无常,因为它在您尝试访问它时每隔一次抛出 InvalidCastException(至少在我正在运行测试的机器)。然后又是这样一个操作的开销。

这是我对任何可能更了解我在 Windows 编程方面的知识的人的问题:既然 HWND 无论如何都会匹配,为什么我们不使用 .MainWindowHandle 值立即检索所需的 IWebBrowser2(请参阅上面注释掉的代码) 通过使用以下方法(受 WatiN 本身在 ShellWindow2.cs 中使用的代码的启发):

    private static IWebBrowser2 GetIWebBrowser2Directly(IntPtr embeddedWebBrowserWindowHandle)
    {
        IHTMLDocument2 document2 = UtilityClass.TryFuncIgnoreException(() => IEUtils.IEDOMFromhWnd(embeddedWebBrowserWindowHandle));
        if (document2 == null) return null;

        IHTMLWindow2 parentWindow = UtilityClass.TryFuncIgnoreException(() => document2.parentWindow);
        if (parentWindow == null) return null;

        return UtilityClass.TryFuncIgnoreException(() => ShellWindows2.RetrieveIWebBrowser2FromIHtmlWindw2Instance(parentWindow));
    }

(作为旁注,我们甚至可以制作一个代理对象,在我的另一篇文章中进行了描述,以缓存窗口句柄,以避免向 IWebBrowser2.HWND 询问)。

这对我来说很好。我看不到 HWND 之间有任何冲突或不匹配——不知道是否有我可能错过的极端情况。我很想在 WatiN 论坛上问这个问题,但我想先在 Programmers' Central 上问一下,以防我遗漏了一些明显的东西。

谢谢大家。任何提示表示赞赏。

干杯,多米尼克

4

1 回答 1

1

我开始深入研究 Internet Explorer 的内部窗口结构,并提出了以下层次结构(当然省略了不相关的窗口):

框架
|
-- TabWindowClass-1 --convert--> FirstIWebBrowser2
|
-- TabWindowClass-2 --convert--> SecondIWebBrowser2
|
...
|
-- TabWindowClass-Nth --convert--> Nth-IWebBrowser2

通过测试,我在Windows7 + IE9(9.0.8112.16421)中得出以下发现

  1. 有趣的是(并且违反直觉) IWebBrowser2.HWND 永远不会与它来自的 TabWindowClass 的 HWND 相同。

  2. IEFrame->HWND 与同一 Internet Explorer 进程中的任何 IWebBrowser2.HWND 属性相同。即使我们在同一个 Internet Explorer 进程中打开多个选项卡也是如此。

  3. Internet Explorer 进程的 Process.MainWindowHandle 属性(当我们以编程方式启动 Internet Explorer 时)与 IEFrame->HWND 相同,因此也与 IWebBrowser2 对象的属性相同。

  4. 可以立即使用 IEFrame 的 HWND 检索处于活动状态的选项卡(使用我在原始帖子中概述的方法)。

关于为什么上述 hwnd-layout 成立,我最有根据的猜测是:

  1. Redmond 的任何一个人都在 IEFrame 和 IWebBrowser2 之间进行了这种 hwnd-wiring,因为实际上一次只有 1 个活动选项卡窗口(而用户会产生 X 个选项卡的错觉)。或者 ...

  2. 因为需要保持与针对先前版本 IE 的现有代码的向后兼容性,并且该代码使用 IEFrame 的 HWND 来获取它的 IWebBrowser2 对象。

在任何一种情况下,当从 IWebBrowser2 接口访问它时,我都觉得这个 HWND 接线的实现可能存在内部错误,这会导致 InvalidCastException。

任何对此问题有更深入了解的人,请随时写一两行。希望这些帮助。

干杯,多米尼克

于 2011-10-14T15:31:57.587 回答