BETA
Aby się zalogować, najpiew wybierz portal.
Aby się zarejestrować, najpiew wybierz portal.
Podaj słowa kluczowe
Słowa kluczowe muszą mieć co najmniej 3 sąsiadujące znaki alfanumeryczne
Pole zawiera niedozwolone znaki

Baza wiedzy











Tworzenie wtyczek dla IE z wykorzystaniem platformy .NET.

31-07-2006 21:03 | User 79013
Chciałbym zaprezentować Wam krótki, aczkolwiek mam nadzieję, że równocześnie wyczerpujący, opis procesu tworzenia dodatków (add-in) dla przeglądarki Internet Explorer (IE). Technika, którą wykorzystam, to znane Wam zapewne ze słyszenia i nie tylko Browser Helper Objects (BHOs). Ma ona swoje plusy i minusy, które postaram się omówić. Co może Was szczególnie zainteresować to fakt, że napiszemy BHO w kodzie zarządzalnym (a nie w brrr... C++ :-)) Zapraszam więc do lektury – stworzymy program blok

Dygresja zamiast wprowadzenia


Pisząc o wtyczkach do IE, rozpocznę od uwagi na temat... FireFox. Czy zdajecie sobie sprawę z tego, że przeglądarka, o której jeszcze niedawno nikt nie słyszał, zgodnie z raportem XiTiMonitor  zdobyła już trochę ponad 14% rynku. Zgłębiając ten sam raport, znajdziemy w nim informację, iż w Polsce wykorzystuje ją ponad 20% użytkowników. Nie szukając daleko, w 2006 roku (do daty publikacji artykułu) portal CodeGuru.pl ogląda przez FireFox’a niemal 37% użytkowników, a w lipcu wejść za pomocą tej przeglądarki jest już prawie 40%! Czemu więc FireFox zawdzięcza taką popularność? Zakładkom?
Zapewne, ale także bardzo dobrze współpracującemu środowisku programistów, którzy dbają o jego ciągły rozwój i rozbudowę możliwości przeglądarki. I tym małym, no jak im tam... „extensions”? Wtyczki napisane z wykorzystaniem XML User Interface Language (XUL) oraz te tworzone innymi metodami powstają jak grzyby po deszczu i to często właśnie one decydują o tym, że użytkownicy tak bardzo przywiązują się do Firefox’a.
Ale zaraz, zaraz, czy do IE nie ma wtyczek? Owszem są, każdy z Nas pewnie zna pasek (toolbar) instalowany wraz z aplikacjami Google, kilku z Was zapewne nawet było na stronie Add-Ons for Microsoft Internet Explorer. Ale po przeszukaniu bazy dodatków (516) okazuje się, że tylko pewna część (186) jest bezpłatna, a dla porównania istnieje ponad 1600 rozszerzeń dla Firefox’a i niewiele z nich wymaga opłat. Dodatkowo kod rozwiązań dla IE jest w większości przypadków zamknięty, gdy aplikacje Firefox’a to najczęściej projekty open-source.
Co więc powoduje, że dla IE nie powstają tego typu rozwiązania? Czyżby było to aż tak trudne?
Nie sądzę i dlatego napisałem ten artykuł, po którego lekturze, mam nadzieję, zaczniesz tworzyć świetne wtyczki dla IE.

Dlaczego BHO?

Jeśli nie wiesz, czym jest Browser Helper Objects (BHOs), na razie załóżmy, że to tylko nazwa jednej z technik rozszerzania IE o własną funkcjonalność, z wykorzystaniem bliżej nie zidentyfikowanych obiektów, które pomagają wykonać przeglądarce dodatkową pracę. Podobnie niekoniecznie należy sobie zaprzątać głowę terminami takimi jak Explorer Bars i Tool Bands, które w równie naturalny i zrozumiały sposób, oznaczają metody polegające na dodawaniu własnych pasków i narzędzi. Dla zainteresowanych polecam przegląd możliwości rozszerzania IE w sekcji „Browser Extensions” na stronach portalu MSDN [1].
W niniejszym artykule wybrałem metodę polegającą na zaimplementowaniu rozszerzenia jako BHO, ponieważ, w ten sposób mogłem relatywnie szybko osiągnąć mój cel – stworzyć wtyczkę, która będzie blokowała reklamy. Zdecydowałem się na tę metodę także dlatego, iż uznałem, że jeśli programiści tworzący Adobe Reader, czy paski Yahoo! lub Google wybrali tę właśnie drogę, to coś w tym musi być (tak, tak... jeśli jeszcze o tym nie wiesz, to właśnie pomocny BHO, pozwala Ci otwierać pliki PDF bezpośrednio w przeglądarce). Wreszcie stwierdziłem, że jeśli nawet produkt firm IBM - WebAdapt2Me [2],[3], korzysta z tej techniki w celu zmiany wyglądu przeglądanych stron (a dokładniej poprawy ich wyglądu) to coś w niej musi być. Macie rację, nie była to decyzja techniczna – aczkolwiek okazała się bardzo słuszna.

BHO i IE, jak to pożenić?

Aby IE chciał uruchomić naszą wtyczkę podczas startu, musi ją najpierw poznać. Rolę biura matrymonialnego w tym przypadku pełni rejestr systemu Windows. Gdzie i co należy wpisać, powiem w stosownym momencie. Musicie wiedzieć, że IE nie zadaje się z byle kim, to porządna przeglądarka, która przestrzega pewnych zasad etykiety. Zasady te nazywamy interfejsem COM, hej... poczekaj... nie zamykaj jeszcze strony! To nie jest aż tak trudne!
Interfejs COM to (w uproszczeniu) nic innego jak grupa funkcji, które nasz obiekt (klasa) będzie musiała zaimplementować, aby IE mógł się z nią komunikować. W zamian przeglądarka będzie na tyle uprzejma, iż poinformuje naszą wtyczkę o zachodzących w niej zdarzeniach (np. o rozpoczęciu pobierania strony) i pozwoli na wykorzystanie swojego interfejsu, czyli wywołanie jej publicznie dostępnych funkcji. Z ich pomocą BHO może uzyskać dostęp do zawartości okna przeglądarki, pasków, menu, itd.
Większość rozszerzeń BHO było i zapewne nadal jest implementowanych głównie jako komponenty ATL napisane w języku C++ (choć np. wspomniane rozwiązanie WebAdapt2Me napisano w języku Java). Dla programistów C# brzmi to pewnie niezbyt zachęcająco, na całe szczęście istnieje coś takiego jak COM Interoperability [4]. Dzięki temu mechanizmowi, który będziesz miał okazję obejrzeć w akcji, można zbudować kod zarządzalny, który współpracuje z IE tak jak tradycyjna aplikacja COM.
Dobrze, wiemy już, że IE i BHO komunikują się poprzez specjalne interfejsy, wiemy też, że to IE ładuje bibliotekę BHO do pamięci. To czego powinieneś być jeszcze świadom to dość nieciekawy fakt, że każda instancja IE powoduje załadowanie nowego obiektu BHO. Chyba możemy już przejść do szczegółów technicznych?

Szczegóły techniczne

Jak już wspomniałem, najprostszy BHO to obiekt, który implementuje oczekiwany przez IE interfejs, jest przez przeglądarkę instancjonowany, a następnie komunikuje się z nią, wykorzystując tzw. model obiektu IE i zdarzenia zgłaszane przez przeglądarkę. Model obiektowy IE to właśnie zestaw metod, które możemy wywołać z poziomu naszego BHO, aby zmusić IE do określonych czynności. Co ciekawe i niebezpieczne jednocześnie - obiekt BHO nie musi mieć interfejsu użytkownika, idealnie nadaje się więc do złych, ciemnych spraw, takich jak śledzenie aktywności użytkownika itp.
Element przeglądarki , do którego IE przekazuje referencję, to obszar, w którym przeglądarka wyświetla strony, czyli instancja klasy WebBrowser. To z jej pomocą BHO podepnie się pod kolejkę komunikatów, które zgłasza przeglądarka lub przejmie kontrolę nad stroną.
Jednak aby IE, który rozumiem interfejsy COM, mógł rozmawiać z kodem uruchomionym na platformie .NET, musi zajść specjalny proces opakowania interfejsu z naszego obiektu BHO. Platforma .NET robi to bardzo sprytnie, generując w trakcie działania specjalny obiekt COM Callable Wrapper (CCW), który pozwala IE wywoływać funkcje z BHO oraz obiekt Runtime Callable Wrapper (RCW), który wykorzystywany jest, aby z poziomu BHO dostać się do obiektów wewnątrz IE (Patrz rysunek 10. w dalszej części artykułu). Aby platforma mogła stworzyć te obiekty, musimy jej jednak trochę pomóc. Po pierwsze należy w opcjach naszego projektu zaznaczyć opcję „Make assembly COM-Visible".

Rysunek 1.
Rysunek 1. Jak zaznaczyć opcję "Make assembly COM-Visible"

Oraz w opcjach budowania projektu zaznaczyć „Register for COM-Interop”.

Rysunek 2.
Rysunek 2. Gdzie zaznaczyć opcję "Register for COM-Interop".

Gdy to zrobimy, Visual Studio automatycznie wywoła dla nas program regasm.exe, który odpowiednio zarejestruje nasz pakiet (assembly) w rejestrze oraz wygeneruje specjalną bibliotekę typów dla naszego pakietu. Druga czynność to ukłon w stronę rozwiązań COM, które, aby dogadać się pomiędzy sobą, posługują się specjalnym językiem, który pozwala na opisanie typów i metod udostępnianych przez dany interfejs. Uruchamiając aplikację tblexp.exe, generujemy właśnie taki słownik, który kiedyś pozwalał na wywoływanie metod obiektu COM napisanego w np. języku Visual Basic z poziomu C++, a dziś pozwala na porozumiewanie się pomiędzy obiektami COM a platformą .NET.
Jeżeli chcemy, aby nasza biblioteka potrafiła rozmawiać z IE, musimy jeszcze dodać referencję do biblioteki – i tu zaczynają się małe schody. Do komunikacji z IE w wersji poprzedniej (czyli IE 6.0) wykorzystywana była po prostu referencja do biblioteki SHDocVw.dll, która pojawiała się na poniższej liście jako „Microsoft Internet Controls”. Niestety wraz z wprowadzeniem IE 7.0 biblioteka SHDocVw.dll stała się częścią większej biblioteki ieframe.dll. To co pozwala zachować zgodność pomiędzy tymi dwoma bibliotekami to właśnie wspomniane interfejsy COM. Dzięki nim aplikacje, które kiedyś rozmawiały z SHDocVw.dll, dziś będą potrafiły rozmawiać z nową wersją biblioteki. Myślicie, że to niemożliwe? I macie trochę racji, oprócz interfejsu każdy obiekt COM posiada GUID – unikalny numer pozwalający na zidentyfikowanie konkretnego obiektu. Jeśli podejrzymy pliki SHDocVw.dll i ieframe.dll przy pomocy dowolnej przeglądarki interfejsów (np.TypeLib Browser [5]), zobaczymy ciekawą zależność:

Library name: SHDocVw
Version: 1.1
Documentation string: Microsoft Internet Controls
Path: c:\WINDOWS\system32\shdocvw.dll
Library GUID: {EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B}

i

Library name: SHDocVw
Version: 1.1
Documentation string: Microsoft Internet Controls
Path: c:\WINDOWS\system32\ieframe.dll
Library GUID: {EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B}

Przy okazji- jeśli chcemy do tego celu wykorzystać program OLE/COM Viewer, musimy odszukać w nim w sekcji Type Libraries bibliotekę typów „Microsoft Internet Controls”. Gdy klikniemy ją dwukrotnie, zobaczymy pełną listę interfejsów danego obiektu COM oraz sam interfejs w języku IDL. Wygląda on następująco:

// Generated .IDL file (by the OLE/COM Object Viewer)
//
// typelib filename: ieframe.dll

[
  uuid(EAB22AC0-30C1-11CF-A7EB-0000C05BAE0B),
  version(1.1),
  helpstring("Microsoft Internet Controls")
]
library SHDocVw
{

Teraz już chyba nikt nie ma wątpliwości, że mamy do czynienia z tą samą kontrolką.
Dzięki temu obojętnie, czy skorzystamy ze starszej czy nowszej wersji biblioteki, wciąż mamy dostęp do tego samego interfejsu. Dodajmy, więc referencję:

Rysunek 3.
Rysunek 3. Jak dodać referencję

Jak widzimy, ieframe.dll reprezentuje też jeszcze inny typ Microsoft Browser Helpers.

Rysunek 4.
Rysunke 4. (który sugeruję otworzyć w nowym oknie) Przedstawia typy dostęne po dodaniu referencji do biblioteki SHDocVw.dll

Po dodaniu referencji środowisko Visual Studio stworzy dla nas specjalny plik, Interop.SHDocVw.dll - to właśnie dzięki niemu będziemy mogli wywoływać metody z IE. Jak widzimy w oknie Object Browser możemy swobodnie poszukiwać ciekawych metod, zdarzeń i już planować, jakby tu wykorzystać IE.
Ale zaraz, przecież IE jeszcze nic nie wie o naszym obiekcie. Musimy poinformować go za pomocą wpisu w rejestrze systemowym. Ścieżka, na której znajdują się wpisy informujące IE, jakie rozszerzenia powinien załadować, to:

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects

I to, co tam najprawdopodobniej zobaczysz, to już istniejące wpisy:

Rysunek 5.
Rysunke 5. Wpisy w rejestrze, w sekcji dotyczącej Browser Helper Objects

Napisałem wpisy, a Ty widzisz jakieś ciągi znaczków, GUID’y jakieś? Nie przejmuj się, zerknij do rejestru raz jeszcze w poszukiwaniu bardziej ludzkiego opisu:
Np. HKEY_CLASSES_ROOT\CLSID\{06849E9F-C8D7-4D59-B87D-784B7D6BE0B3} to nic innego jak obiekt pomocniczy Adobe Reader’a

Rysunek 6.
Rysunek 6. Prezentacja wpisu w rejestrze identyfikującego Adobe PDH Reader Link Helper (BHO od Adobe)

Jak już pewnie przeczuwasz i Ty będziesz musiał dodać tutaj swój wpis. Nim to jednak zrobimy, potrzebujemy napisać odrobinę kodu.

Kod

Mamy już w miarę dobrze opanowane podstawy teoretyczne, czas zabrać się za pisanie naszego BHO. Zgodnie z tym, co napisał Dino Esposito w jednym z najdokładniejszych artykułów o BHO jakie znam [6], IE będzie starał się powiązać nasz komponent za pomocą interfejsu IObjectWithSite. Szczegóły wymagałyby zagłębienia się w technologię OLE, więc mam nadzieję, iż wystarczy że wspomnę, że IObjectWithSite, umożliwia szybsze (czyt. lżejsze) interakcje pomiędzy IE a naszym komponentem. Interfejs IObjectWithSite deklaruje dwie metody:
SetSite - która zwraca wskaźnik na interfejs IUnknow przeglądarki. IUnknown to tak naprawdę interfejs, który musi zaimplementować każdy obiekt COM, w tym przypadku pozwala on nam na wykorzystanie połączenia z przeglądarką w przyszłości (za jego pomocą dostaniemy uchwyt do kontenera strony).
GetSite – zwraca wskaźnik do interfejsu ustawiony wcześniej poprzez funkcję SetSite()
Jak wykorzystać te mechanizmy, zobaczymy w praktyce. Na razie skupmy się na zdefiniowaniu interfejsu, np. w pliku IObjectWithSite.cs (w solucji, którą już pewnie ściągnąłeś):

[Kod C#]

using System; using System.Runtime.InteropServices; //(1)
namespace AdBouncer
{
[ComImport(), Guid("fc4801a3-2ba9-11cf-a229-00aa003d7352")] //(2) [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] //(3)
public interface IObjectWithSite
{
   void SetSite([In ,MarshalAs(UnmanagedType.IUnknown)] object pUnkSite); //(4)
   void GetSite(ref Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out object ppvSite); //(5)
}
}

Mnóstwo atrybutów w powyższym kodzie wynika z konieczności poinformowania środowiska COM za pomocą .mechanizmu COM Interop, jak dokładnie ma działać wybrany komponent - a raczej jeden z jego interfejsów IObjectWithSite, którego wymaga IE. Mimo że to nie COM Interop jest tematem niniejszego artykułu, należy Ci się kilka wyjaśnień dotyczących powyższego kodu.
Po pierwsze, aby skorzystać z możliwości komunikacji ze środowiskiem COM, należy wykorzystać przestrzeń nazw System.Runtime.InteropServices (1). W przypadku standardowego interfejsu wystarczyłoby podać jego typ (3), jednak IObjectWithSite jest dość nietypowy – istnieje już przecież w systemie, a nasz obiekt chce tylko zadeklarować, że go zaimplementował. Dlatego też używamy atrybutu ComImportAttribute (2), podając właściwy GUID. GUID wpisany jako atrybut to nic innego jak wskazówka dla środowiska COM, że ma do czynienia z tym właśnie interfejsem (jeśli nie wierzysz, sprawdź w rejestrze). Dość dziwne sygnatury metod SetSite i GetSite wynikają z konieczności poinformowania środowiska COM, jak dokładnie będą przekazywane parametry. Głodnym szczegółów polecam książkę [7] – dostępną za darmo dzięki wydawnictwu Apress.
To czego jeszcze potrzebujemy to implementacja powyższego interfejsu, tak abyśmy mogli już rozpocząć działanie naszego komponentu.
Na początku zdefiniujemy swój własny prywatny interfejs IAdBouncer (1), który będzie widoczny dla środowiska COM oraz klasę, która tak naprawdę będzie zawierała metody SetSite i GetSite.

[Kod C#]

// (1)
[Guid("38ECA4D0-A3AF-4aaa-8E08-4082CF9C0DC1")] //(2) [InterfaceType(ComInterfaceType.InterfaceIsDual)] //(3)
public interface IAdBouncer { }

Słusznie zastanawiasz się, dlaczego zadeklarowałem pusty interfejs, mógłbym przytoczyć długie wytłumaczenie (czyt. tłumaczenie z MSDN’u) lub po prostu powiedzieć, że taka jest dobra praktyka. Stały interfejs powoduje, że cokolwiek zechce skorzystać z naszego zarejestrowanego w systemie obiektu COM, nie będzie dezorientowane przez zmieniające się nazwy metod, typu ich parametrów itd. Bo mimo że dla klasy także możemy wygenerować interfejs COM, to nie muszę Ci się chyba tłumaczyć z tego, jak często będzie się on zmieniał, zanim zdecyduję się na jego ostateczną postać.
[Kod C#]

[ClassInterfaceAttribute(ClassInterfaceType.None)] //(4) [GuidAttribute("4A3A88A2-476C-485e-81F1-81ED3C9D1153")] //(5)
[ProgIdAttribute("Testowy obiekt BHO - AdBouncer")] //(6)
public class AdBouncer : IAdBouncer, IObjectWithSite //(7)
{
   public AdBouncer(){} (8)
  
   public void SetSite(object pUnknownSite) {//… }
  
   public void GetSite(ref System.Guid riid, out object ppvSite)
   {
      ppvSite = null; //(9)
      Marshal.ThrowExceptionForHR(E_FAIL);
   }
}
Pierwszy atrybut (4) mówi środowisku, że dla danej klasy nie ma generować interfejsu COM. Właśnie o to nam przecież chodzi. Co ciekawe podczas rejestracji tego obiektu w systemie (czyli dodaniu swojego wpisu w rejestrze) wykorzystamy GUID nie naszego interfejsu, ale klasy (5). Sama zaś klasa to implementacja obu interfejsów, wspomnianego wcześniej IObjectWithSite i IAdBoucer (7). Atrybut ProgId (6) pozwala na zdefiniowanie innej nazwy programu niż domyślnie przypisywana nazwa <namespace>.<nazwa_klasy>, chociaż, jak zobaczymy, nie stanowi to dla nas istotnej różnicy. Ważną sprawą jest też konstruktor (8) - aby nasz obiekt COM został prawidłowo stworzony, środowisko .NET nie może w tym przypadku generować konstruktora domyślnego, musimy go podać explicite.
Metoda GetSite już powoli zaczyna pracować (9), zwraca wprawdzie tylko kod błędu E_FAIL (oznaczający, iż nie udało się odnaleźć żądanego interfejsu), ale to zawsze coś.
Przy okazji warto nadmienić, iż właśnie na potrzeby obsługi błędów zdefiniowałem stałe:

[Kod C#]

const int E_FAIL = unchecked((int)0x80004005);
const int E_NOINTERFACE = unchecked((int)0x80004002);

Mamy już gotowy do załadowania komponent COM zarejestrowany w systemie przez Visual Studio.NET . Chociaż nic on jeszcze nie robi, ale jego szkielet pozwala nam na wiele.
Nasz obiekt możemy odrejestrować ręcznie:
    regasm /unregister AdBouncer.dll
a później zarejestrować ponownie poleceniem:
    regasm /tlb AdBouncer.dll
i jednoczenie wygenerować bibliotekę typów TLB oraz stworzyć kilka wpisów w rejestrze. Niezbyt to ciekawe prawda?
Jeżeli nie korzystasz z przykładowej solucji, zwróć uwagę na fakt, iż assembly, które ma być widziane przez środowisko COM, musi być podpisane. Wygeneruj więc nowy klucz

Rysunke 7.
Rysunke 7. Jaki i gdzie wygenerować klucz

A następnie sprawdź, czy w dołączonych wcześniej referencjach zaszły odpowiednie zmiany

Rysunek 8.
Rysunek 8. Zmiany w referencjach po dodaniu klucza

Wypadałoby zobaczyć, czy IE potrafi już skorzystać z naszego komponentu... niestety na liście dostępnej po wybraniu [Tools -> Internet Options -> Programs (zakładka) -> Manage Add-On (przycisk)] nic jeszcze nie widzimy. Oczywiście, nie ma przecież wpisu w rejestrze – możemy go dodać ręcznie we wspomnianej sekcji
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects
albo wykorzystać możliwości COM Interop. Dodajmy więc do naszego kodu dwie metody:

[Kod C#]

[ComRegisterFunctionAttribute] //(1)
[ComVisible(false)] //(2)
public static void Register(Type t)
{
   string guid = t.GUID.ToString("B");
   RegistryKey rkey =     Registry.LocalMachine.CreateSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects");
   rkey.CreateSubKey(guid); //(3)
   rkey.Close();
}

[ComUnregisterFunctionAttribute] //(4)
[ComVisible(false)]
public static void Unregister(Type t)
{
   string guid = t.GUID.ToString("B");
   RegistryKey rkey = Registry.LocalMachine.CreateSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects");
   rkey.DeleteSubKey(guid,false); //(5)
   rkey.Close();
}

Proste prawda? Atrybut ComRegisterFunction (1) spowoduje, że kiedy albo my, albo środowisko będzie rejestrowało nasz obiekt (używając regasm), zostanie wywołana ta właśnie metoda. Analogicznie w momencie odrejestrowywania wywołamy metodę opatrzoną atrybutem ComUnregisterFunction (4). Atrybut ComVisible (2) ustawiłem na false, aby nie zaprzątać środowisku COM głowy moimi pomocniczymi metodami. Co robią metody (3) i (5) chyba nie muszę tłumaczyć. Rekompilujemy, uruchamiamy IE i przechodzimy do zakładki Manage Add-Ons
Rysunek 9.
Rysunke 9. Okno rozszerzeń w IE

Hej, mamy nasz obiekt BHO! Tylko że nazywa się jakoś tak dziwnie... czyżby IE ignorował parametr ProgID... Niestety tak. Przyda się więc mała poprawka w naszym kodzie rejestrującym.
Wiemy, że nasz COM jest zarejestrowany w rejestrze pod adresem
HKEY_CLASSES_ROOT\CLSID\{4A3A88A2-476C-485E-81F1-81ED3C9D1153}
a więc kawałek kodu:
[Kod C#]

rkey = Registry.ClassesRoot.CreateSubKey(@"\CLSID\" + guid);
rkey.SetValue(null, "Testowa aplikacja BHO - AdBouncer");
w metodzie rejestrującej pozwoli nam na łatwą zmianę nazwy BHO.
Mamy więc szkielet naszej aplikacji – nim wykonana on jakąś pracę proszę przeanalizuj poniższy rysunek.
Rysunek 10.
Rysunek 10. Jak to .NET, IE dogaduję się przy pomocy COM-Interop.

Tak jak wcześniej wspomniałem, dzięki tym wszystkim atrybutom środowisko wygeneruje dla nas całą otoczkę potrzebną do pracy z IE. Będziemy mieli obiekt RCW, przez który odwołamy się do wnętrza przeglądarki oraz CCW, pozwalający przeglądarce odwołać się do naszego kodu.
Interfejsy IWebBrowser2 i DWebBrowserEvent2 zaraz wykorzystamy. A interfejsy IDispatch i IUnknown są potrzebne środowisku COM do pracy, więc sądzę, iż nie muszę ich tu objaśniać – nasz cel to w końcu BHO!

Logika naszego programu (kod c.d.)

Czas dodać do naszego programu trochę logiki, najpierw dodajmy zmienną reprezentującą interfejs przeglądarki

[Kod C#]

protected SHDocVw.IWebBrowser2 pIWebBrowser2;

Następnie z przekazanego do metody SetSite wskaźnika wyciągnijmy ten interfejs, rzutując go na właśnie stworzoną zmienną (1). W tym celu w ciele metody SetSite dodajmy:

[Kod C#]

if (pIWebBrowser2!=null){ Release(); }
if (pUnknownSite==null) { return; }
pIWebBrowser2 = pUnknownSite as SHDocVw.IWebBrowser2; ///(1)

Skąd sprawdzenie, czy pIWebBrowser2 nie jest już czasem przypisany oraz wywołanie tajemniczej metody Release(), którą za chwilę omówię. Otóż ustawianie wskaźnika do kontenera ma miejsce za każdym razem, gdy zostanie zgłoszone zdarzenie lub istnieje potrzeba komunikacji z obiektem BHO (a więc więcej niż kilka razy) i musimy być na to przygotowani. Może się zdarzyć, że przekazany wskaźnik będzie wartością niezdefiniowaną, w tym przypadku grzecznie opuszczamy naszą metodę, nic nie robiąc. Powyższy fragment kodu zamyka rzutowanie. Tak mało kodu, a tyle słów, prawda? :-)
Teraz mała sztuczka. Nie wspominałem o tym, ale obiekty Browser Helper Objects, jak sama nazwa wskazuje, pomagają przeglądarce. Zgodnie więc z Microsoft-ową konsekwencją mogą być ładowane także z poziomu Explorer’a. Musimy więc zabezpieczyć się na taką okoliczność, sprawdzając, co jest środowiskiem hostującym nasz obiekt BHO.

[Kod C#]

string sHostName = pIWebBrowser2.FullName;
if (!(sHostName.ToUpper().EndsWith("IEXPLORE.EXE")))
{ Release(); return; }

Jeśli to nie Internet Explorer, grzecznie ignorujemy wywołanie.
Teraz zaczniemy nasłuchiwać zdarzeń zgłaszanych przez przeglądarkę. Potrzebujemy do tego odpowiedniej zmiennej:

[Kod C#]

protected SHDocVw.DWebBrowserEvents2_Event pDWebBrowserEvents2

oraz odrobiny kodu:

[Kod C#]

pDWebBrowserEvents2 = pIWebBrowser2 as SHDocVw.DWebBrowserEvents2_Event; //(1)
if (pDWebBrowserEvents2 != null) { // tu za chwilę podepniemy zdarzenia }
else { Release(); return; }

Jeśli udało się nam uzyskać wskaźnik na interfejs służący do zgłaszania zdarzeń (1), możemy je wykorzystać w sekcji, którą zaraz wypełnimy logiką naszego programu. Jeżeli zaś nie udało nam się uzyskać dostępu do zdarzeń (lub ich po prostu nie było), oddajemy kontrolę IE.
Co robi tajemnicza metoda Release()?
[Kod C#]

protected void Release()
{
if (pDWebBrowserEvents2!=null) {
 Marshal.ReleaseComObject(pDWebBrowserEvents2);
 pDWebBrowserEvents2=null;
}
if (pIWebBrowser2 != null) {
  Marshal.ReleaseComObject(pIWebBrowser2);
  pIWebBrowser2 = null;
 }
}

Wykorzystując funkcję Marshal.ReleaseComObject zwalniamy powiązanie pomiędzy obiektem COM IE a naszym małym komponentem. Bez tego bardzo szybko okazałoby się, że wciąż trzymamy kilka połączeń pomiędzy różnymi oknami przeglądarki.
Czas przejść do implementacji ciała metody GetSite() – posłużę się tu standardowym wzorcem
[Kod C#]

public void GetSite(ref System.Guid riid, out object ppvSite)
{
 ppvSite=null;
 if (pIWebBrowser2 != null)
 {
   IntPtr pSite = IntPtr.Zero;
   IntPtr pUnknown = Marshal.GetIUnknownForObject(pIWebBrowser2);
   Marshal.QueryInterface(pUnknown, ref riid, out pSite);
   Marshal.Release(pUnknown); Marshal.Release(pUnknown);
   if (!pSite.Equals(IntPtr.Zero)) { ppvSite = pSite; }
   else {
     Release();
     Marshal.ThrowExceptionForHR(E_NOINTERFACE);
   }
 }
 else {
   Release();
   Marshal.ThrowExceptionForHR(E_FAIL);
  }
}

Kolejno sprawdzamy, czy mamy już dostęp do interfejsu kontenera strony, jeśli tak to próbujemy ze środowiska COM uzyskać interfejs IUnknown obiektu z GUI, o który zostaliśmy poproszeni (jakby nie łatwiej było poprosić nas o sam kontener, ale cóż jeśli wszedłeś między COM’y musisz gadać jak i one). Następnie zwracamy wskaźnik lub odpowiednio sygnalizujemy błędy.

Obsługa zdarzeń (logika c.d.)

Jako że musimy odpowiednio rozpoznać zdarzenia z IE, w kodzie łączymy nasze metody z odpowiednimi zdarzeniami zgłaszanymi za pomocą interfejsu DwebBrowserEvents2 – w poprzednio przygotowane miejsce wrzucimy prosty kawałek kodu:

[Kod C#]

pDWebBrowserEvents2.OnQuit += new SHDocVw.DWebBrowserEvents2_OnQuitEventHandler(OnQuit);

i dodamy implementację metody OnQuit:

[Kod C#]

protected void OnQuit() {
  System.Windows.Forms.MessageBox.Show("Test");
  Release();
}

Oczywiście wywołanie okienka zaraz usuniemy – ale jeśli dotąd tworzyłeś własny kod na podstawie tego tekstu – proponuję abyś skompilował kod. Twój BHO już pracuje!!!

Na potrzeby naszego programu (aby usunąć reklamy) podepniemy się pod zdarzenie DocumentComplete

[Kod C#]

pDWebBrowserEvents2.DocumentComplete += new SHDocVw.DWebBrowserEvents2_DocumentCompleteEventHandler(DocumentComplete);

Oznacza ono, że przeglądarka już zakończyła ładowanie treści stron. Nie jest to najlepszy mechanizm blokowania treści. Jednak na potrzeby tego przykładu wykorzystam właśnie tę metodę (może ktoś pokusi się o odnalezienie lepszej metody, która pozwoli na blokowanie reklam na poziomie Privoxy [9] ?). Interfejs DwebBrowserEvents2 jest zdefiniowany na stronach MSDN’a.
Przy okazji, dla obu podpiętych zdarzeń dodałem kod, który je odpina w metodzie Release
Mamy już więc metodę, która zapewnia nam możliwość obsługi strony, gdy zostanie ona załadowana. Pozostaje tylko pytanie, jak ją przetwarzać? Z pomocą przyjdzie nam kolejny obiekt COM. Do projektu dodajemy referencję do biblioteki typów mshtml.tlb (Add References -> COM (zakładka) -> Microsoft HTML Object Library (wybieramy i klikamy OK).
Teraz przyszła kolej na przetwarzanie kodu strony, czyli implementację metody DocumentComplete
[Kod C#]

protected void DocumentComplete(object pDisp, ref object URL)
{
   try {
   IHTMLDocument2 document = (IHTMLDocument2)pIWebBrowser2.Document;
   if (document != null) {
     IHTMLElementCollection allElements = (IHTMLElementCollection)document.all;
     foreach (IHTMLElement element in allElements) {
       if ( (String.Compare(element.tagName,"img",true)==0) ||
            (String.Compare(element.tagName,"object",true)==0) ) {
         if (element.outerHTML.ToLower().Contains("/kropka") ||
             element.outerHTML.ToLower().Contains("/reklama")) {
            element.outerHTML = "";
         }
      }
    }
  }
 } catch {
   Release();
   Marshal.ThrowExceptionForHR(E_FAIL);
}}

Wykorzystując interfejs IHTMLDocument2 i atrybut Document kontenera strony, otrzymamy uchwyt do dokumentu HTML. Osoby, które dobrze znają obiekt DOM, mogą w tym momencie poczuć się jak w domu. Po stronie jednak powędruję nie korzystając z IHTMLDOMNode, a z obiektów IHTMLElement. I element po elemencie sprawdzę, czy mam do czynienia z obrazkiem lub osadzonym obiektem, a jeśli tak, sprawdzę, co znajduje się we wnętrzu definicji danego obiektu. Jak widać testowałem program na stronach Onet.pl – gdzie często reklamy to pliki pobierane z adresów zawierających „/kropka” i „/reklama”. Jeśli znajdę taki ciąg, bezwzględnie obchodzę się z całym elementem – po prostu pozbywając się go ze strony (w modelu DOM, użylibyśmy metody removeNode albo podmienili dany węzeł jego nową wersją).
Oczywiście taka realizacja jest co najmniej prymitywna. I nawet zwykłe F5 na stronie spowoduje, że wszystkie reklamy znów się pokażą (nie obsługujemy przecież tego zdarzenia).

Ale nie o to przecież chodziło – mieliśmy się dowiedzieć, jak tworzyć rozszerzenia dla IE. I choć nie mamy w pełni funkcjonalnego programu, sądzę, że jeśli doszedłeś aż tutaj, wiesz już mniej więcej wszystko, co będzie Ci potrzebne aby rozszerzać IE. Może przydałoby się kilka słów o tym, jak tworzyć własne dodatki w menu i toolbarze – ale o tym chętnie opowiem zainteresowanym na forum, nie przedłużając tego i tak już dość obszernego artykułu.

Fajnie, jeśli przyda Ci się ta odrobina wiedzy na temat IE COM – Interop i zainteresujesz się tematem rozszerzania IE. Może nie jest ono banalne, ale na pewno nie będzie dla Ciebie już takie trudne



[1] Sekcja Browser Extensions portalu MSDN (http://msdn.microsoft.com/library/default.asp?url=/workshop/browser/ext/extensions.asp)
[2] Strona produktu WebAdapt2Me
http://www-306.ibm.com/able/solution_offerings/WebAdapt2Me.html
[3] Prezentacja pokazująca jak działa WebAdapt2Me (tytuł prezentacji to dość wieloznaczne; „HTML Parsing in Java for Accessibility Transformations”)
http://www.idealliance.org/papers/xml02/slides/tibbitts/tibbitts.ppt
[4] COM Interoperability na stronach MSDN
http://msdn2.microsoft.com/en-us/library/6bw51z5z.aspx
[5] TypeLib browser
http://com.it-berater.org/typelib_browser.htm
[6] Browser Helper Objects: The Browser the Way You Want It, Dino Esposito (January 1999)
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwebgen/html/bho.asp
[7] Zainteresowani tematem COM i .NET powinni przeczytać książkę „COM and .NET Interoperability”, dostępną za darmo na stronie http://www.apress.com/free/.
[8] Interfejs DWebBrowserEvents2
http://msdn.microsoft.com/workshop/browser/webbrowser/reference/ifaces/dwebbrowserevents2/dwebbrowserevents2.asp
[9] Privoxy http://www.privoxy.org/


Załączniki:

Podobne artykuły

Komentarze 1

User 81274
User 81274
1 pkt.
Nowicjusz
21-01-2010
oceń pozytywnie 0
test
test
pkt.

Zaloguj się lub Zarejestruj się aby wykonać tę czynność.