...

четверг, 19 декабря 2013 г.

Доступ к контенту Modern-приложения на HTML\JS из Desktop-приложения под Windows 8

Иногда бывает нужно из одной программы добраться до содержимого другой программы. Ну, например, получить из неё какой-нибудь контент, или автоматизировать действия. В случае классических приложений Windows эта проблема решается весьма просто — находим родительское окно через FindWindow, далее, зная его HWND, можем перечислить дочерние окна и элементы управления на них. А тут уже полная свобода — можем получить текст, написанный на этих элементах, изменить их размеры и положение, отправить сообщения для эмуляции клика мышью или набора текста с клавиатуры, даже удалить имеющиеся элементы и создать новые.

Но для Modern-приложений всё иначе. Давайте возьмём, к примеру, приложение «Погода» из стандартного набора Windows8. Допустим, мы открыли его в боковой панели и хотим как-то узнать из нашего обычного (Desktop) приложения, а какую же оно показывает температуру. Если посмотреть на окно «Погоды» с помощью Spy++ мы увидим родительское окно типа Windows.UI.Core.CoreWindow и вложенное в него окно Web Platform Embedding. А значит перед нами Modern-приложение написанное на HTML\Js и живущее внутри встроенного компонента браузера. То есть вышеописанные манипуляции с Windows-контролами не имеют смысла — их в этом окне попросту нет, поскольку всё его содержимое рендерится целиком.


Но давайте же всё-таки попробуем вытащить из него текущую температуру.


Начнём с того, что на MSDN, в его стиле, есть две статьи с противоположным содержанием — одна предостерегает нас от того, чтобы лазить руками в IE, встроенный в чужие компоненты, потому что это небезопасно и можно всё сломать («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. Бла-бла-бла...»). А вторая говорит, что всё ок, можно, и даже даёт код, как это сделать. Первая нам не интереса, а вторая — KB 249232.


Правда, в ней есть ошибка — в вызове функции ObjectFromLresult они пытаются взять неверный интерфейс и в итоге ничего не работает. Но это вообще стиль MSDN, надо привыкать.


Итак, в чём же суть нашей затеи?



  1. Находим окно верхнего уровня с заданным заголовком и классом («Weather» и «Windows.UI.Core.CoreWindow», соответственно).

  2. Перечисляем его «детей», находим окно класса «Internet Explorer_Server».

  3. Отправляем этому окну сообщение WM_HTML_GETOBJECT, получаем в ответ указатель, который с помощь функции ObjectFromLresult может быть преобразован к указателю на интерфейс IHTMLDocument2.

  4. Имея IHTMLDocument2 мы уже можем делать с документом всё, что угодно — получить его контент, изменить, сэмулировать «клик», выполнить Javascript.


Я практически был уверен, что где-то в районе пунктов 3-4 на пути встанет механизм безопасности Windows, отделяющий Modern-приложения друг от друга и от десктопных, и уже был готов применять что-то из средств, описанных мною в прошлой статье. Но… Этого не понадобилось. Несмотря на то, что приложение «Погода» работает вроде бы в песочнице, вроде бы с Low Integrity — мы спокойно можем отправлять ему сообщения, получать указатель на IHTMLDocument2, обмениваться с ним данными. Никаких барьеров безопасности преодолевать не пришлось — их просто нет.


Итог:


Основной код


#include "stdafx.h"
#include <iostream>
#include <sstream>
#include <mshtml.h>
#include <atlbase.h>
#include <oleacc.h>
#include "conio.h"

using namespace std;

BOOL CALLBACK EnumChildProc(HWND hwnd,LPARAM lParam)
{
TCHAR buf[100];

::GetClassName( hwnd, (LPTSTR)&buf, 100 );
if ( _tcscmp( buf, _T("Internet Explorer_Server") ) == 0 )
{
*(HWND*)lParam = hwnd;
return FALSE;
}
else
return TRUE;
};

void GetDocInterface(HWND hWnd)
{
CoInitialize( NULL );

// Explicitly load MSAA so we know if it's installed
HINSTANCE hInst = ::LoadLibrary( _T("OLEACC.DLL") );
if ( hInst != NULL )
{
if ( hWnd != NULL )
{
HWND hWndChild=NULL;
// Get 1st document window
::EnumChildWindows( hWnd, EnumChildProc, (LPARAM)&hWndChild );
if ( hWndChild )
{
CComPtr<IHTMLDocument2> spDoc;
LRESULT lRes;

UINT nMsg = ::RegisterWindowMessage( _T("WM_HTML_GETOBJECT") );
::SendMessageTimeout( hWndChild, nMsg, 0L, 0L, SMTO_ABORTIFHUNG, 1000, (DWORD*)&lRes );

LPFNOBJECTFROMLRESULT pfObjectFromLresult = (LPFNOBJECTFROMLRESULT)::GetProcAddress( hInst, "ObjectFromLresult" );
if ( pfObjectFromLresult != NULL )
{
HRESULT hr;
hr = (*pfObjectFromLresult)( lRes, IID_IHTMLDocument2, 0, (void**)&spDoc );
if ( SUCCEEDED(hr) )
{
BSTR bstrContent = NULL;
IHTMLElement *p = 0;
spDoc->get_body(&p);

if (p)
{
p->get_innerHTML( &bstrContent );
std::wstring ws(bstrContent, SysStringLen(bstrContent));
std::string s(ws.begin(), ws.end());
cout << s;
p->Release();
}
}
}
} // else document not ready
} // else Internet Explorer is not running
::FreeLibrary( hInst );
} // else Active Accessibility is not installed
CoUninitialize();
}

int _tmain(int argc, _TCHAR* argv[])
{
wstring windowTitle, windowClass;

wcout << "Please enter parent window title (you can find it by Spy++):" << endl;
std::getline(std::wcin, windowTitle);
wcout << "Please enter parent window class (you can find it by Spy++):" << endl;
std::getline(std::wcin, windowClass);

HWND hwnd = FindWindow(windowClass.c_str(), windowTitle.c_str());
wcout << "HWND is " << hwnd << endl;

GetDocInterface(hwnd);

_getch();
return 0;
}







Весь проект на Github

This entry passed through the Full-Text RSS service — if this is your content and you're reading it on someone else's site, please read the FAQ at fivefilters.org/content-only/faq.php#publishers.


Комментариев нет:

Отправить комментарий