WatiN, запуск Internet Explorer и дескриптор окна IWebBrowser2

Я изучал следующий фрагмент кода из 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).

Это работает для меня просто отлично. Я не вижу никакого конфликта или несоответствия между HWNDs - не знаю, если есть угловой случай, который я мог бы упустить. Я испытываю желание спросить об этом на форумах WatiN, но я подумал сначала спросить здесь, в Центре программистов, на случай, если я упущу что-то очевидное.

Спасибо всем заранее. Любой совет приветствуется.

Ура, Доминик

1 ответ

Решение

Я начал копаться во внутренней оконной структуре Internet Explorer и придумал следующую иерархию (несоответствующие окна опущены, конечно):

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

В ходе тестирования я сделал следующие выводы в Windows7 + IE9(9.0.8112.16421)

  1. Интересно (и нелогично), что IWebBrowser2.HWND НИКОГДА не идентичен HWND класса TabWindowClass, из которого он получен.

  2. IEFrame-> HWND - это то же самое, что ЛЮБОЙ из свойств IWebBrowser2.HWND в том же процессе Internet Explorer. Это верно, даже если у нас открыто несколько вкладок в одном и том же процессе Internet Explorer.

  3. Атрибут Process.MainWindowHandle процесса Internet Explorer (когда мы запускаем Internet Explorer программно) идентичен IEFrame-> HWND и, следовательно, идентичен атрибутам объектов IWebBrowser2.

  4. Активную вкладку можно получить с помощью HWND IEFrame сразу же (используя метод, который я описал в исходном посте).

Мои наиболее образованные предположения относительно того, почему вышеупомянутая раскладка hwnd такова:

  1. Любой, кто работал в Редмонде, сделал эту hwnd-связь между IEFrame и IWebBrowser2, потому что на самом деле существует только 1 активное окно вкладок за раз (в то время как пользователю предоставляется иллюзия X количества вкладок). Или же...

  2. Потому что была необходимость поддерживать обратную совместимость с уже существующим кодом, предназначенным для предыдущих версий IE, и этот код использовал HWND IEFrame, чтобы получить объект IWebBrowser2.

В любом случае я чувствую, что может быть внутренняя ошибка в реализации этой проводки HWND, когда дело доходит до доступа к ней из интерфейса IWebBrowser2, что приводит к InvalidCastException.

Любой, кто лучше разбирается в этом вопросе, пожалуйста, не стесняйтесь написать одну или две строки. Надеюсь, что это поможет.

Ура, Доминик

Другие вопросы по тегам