Přejít k navigační liště

Zdroják » Webdesign » Jak vytvářet doplňky pro Internet Explorer

Jak vytvářet doplňky pro Internet Explorer

Články Webdesign

Internet Explorer lze rozšířit mnoha způsoby. Je možné přidávat položky do nabídek, vytvářet vlastní panely, samostatná tlačítka na panel příkazů nebo vytvořit akcelerátor. Protože se doplňky, až na akcelerátory, programují v C++, ukážeme si, jak si práci pomocí .NET knihoven zjednodušit.

Vytvoření doplňku Internet Exploreru rozhodně není intuitivní záležitost. Když jsem před rokem potřeboval doplněk pro IE vytvořit, návod podobný tomuto jsem hledal marně. Doplňky pro Internet Explorer pro jeho vývojový tým nejsou v současné době priorita, takže jejich vytváření není tak snadné, jak by mohlo být. Přesto dokážou být velice užitečné.

Ukažme si proto, jak vytvořit jednoduchý doplněk – tlačítko pro příkazový panel, který vypíše titulek a adresu stránky. Na tomto prostém příkladu osvětlíme princip vytváření doplňku, jeho registraci a vytváření instalátoru.

Projekt

Nejprve je potřeba založit ATL projekt. Ten naleznete v kategorii Visual C++. Pokud chcete, aby vám rovnou fungovaly ukázky zdrojového kódu bez přejmenovávání tříd a rozhraní, nazvěte ho MyIeAddon.

V průvodci je potřeba zaškrtnout Allow merging of proxy/stub code, aby se vygeneroval jen jeden projekt.

Prvním krokem je přidání třídy, která tlačítko reprezentuje. Z místní nabídky projektu vyberte Add a poté Class. Následně zvolte položku ATL Simple Object.

Třídu vhodně pojmenujte. Já jsem zvolil název MyButton, aby bylo z ukázek zdrojového kódu hned jasné, o kterou třídu se jedná.

Podporu pro náhled či hledání nechte v průvodci vypnutou. Volbu Aggregation nastavte na No a zaškrtněte IObjectWithSite.

Tak to byla ta lehčí část. Nyní je potřeba přidat několik řádků kódu do hlavičky třídy tlačítka. Tím se definuje rozhraní pro jeho zobrazení v Internet Exploreru.

// 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,
    public CComCoClass,
    public IObjectWithSiteImpl,
    public IDispatchImpl,
    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 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::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;

}

Metoda Exec má na starosti obsluhu stisknutí tlačítka. Metoda QueryStatus určuje, je-li tlačítko povolené, či zakázané. Metoda OnDocumentCom­plete je volána vždy po načtení stránky.

Registrace

Knihovna se musí zaregistrovat, čímž se vlastně nainstaluje do Internet Exploreru. K tomu je potřeba nejprve zjistit patřičná GUID objektů v knihovně. Ty se nacházejí v souboru MyIeAddon.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 souboru MyIeAddon.rgs vložte tento kód a nahraďte v něm použitá GUID za ty vaše. Tím se zaregistruje rozšíření prohlíž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)%%MyIeAddonicon.ico'
            val 'HotIcon' = s '%%ProgramFiles(x86)%%MyIeAddonicon.ico'
          }
        }
      }
    }
  }
}

Tlačítko má pouze jedinou ikonu o rozměrech 16 × 16 px a nelze jí dynamicky měnit. Položky Icon a HotIcon definují její umístění. V cestě je možné uvést systémovou proměnnou. Není to sice na MSDN nikde dokumentováno, ale funguje to. Využijete to pro instalaci x86 doplňku na x64 systémech. Jak proměnnou přidat během instalace na starších systémech, kde není definována, ukážu později.

Jsou ještě další typy doplňků. Vedle rozšíření prohlížeče existuje ještě objekt pomocníka prohlížeče. Zatímco rozšíření prohlížeče je do paměti načteno až když uživatel na tlačítko klikne (proto nejde měnit ikona), pomocník prohlížeče se načítá při jeho spuštění. Třída pro zpracování událostí jako například načtení stránky je tedy lepší registrovat spíše jako objekt pomocníka prohlíž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 doplňku musí implementovat trošku jiné rozhraní. Připravil jsem však ukázku zdrojového kódu tak, že implementuje rozhraní obě. Typ doplňku tedy určuje jen to, jakým způsobem se zaregistruje. Je možné registrovat jednu třídu jako rozšíření prohlížeče i objekt pomocníka prohlížeče.

V některých případech se stává, že se registrace po buildu nepovede. Naštěstí je v přímo popisu chyby popsáno, jak ji vyřešit. Stačí ve vlastnostech projektu povolit Per-user Redirection.

Funkce

Samotná funkcionalita může být napsaná v jazyce C++/CLI, který dovoluje použít knihovny platformy .NET. To sice mírně zvýší dobu potřebnou pro spuštění Internet Exploreru, na druhou stranu není nutné učit se MFC. Úvod do C++/CLI světa sepsal Jakub Čermák.

Rozšíření CLI zavádí jazykové konstrukce nutné pro programování v .NET, například managed třídy. Jejich instance se vytváří příkazem gcnew. Ukazuje se na ně zvláštními pointery a o jejich uvolňování z paměti se stará garbage collector.

Co všechno lze se stránkou dělat bude předmětem dalších dílů. Pro představu jen uvedu, že jde například upravovat DOM stránky nebo číst cookies prohlížeče.

Instalátor

K vytvoření instalátoru se nejvíce hodí InstallShield LE, který lze do Visual Studia doinstalovat. Postará se o zkopírování souborů do správného adresáře a další důležité procedury. Je schopný v případě potřeby stáhnout a nainstalovat .NET Framework.

Vytvoření instalátoru není úplně přímočaré. Problém způsobují dvě složky Program Files na x64 systémech. V souborech k instalaci je potřeba ve vlastnostech instalované knihovny nastavit samočinnou registraci. Ta během instalace přes program regsvr32 registruje knihovnu stejně jako Visual Studio po buildu.

Pokud používáte C++/CLI, zaškrtněte v Redistributables Microsoft .NET Framework Full (Web Download). Tím instalátor doinstaluje .NET Framework 4, pokud ještě v systému není. Dále v položce Cumstom Actions přidejte do události After Register Product nový VBScript.

V něm se může vytvořit systémová proměnná ProgramFiles(x86), pokud v systému ještě definovaná není. To obstará jednoduchý skript ve Visual Basicu:

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

Aktualizace doplňku se provádí opětovnou instalací novější verze. Internet Explorer během ní nesmí být spuštěn.

V dalším díle si ukážeme, jak číst cookie prohlížeče a vytvářet tak doplňky, které mohou nějakým způsobem zpracovat s profilem uživatele. Ten se tak nebude muset přihlašovat zvlášť na webové stránce a zvlášť do doplňku prohlížeče.

Ke stažení

Zdrojový kód příkladu

Komentáře

Subscribe
Upozornit na
guest
16 Komentářů
Nejstarší
Nejnovější Most Voted
Inline Feedbacks
View all comments
Jj

Clanek hezky. :-) Ale ten kod? To se neda cist. Uz nikdy nebudu hazet spinu na Perl.

Karel

jeden čudlík v IE = pouhých 161 řádků kódu :-)

Michal Policansky

Perl je jazyk postaveny na principech ktere jsou pro navrh jazyka naprosto zcestny.
Tohle je WIN API , sice silenost ale je to aspon staticky kompilovany cecko kde vam preklep najde kompilator.

Karel

A vůbec, jděte s IE do zadele. Na webu o moderních webových technologiích nemá článek o IE co dělat :-P

http://duri.myopenid.com/

Tak ho nečítaj a už duplom nekomentuj.

Karel

prudce inteligentní odpověď :D

Martin Malý

Buď to byla dobře skrytá ironie, nebo si nevidíte do úst… :)

Karel

Ale vidím. Já s tím každej den pracuju. Zato ty si nevidíš na špičku nosu.

Martin Malý

Klaním se před silou těchto argumentů. Řadím je hned na druhé místo za „…protože jsem to řekla!“ Gratuluji!

Karel

V čem se liší argument „nevidíš si na špičku nosu“ od argumentu „nevidíš si do úst“? Špatně jste se vyspal, mistře?

bauglir

Wow, tady má někdo mindrák :)

Michal Semerad

Ted si mu to nandal, mistre! Ale kamo, on se muze klidne vyspat lip, ale tobe uz nepomuze nic! Nainstaluj si IE9/IE10 a pak mel neco o tom jestli to sem patri nebo ne.

Čelo

Klid děcka.

Michal Policansky

Dik za dobrej clanek, takovych bych chtel tady cist vice.

Martin Malý

Budete, bude pokračování…

Enum a statická analýza kódu

Mám jednu univerzální radu pro začínající programátorty. V učení sice neexistují rychlé zkratky, ovšem tuhle radu můžete snadno začít používat a zrychlit tak tempo učení. Tou tajemnou ingrediencí je statická analýza kódu. Ukážeme si to na příkladu enum.