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