Dajbych.net


Jak vytvářet doplňky pro Internet Explorer, díl 1

, 10 minut čtení

In­ter­net Ex­plo­rer lze roz­ší­řit mnoha způ­soby. Je možné při­dá­vat po­ložky do nabí­dek, vy­tvá­řet vlastní pa­nely, sa­mo­statná tla­čítka na pa­nel pří­kazů nebo vy­tvo­řit ak­ce­le­rá­tor. Pro­tože se do­plňky, až na ak­ce­le­rá­tory, pro­gra­mují v C++, uká­žeme si, jak si práci po­mocí .NET kniho­ven zjed­no­du­šit. Vyu­ži­jeme při­tom roz­šíření CLI ja­zyka C++, které do­káže zpřístup­nit celý .NET. Část do­plňku tedy může být na­psána v ja­zyce C#.

Vy­tvo­ření do­plňku In­ter­net Ex­plo­reru roz­hodně není in­tui­tivní zá­le­ži­tost. Když jsem před rokem po­tře­bo­val do­pl­něk pro IE vy­tvo­řit, ná­vod po­dobný to­muto jsem hle­dal marně. Do­plňky pro In­ter­net Ex­plo­rer pro jeho vý­vo­jový tým nejsou v sou­časné době pri­o­rita, takže je­jich vy­tvá­ření není tak snadné, jak by mohlo být. Přesto do­ká­žou být ve­lice uži­tečné.

Ukažme si proto, jak vy­tvo­řit jed­no­duchý do­pl­něk – tla­čítko pro pří­ka­zový pa­nel, který vy­píše ti­tulek a ad­resu stránky. Na tomto prostém pří­kladu osvět­líme prin­cip vy­tvá­ření do­plňku, jeho re­gis­traci a vy­tvá­ření in­stalá­toru.

Projekt

Nej­prve je po­třeba zalo­žit ATL pro­jekt. Ten nalez­nete v ka­te­go­rii Vi­sual C++. Po­kud chcete, aby vám rov­nou fun­go­valy ukázky zdro­jo­vého kódu bez pře­jme­no­vá­vání tříd a roz­hraní, na­zvěte ho MyIeAd­don.

V prů­vodci je po­třeba za­škrt­nout Allowmer­gingofproxy/stubcode, aby se vy­ge­ne­ro­val jen je­den pro­jekt.

Prv­ním kro­kem je při­dání třídy, která tla­čítko re­pre­zen­tuje. Z místní nabídky pro­jektu vyberte Add a poté Class. Ná­sledně zvolte po­ložku ATL Sim­pleOb­ject.

Třídu vhodně po­jme­nujte. Já jsem zvo­lil ná­zev MyBut­ton, aby bylo z uká­zek zdro­jového kódu hned jasné, o kte­rou třídu se jedná.

Pod­poru pro náhled či hle­dání nechte v prů­vodci vypnu­tou. Volbu Ag­gre­gation na­stavte na No a za­škrt­něte IOb­jectWi­th­Site.

Tak to byla ta lehčí část. Nyní je po­třeba přidat ně­ko­lik řádků kódu do hla­vičky třídy tla­čítka. Tím se de­fi­nuje roz­hraní pro jeho zob­ra­zení v Inter­net Explo­reru.

// MyButton.h : Declaration of the CMyButton #pragma once #include "resource.h" // main symbols #include <shlguid.h> // IID_IWebBrowser2, DIID_DWebBrowserEvents2, etc. #include <exdispid.h> // DISPID_DOCUMENTCOMPLETE, etc. #include <mshtml.h> // DOM interfaces #include "MyIeButton_i.h" #if defined(_WIN32_WCE) && !defined(_CE_DCOM) && !defined(_CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA) #error "Single-threaded COM objects are not properly supported on Windows CE platform, such as the Windows Mobile platforms that do not include full DCOM support. Define _CE_ALLOW_SINGLE_THREADED_OBJECTS_IN_MTA to force ATL to support creating single-thread COM object's and allow use of it's single-threaded COM object implementations. The threading model in your rgs file was set to 'Free' as that is the only threading model supported in non DCOM Windows CE platforms." #endif using namespace ATL; // CMyButton class ATL_NO_VTABLE CMyButton : public CComObjectRootEx<CComSingleThreadModel>, public CComCoClass<CMyButton, &CLSID_MyButton>, public IObjectWithSiteImpl<CMyButton>, public IDispatchImpl<IMyButton, &IID_IMyButton, &LIBID_MyIeButtonLib, 1, 0>, public IDispEventImpl<1, CMyButton, &DIID_DWebBrowserEvents2, &LIBID_SHDocVw, 1, 1>, public IDispEventImpl<0, CMyButton, &DIID_HTMLDocumentEvents2, &LIBID_MSHTML, 4, 0>, public IOleCommandTarget { public: CMyButton() { } DECLARE_REGISTRY_RESOURCEID(IDR_MYBUTTON) DECLARE_NOT_AGGREGATABLE(CMyButton) BEGIN_SINK_MAP(CMyButton) SINK_ENTRY_EX(1, DIID_DWebBrowserEvents2, DISPID_DOCUMENTCOMPLETE, OnDocumentComplete) END_SINK_MAP() BEGIN_COM_MAP(CMyButton) COM_INTERFACE_ENTRY(IMyButton) COM_INTERFACE_ENTRY(IDispatch) COM_INTERFACE_ENTRY(IObjectWithSite) COM_INTERFACE_ENTRY(IOleCommandTarget) END_COM_MAP() DECLARE_PROTECT_FINAL_CONSTRUCT() HRESULT FinalConstruct() { return S_OK; } void FinalRelease() { } public: // IObjectWithSite STDMETHOD(SetSite)(IUnknown *pUnkSite); // IOleCommandTarget STDMETHOD(Exec)(const GUID *pguidCmdGroup, DWORD nCmdID, DWORD nCmdExecOpt, VARIANTARG *pvaIn, VARIANTARG *pvaOut); STDMETHOD(QueryStatus)(const GUID *pguidCmdGroup, ULONG cCmds, OLECMD *prgCmds, OLECMDTEXT *pCmdText); // DWebBrowserEvents2 void STDMETHODCALLTYPE OnDocumentComplete(IDispatch *pDisp, VARIANT *pvarURL); private: CComPtr<IWebBrowser2> m_spWebBrowser; BOOL m_fAdvised; CComQIPtr<IOleCommandTarget, &IID_IOleCommandTarget> m_spTarget; }; OBJECT_ENTRY_AUTO(__uuidof(MyButton), CMyButton) Samotná implementace metod může vypadat například takto: // MyButton.cpp : Implementation of CMyButton #include "stdafx.h" #include "MyButton.h" // CMyButton void STDMETHODCALLTYPE CMyButton::OnDocumentComplete(IDispatch *pDisp, VARIANT *pvarURL) { } STDMETHODIMP CMyButton::SetSite(IUnknown *pUnkSite) { if (pUnkSite != NULL) { // Cache the pointer to IWebBrowser2 CComQIPtr<IServiceProvider> sp = pUnkSite; HRESULT hr = sp->QueryService(IID_IWebBrowserApp, IID_IWebBrowser2, (void**)&m_spWebBrowser); hr = sp->QueryInterface(IID_IOleCommandTarget, (void**)&m_spTarget); if (SUCCEEDED(hr)) { // Register to sink events from DWebBrowserEvents2. hr = IDispEventImpl::DispEventAdvise(m_spWebBrowser); if (SUCCEEDED(hr)) { m_fAdvised = TRUE; } } } else { // Unregister event sink. if (m_fAdvised) { IDispEventImpl::DispEventUnadvise(m_spWebBrowser); m_fAdvised = FALSE; } // Release pointer m_spWebBrowser.Release(); m_spTarget.Release(); } // Return base implementation return IObjectWithSiteImpl<CMyButton>::SetSite(pUnkSite); } STDMETHODIMP CMyButton::Exec(const GUID *pguidCmdGroup, DWORD nCmdID, DWORD nCmdExecOpt, VARIANTARG *pvaIn, VARIANTARG *pvaOut) { if (m_spWebBrowser != NULL) { BSTR url; BSTR title; m_spWebBrowser->get_LocationURL(&url); m_spWebBrowser->get_LocationName(&title); MessageBox(NULL, url, title, 0); ::SysFreeString(url); ::SysFreeString(title); return S_OK; } else { MessageBox(NULL, _T("No Web browser pointer"), _T("Oops"), 0); return E_ABORT; } } STDMETHODIMP CMyButton::QueryStatus(const GUID *pguidCmdGroup, ULONG cCmds, OLECMD prgCmds[], OLECMDTEXT *pCmdText) { HRESULT hr = OLECMDERR_E_UNKNOWNGROUP; if (pguidCmdGroup && IsEqualGUID(*pguidCmdGroup, CLSID_ToolbarExtButtons)) { for (ULONG i = 0; i < cCmds; i++) { if (m_spWebBrowser) { // By default, we'll support all commands prgCmds[i].cmdf = OLECMDF_ENABLED | OLECMDF_SUPPORTED; } else { // If we wanted to latch the button down, we could do this: prgCmds[i].cmdf |= OLECMDF_LATCHED; } } hr = S_OK; } return hr; }

Me­toda Exec má na sta­rosti ob­sluhu stisk­nutí tla­čítka. Me­toda Que­ryS­ta­tus ur­čuje, je-li tla­čítko po­vo­lené, či za­ká­zané. Me­toda On­Do­cu­ment­Com­plete je vo­lána vždy po na­čtení stránky.

Registrace

Knihovna se musí za­re­gis­tro­vat, čímž se vlastně na­in­staluje do In­ter­net Ex­plo­reru. K tomu je po­třeba nej­prve zjis­tit pa­t­řičná GUID ob­jektů v knihovně. Ty se nachá­zejí v sou­boru MyIeAd­don.idl:

interface IMyButton : IDispatch{ }; [ uuid(78F4AA07-8BAD-4D7E-AA30-D3726A96C3FD), version(1.0), ] library MyIeButtonLib { importlib("stdole2.tlb"); [ uuid(9FCA1565-739D-4741-8957-D5A7957AB6F4) ] coclass MyButton { [default] interface IMyButton; }; };

Do sou­boru MyIe­Ad­don.rgs vložte tento kód a na­hraďte v něm po­u­žitá GUID za ty vaše. Tím se za­re­gis­truje roz­šíření pro­hlí­žeče.

HKLM { NoRemove SOFTWARE { NoRemove Microsoft { NoRemove 'Internet Explorer' { NoRemove Extensions { ForceRemove '{A3278C3B-DA28-4E8E-924D-E1676D7BF4BE}' = s 'MyButton' { val 'Default Visible' = s 'yes' val 'ButtonText' = s 'My IE Button' val 'CLSID' = s '{1FBA04EE-3024-11d2-8F1F-0000F87ABD16}' val 'ClsidExtension' = s '{A3278C3B-DA28-4E8E-924D-E1676D7BF4BE}' val 'Icon' = s '%%ProgramFiles(x86)%%\MyIeAddon\icon.ico' val 'HotIcon' = s '%%ProgramFiles(x86)%%\MyIeAddon\icon.ico' } } } } } }

Tla­čítko má pouze je­di­nou ikonu o roz­mě­rech 16 × 16 px a nelze jí dy­na­micky mě­nit. Po­ložky Icon a Ho­tI­con de­fi­nují její umís­tění. V cestě je možné uvést systé­mo­vou pro­měn­nou. Není to sice na MSDN ni­kde do­ku­men­to­váno, ale fun­guje to. Vyu­ži­jete to pro in­stalaci x86 do­plňku na x64 systé­mech. Jak pro­měn­nou při­dat bě­hem in­stalace na star­ších systé­mech, kde není de­fi­no­vána, ukážu poz­ději.

Jsou ještě další typy do­plňků. Ve­dle roz­šíření pro­hlí­žeče exis­tuje ještě ob­jekt po­moc­níka pro­hlí­žeče. Zatímco roz­šíření pro­hlí­žeče je do paměti na­čteno až když uži­va­tel na ta­čítko klikne (proto nejde mě­nit ikona), po­moc­ník pro­hlí­žeče se na­čítá při jeho spuš­tění. Třída pro zpra­co­vání udá­lostí jako například na­čtení stránky je tedy lepší re­gis­tro­vat spíše jako ob­jekt po­moc­níka pro­hlí­žeče:

HKLM { NoRemove SOFTWARE { NoRemove Microsoft { NoRemove Windows { NoRemove CurrentVersion { NoRemove Explorer { NoRemove 'Browser Helper Objects' { ForceRemove '{ 9FCA1565-739D-4741-8957-D5A7957AB6F4}' = s ' MyIeAddon' { val 'NoExplorer' = d '1' } } } } } } } }

Každý typ do­plňku musí im­ple­men­to­vat trošku jiné roz­hrní. Při­pra­vil jsem však ukázku zdro­jo­vého kódu tak, že im­ple­men­tuje roz­hraní obě. Typ do­plňku tedy ur­čuje jen to, jakým způ­so­bem se za­re­gis­truje. Je možné re­gis­tro­vat jednu třídu jako roz­šíření pro­hlí­žeče i ob­jekt po­moc­níka pro­hlí­žeče.

V ně­kterých pří­pa­dech se stává, že se re­gis­trace po buildu ne­po­vede. Na­štěstí je v přímo po­pisu chyby po­psáno, jak jí vyře­šit. Stačí ve vlast­nos­tech pro­jektu po­vo­lit Per-user Re­di­rection.

Funkce

Sa­motná funk­ci­o­na­lita může být na­psaná v ja­zyce C++/CLI, který do­vo­luje po­u­žít kni­hovny plat­formy .NET. To sice mírně zvýší dobu po­třeb­nou pro spuš­tění In­ter­net Ex­plo­reru, na druhou stranu není nutné učit se MFC. Úvod do C++/CLI světa se­psal Ja­kub Čer­mák.

Roz­šíření CLI za­vádí ja­zy­kové kon­strukce nutné pro pro­gra­mo­vání v .NET, na­pří­klad ma­naged třídy. Je­jich in­stance se vy­tváří pří­ka­zem gc­new. Uka­zuje se na ně zvlášt­ními poin­tery a o je­jich uvol­ňo­vání z paměti se stará gar­bage collec­tor.

Co všechno lze se strán­kou dě­lat bude před­mě­tem dal­ších dílů. Pro před­stavu jen uvedu, že jde na­pří­klad upra­vo­vat DOM stránky nebo číst co­o­kies pro­hlí­žeče.

Instalátor

K vy­tvo­ření in­stalá­toru se nej­více hodí In­stall­Shield LE, který lze do Vi­sual Stu­dia do­in­stalo­vat. Po­stará se o zko­píro­vání sou­borů do správ­ného ad­re­sáře a další dů­le­žité pro­cedury. Je schopný v pří­padě po­třeby stáh­nout a na­in­stalo­vat .NET Fra­mework.

Vy­tvo­ření in­stalá­toru není úplně pří­mo­čaré. Problém způ­so­bují dvě složky Pro­gram Fi­les na x64 systé­mech. V sou­bo­rech k in­stalaci je po­třeba ve vlast­nos­tech in­stalo­vané knihovny na­sta­vit sa­mo­čin­nou re­gis­traci. Ta bě­hem in­stalace přes pro­gram re­gsvr32 re­gis­truje kni­hovnu stejně jako Vi­sual Studio po buildu.

Pokud po­u­ží­váte C++/CLI, za­škrt­něte v Re­dis­tri­butablesMicro­soft .NET Fra­mework Full (Web Down­load). Tím in­stalá­tor do­in­staluje .NET Fra­mework 4, po­kud ještě v systému není. Dále v po­ložce Cum­s­tom Acti­ons při­dejte do udá­losti Af­ter Re­gis­ter Pro­duct nový VB­Script.

V něm se může vy­tvo­řit systé­mová pro­měnná Pro­gram­Fi­les(x86), po­kud v systému ještě de­fi­no­vaná neni. To ob­stará jed­no­duchý skript ve Vi­sual Ba­sicu:

Set wshShell = CreateObject( "WScript.Shell" ) If wshShell.ExpandEnvironmentStrings( "%ProgramFiles(x86)%" ) = "%ProgramFiles(x86)%" Then Set wshSystemEnv = wshShell.Environment( "SYSTEM" ) wshSystemEnv( "ProgramFiles(x86)" ) = "C:\Program Files" End If

Ak­tua­li­zace do­plňku se pro­vádí opě­tov­nou in­stalací no­vější verze. In­ter­net Ex­plo­rer bě­hem ní ne­smí být spuš­těn.

V dal­ším díle si uká­žeme, jak číst co­o­kie pro­hlí­žeče a vy­tvá­řet tak do­plňky, které mo­hou ně­jakým způ­so­bem zpra­co­vat s pro­fi­lem uži­va­tele. Ten se tak ne­bude muset při­hla­šo­vat zvlášť na we­bové stránce a zvlášť do do­plňku pro­hlí­žeče.

Zdro­jový kód pří­kladu.

Člá­nek byl se­psán pro Zdro­ják.