Dajbych.net


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

, 8 minut čtení

ie9 logo

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. Využijeme přitom rozšíření CLI jazyka C++, které dokáže zpřístupnit celý .NET. Část doplňku tedy může být napsána v jazyce C#.

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<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;

}

Metoda Exec má na starosti obsluhu stisknutí tlačítka. Metoda QueryStatus určuje, je-li tlačítko povolené, či zakázané. Metoda OnDocumentComplete 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)%%\MyIeAddon\icon.ico'
            val 'HotIcon' = s '%%ProgramFiles(x86)%%\MyIeAddon\icon.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 tačí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é rozhrní. 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 jí 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á neni. 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.

Zdrojový kód příkladu.

Článek byl sepsán pro Zdroják.