В настоящий момент существует достаточно много онлайн-игр. Часто разработчики предоставляют пользователям возможность установить клиент, с помощью которого играть становится быстрее (меньше запросов в интернет), удобнее (плагины), дешевле (меньше трафика) и так далее. Для одного из таких клиентов мой знакомый-игрок попросил меня написать «дополнение».

С первого же взгляда на игру стало понятно, что сделана она на движке Internet Explorer'а: в окне приложения находится контрол Internet Explorer_Server, в котором и разворачиваются все события. Задача приобрела более ясное очертание. Первый пункт в глобальном ToDo списке стал формулироваться примерно так: «получить исходный код HTML из контрола Internet Explorer_Server, зная только его HWND».
Тема для меня новая, поэтому первым делом стал читать MSDN и Google.

Небольшое примечание: в своих программах я стараюсь пользоваться только Win32 API, поэтому варианты с MFC, ATL и прочими библиотеками я не рассматривал.

Из найденного выяснил, что нужно действовать через COM-интерфейсы Explorer'а (IHTMLWindow2, IHTMLElement...). Должен заметить, что в интернете не так уж и много примеров и описаний для этой темы. Самый популярный способ — отправлять сообщение WM_HTML_GETOBJECT в окно ИЕ, после чего используется функция ObjectFromLResult(). Однако в МСДН написано:
«This function is designed for internal use by Active Accessibility and is documented for informational purposes only. Neither clients nor servers should call this function.»
Я стал искать другие способы решения вопроса.
Естественно в моих поисковых запросах присутствовало «Internet Explorer» — это сыграло со мной злую шутку. Дело в том, что многие, на чьи решения я натыкался, ковырялись именно в Internet Explorer'е — в браузере. Таким образом я начал пробовать достучаться до истины через интерфейс IShellWindows. К успеху это не привело — окно игры не является «shell window» в отличии от браузера, хоть и использует тот же самый контрол.
Решением стало использование Active Accessibility: интерфейса IAccessible и функции AccessibleObjectFromWindow():
 1.    HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
 2.    if(hr == S_OK)
 3.    {
 4.        IAccessible *iAcc;
 5.        hr = AccessibleObjectFromWindow(hwnd, /*OBJID_WINDOW*/OBJID_CLIENT, IID_IAccessible, (void**)&iAcc);
 6.        if(hr == S_OK)
 7.        {
 8.            VARIANT va;
 9.            va.vt = VT_I4;
10.            va.lVal = 1;
11.            hr = iAcc->get_accChild(va, (IDispatch**)&iAcc);
12.            if(hr == S_OK)
13.            {
14.                hr = iAcc->get_accChild(va, (IDispatch**)&iAcc);
15.                if(hr == S_OK)
16.                {
17.                    hr = iAcc->get_accChild(va, (IDispatch**)&iAcc);
18.                    if(hr == S_OK)
19.                    {
20.                        long lCount;
21.                        hr = iAcc->get_accChildCount(&lCount);
22.                        if(hr == S_OK)
23.                        {
24.                            TCHAR *szCount = new TCHAR[256];
25.                            _ltow(lCount, szCount, 10);
26.                            MessageBox(NULL, szCount, L"ChildCount", MB_OK|MB_ICONASTERISK);
27.                            BSTR bstrName;
28.                            for(int i = 1; i <= lCount; i++)
29.                            {
30.                                va.lVal = i;
31.                                hr = iAcc->get_accName(va, &bstrName);
32.                                if(hr == S_OK)
33.                                {
34.                                    MessageBox(NULL, bstrName, L"Name", MB_OK|MB_ICONASTERISK);
35.                                }
36.                            }
37.                            SysFreeString(bstrName);
38.                            delete [] szCount;
39.                        }
40.                    }
41.                }
42.            }
43.            iAcc->Release();
44.        }
45.    }
46.    CoUninitialize();

В данном примере я получаю интерфейс IAccessible, затем несколько раз использую get_accChild(), чтобы добраться до нужного мне элемента, и потом перебираю всё, из чего состоит страница (get_accChildCount() и get_accName()).
Почему я несколько раз делал get_accChild(): IAccessible интерфейс, который я получаю по HWND окна, имеет сложную древовидную структуру, и нужный мне элемент находится не в корне этого дерева. Увидеть всю иерархию интерфейсов можно с помощью утилиты «AccExplorer» (аналог Spy++), которая входит в состав Active Accessibility 2.0 SDK Tools.
Результат выполнения get_accName() на самом деле не HTML, а нечто наподобии innerText в javascript'е. В некоторых ситуациях этого достаточно, но я решил дойти до конца. После того как я получил указатель на нужный мне интерфейс (после последнего get_accChild()), сделал так:
 1    IServiceProvider *iSP;
 2    hr = iAcc->QueryInterface(IID_IServiceProvider, (void**)&iSP);
 3    if(hr == S_OK)
 4    {
 5        IHTMLElement *iHTMLEl;
 6        hr = iSP->QueryService(IID_IHTMLElement, IID_IHTMLElement, (void**)&iHTMLEl);
 7        if(hr == S_OK)
 8        {
 9            BSTR bstr;
10            hr = iHTMLEl->get_innerHTML(&bstr);
11            if(hr == S_OK)
12            {
13                MessageBox(NULL, bstr, L"HTML", MB_OK|MB_ICONASTERISK);
14            }
15            SysFreeString(bstr);
16            iHTMLEl->Release();
17        }
18        iSP->Release();
19    }

В кратце: IAccessible→IServiceProvider→IHTMLElement.
Таким образом я получил возможность не только читать, но и редактировать исходный код страницы.
В заключение хочу лишь предостеречь от использования этого кода в том виде, в котором он тут находится — обязательно освобождайте (Release()) все интерфейсы, указатели на которые вы получаете. В приведённом тут примере я использую iAcc несколько раз подряд для получения указателя на интерфейсы без освобождения предыдущих — мне кажется так нагляднее, но в реальных условиях наверняка приведёт к утечкам памяти. А условия «hr == S_OK» лучше заменить на «SUCCEEDED(hr)».
Удачи. Everything is code…