Сен 232009

Сегодня, друг мой, мы познакомимся с такой технологией, как COM (Component Object Model). Адский высер товарищей из Microsoft, НО(!) от этого не менее полезный.
Вот конкретно мне это понадобилось, чтобы сделать Internet Explorer послушным, белым и пушистым. То есть, чтобы программно заставить IE делать то, что нужно нам, включая навигацию, доступ к DOM-дереву и тому подобные ништячки, ну ты понимаешь к чему я ;)
Итак, моя задача стояла примерно так:

  1. У меня есть исходник некоей софты, написанной на Delphi 7. Соответственно, реализовывать мы это дело будем, используя именно эту IDE (да, я знаю, что это танцы на костях, но делать нечего, такой уж проект).
  2. Далее, ввиду садомазохистических надобностей, нельзя было реализовывать ЭТО внутри VCL-объекта TWebBrowser и хостить это внутри формы.  Kill me, plz! . То есть, мне нужно создать окно Internet Explorer и перхватить управление.
  3. Далее, нужно сделать так, чтобы Internet Explorer не только программно управлялся и выполнял Navigate и Refresh, но и позволил мне просматривать его элементы и DOM-дерево. Kill me twice, then burn my corpse!

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

Хотелось бы оговориться сразу: я – не программист. Я просто умею программировать. У меня возникают какие-то задачи, я их решаю, так или иначе. Прошу особо умных не разглагольствовать на тему эффективности кода. Я делюсь базисом, тем, что смог сам.

Итак, запустим Delphi, и сделаем ништяк, как у меня: просто брось на форму элемент TButton:

Такой ништяк должен получиться, или примерно такой

Такой ништяк должен получиться, или примерно такой

Далее, расслабься, налей чайку и почитай. Если раньше ты никогда не работал с COM и не знаешь, что есть интерфейс(не пользовательский, а программный), то я опишу в двух словах, как это работает.

Смотри. COM делится, грубо, очень грубо говоря, на клиент и сервер. Они могут быть реализованы даже в одном компоненте, но это уже нас не касается. Так вот, любой компонент ком – это сервер, который реализует интерфейс.

Каждый реализуемый интерфейс – это класс, набор методов и свойств, унаследованный от IUnknown. Иерархичная система наследования. Если ты не знаешь, что есть класс и зачем он нужен, я очень тебе сочувствую и лучше тебе сначала об этом узнать.

Мы не будем рассматривать, как, конкретно, наследуется интерфейс, потому, что задача наследования у нас не стоит.
Как итог, представь: есть объект, есть его интерфейс, есть наша программа, которая знает, что хочет использовать именно этот интерфейс.
Каждый фейс имеет GUID. Это уникальный идентификатор объекта в системе. Он пишется в реестр, при регистрации com-сервера.
Используя этот идентификатор, мы будем получать доступ к интерфейсам.
Так вот, задача твоей программы – знать, какой интерфейс она хочет использовать(знать его GUID) и подключиться к нему.
В программной реализации все гораздо проще, чем в теории, на словах. То есть, ты уже примерно представил модель взаимодействия, движемся дальше.

Следующая подзадача: найти ID для Internet Explorer. Есть много версий, как это сделать, у компиляторов есть свои средства, но я сделал все руками.

  1. Нажми Win+R
  2. введи regedit.
  3. Найди корневую ветку HKEY_CLASSES_ROOT, кликни на нее
  4. Нажми F3, введи Internet Explorer
  5. Нажимай F3 до тех пор, пока не увидишь примерно следующее:

Нашелся

Нашелся

Собственно, это то, что мы искали. {0002DF01-0000-0000-C000-000000000046} – это и есть тот самый, заветный идентификатор. Bingo!

Запиши его куда-нибудь.

Идем дальше. Чтобы заюзать Internet Explorer, нам понадобится заголовок, с декларацией его интерфейса. Рекомендую, для начала, пойти на MSDN и почитать, что это за нахуй вообще. Приготовься, курить будешь долго.

Почитал? Красавец! Двинем дальше.

Как ты уже понял, мы используем интерфейс IWebBrowser2. Свойства и методы его ты можешь посмотреть на MSDN.

Теперь, иди к созданной форме и тыкай дважды в кнопку. Появится обработчик события OnClick.

Прежде, чем писать сюда код, добавь в секцию uses следующие заголовки: ComObj,SHDocVw, MSHTML.

unit Unit1;

interface

uses
 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
 Dialogs, StdCtrls,ComObj,SHDocVw,ActiveX;

type
 TForm1 = class(TForm)
 Button1: TButton;
 private
 { Private declarations }
 public
 { Public declarations }
 end;

var
 Form1: TForm1;
 wb1: IWebBrowser2;
implementation

{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin

end;
end.

Нужные места выделены полужирным. В них я объявил экземпляр IWebBrowser2 и включил нужные заголовки.
Далее, нам придется написать простенький обработчик, чтобы посмотреть, как это все работает.

Вот небольшой код, в котором раскрыта общая тема ебли:

procedure TForm1.Button1Click(Sender: TObject);
var
IE:TGUID;
oVar:OleVariant;
begin
 IE:=StringToGUID('{0002DF01-0000-0000-C000-000000000046}');
 CoCreateInstance(IE, nil, CLSCTX_LOCAL_SERVER, IWebBrowser2, wb1);
 if not(wb1=nil) then
 begin
 wb1.Visible:=true;
 wb1.Navigate('http://werebeast.com',oVar,oVar,oVar,oVar);
 end;
end;

Поясню:

IE:TGUID;
oVar:OleVariant;

Этот код объявит переменные, нужные для создания объекта интерфейса.

IE:=StringToGUID('{0002DF01-0000-0000-C000-000000000046}');

Далее, формируем объект GUID;

CoCreateInstance(IE, nil, CLSCTX_LOCAL_SERVER, IWebBrowser2, wb1);

Именно эта строка создаст для нас сам Internet Explorer и затем получит указатель на его интерфейс в wb1.

wb1.Visible:=true;
wb1.Navigate('http://werebeast.com',oVar,oVar,oVar,oVar);

Здесь указывается, что надо таки показать окно и сделать переход на нужный URL.

Прошу заметить, что wb1 объявлен глобально, и конкретно в этой программе нигде не отслеживается соединение с COM-объектом, его закрытие, или потеря связи. Также, здесь не уничтожается сам оъект. Так кодить нельзя! Здесь так сделано в качестве примера и для простоты понимания.

Двигаемся дальше. Нам нужно получить от браузера, наприимер, тело его HTML содержимого.
Для этого, нам понадобится другой интерфейс IHTMLElement. Он нужен, чтобы хэндлить отдельные элементы HTML из DOM дерева.
Теперь, немоно растяни форму, брось на нее компонент TMemo и еще одну TButton. Смело тыкай на новую кнопку и пиши обработчик:

procedure TForm1.Button2Click(Sender: TObject);

var

Doc: IHTMLDocument2;

begin

 if not (wb1=nil) then

 begin

 wb1.Document.QueryInterface(IHTMLDocument2,Doc);

 Memo1.Lines.Add(Doc.body.innerHTML);

 end;

end;

Поясняю:

wb1.Document.QueryInterface(IHTMLDocument2,Doc);

Здесь, мы запрашиваем данные интерфейса от самого Internet Explorer.
можно сделать так:

Doc:=wb1.Document as IHTMLDocument2;

Но, я не люблю такой подход.

Memo1.Lines.Add(Doc.body.innerHTML);

Интуитивно понятно, да?

Так получилось у меня

Так получилось у меня

Вобщем-то, ничего сложного нет, как видишь.

В качестве домашнего задания: посмотри описание интерфейсов IHTMLElementCollection , IHTMLElement[2] и сделай выводы. Они помогут в поиске нужного элемента на странице.

IHTMLDocument2
Posted by WereBeast

Leave a Reply

(обязательно)

(обязательно)