Calling into your BHO from a client script

BHO means “Browser Helper Object”, that’s an IE plugin that interacts with browser & user events.
Basically this is a COM component that implements IObjectWithSite and is registered under
“HKLM\Software\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects”

A typical BHO could be a pop-up blocker, customizing at client-side an HTML document.
Some of them are also doing some UI stuffs, like adding menu features or acting as a toolbar
Note however those toolbars & co. don’t always require to be a registered as BHOs.

For a step by step BHO go to Building Browser Helper Objects with Visual Studio 2005.
For a sample step by step toolbar, go to that tutorial .

That being said, we might get a great user experience from IE by reacting to its application...
That could make inside IE a kind of “rich intranet application”, interactions between client side scripts, and extensions.

We can imagine different kind of interaction with that schema (non exhaustive list…):

· Calling a method exposed by your browser extension from JavaScript

· Calling JavaScript method/event from your browser extension

· Modify the HTML / JS code from your browser extension

This post will for now only speak about calling a BHO method from your scripting code, maybe in future posts I’ll describe other possible things with some samples.

To get an IE extensions that changes some user UI (menu items, mouse gesture, etc...), we can easily implement in our BHO IDocHostUIHandler and attach it to current document using ICustomDoc::SetUIHandler()

That trick also allows to “extend” the HTML external object by implementing some custom window.external.foo() methods.

When making that window.external call, the webbrowser control will call into IDocHostUIHandler::GetExternal() to get the UI handler IDispatch interface to invoke
This means you just need to implement it to send back your own interface to make it callable.

The problem I wanted to discuss in that post is that there is no existing design for chaining UIHandlers… Indeed UIHandlers exist to allow extending user interface within a custom application that hosts a WebBrowser control, but not for Internet Explorer itself that already exposes a UI...

IOW from a BHO you might not be able to add your own menu items, but only to replace the all menu.
That’s basically the same for HTML external object, IE also relies on that external object to implement some UI features through IShellUIHelper & IShellUIHelper2 interfaces (like Favorites & find dialogs, or runonce and search providers with IE7).

That kind of problem you might have is described by Q330441 (PRB: ICustomDoc::SetUIHandler Causes Changes in Save As Dialog)

If you replace the UI Handler within IE by your BHO instance (in order to answer window.external() calls), you will break thereby several UI related features, that is a highly non desirable behavior.

In order to workaround that design issue, some people have implemented within their BHO a dispatch gateway towards builtin interfaces IShellUIHelper & IShellUIHelper2.
That was quite easy to do, just chaining within our BHO IDispatch methods GetIDsOfNames() & Invoke() to standard IDispatch shell interfaces and was working pretty well.
Unfortunately for some reasons I would need to discuss longer in a next post, that might not be working anymore as expected on IE7, getting instead an E_FAIL result.

The best way I could recommend to call your BHO from a script is to come back to a supported way...One of the easiest method is to create a simple ActiveX that is called from your script, and fires BHO methods. To do that, we basically only have to get the BHO instance from the ActiveX and to call its methods.

A good method is to rely on IE session to store a variant we’ll know about on each side (using IWebBrowser2::PutProperty() / IWebBrowser2::GetProperty()).

In the code bellow I register my BHO instance in session variable called “MyBHO_IDisp”. That allows my BHO to “register” for later use from ActiveX.

STDMETHODIMP CObjectBHO::SetSite(IUnknown* pUnkSite)

{

       …

       // Registers the IE session to allow ActiveX to call into it

       hr = ReferenceMe(false);

       if (FAILED(hr))

             return S_FALSE;

       …

}

HRESULT CObjectBHO::ReferenceMe(bool bRemove)

{

BSTR bstrThisKey = SysAllocString(L"MyBHO_IDisp");

VARIANT vThis;

HRESULT hr = S_FALSE;

       if (!bRemove)

       {

             if (!m_spWebBrowserApp)

             goto Cleanup;

             // Save this to a variant that will be referenced in IE Session

             VariantInit(&vThis);

             vThis.vt = VT_DISPATCH;

             vThis.pdispVal = static_cast<IDispatch*>(this);

             // Add our this pointer to IE session by adding a named property

             if (FAILED( m_spWebBrowserApp->PutProperty(bstrThisKey, vThis) ))

             goto Cleanup;

       }

       else

       {

             VariantInit(&vThis);

             vThis.vt = VT_NULL;

             // Time to release, remove our reference

             if (FAILED( m_spWebBrowserApp->PutProperty(bstrThisKey, vThis) ))

             goto Cleanup;

       }

       hr = S_OK;

Cleanup:

VariantClear(&vThis);

SysFreeString(bstrThisKey);

       return hr;

}

My ActiveX object now only has to implement IObjectWithSiteImpl::SetSite() to save the client site instance, and to grab the BHO instance from its session properties (GrabBHOInstance())

// CMyClass

class ATL_NO_VTABLE CMyClass :

       public CComObjectRootEx<CComSingleThreadModel>,

       public CComControl<CMyClass>,

       public CComCoClass<CMyClass, &CLSID_MyClass>,

       public IObjectWithSiteImpl <CMyClass>,

       public IDispatchImpl<IMyClass, &IID_IMyClass, &LIBID_MySampleATLLib, /*wMajor =*/ 1, /*wMinor =*/ 0>

{

private:

       IDispatch *m_pBHODisp;

       CComPtr<IUnknown> _spUnkSite;

       HRESULT(GrabBHOInstance)(void);

public:

       CMyClass()

       {

       }

STDMETHODIMP CMyClass::SetSite(IUnknown* pUnkSite)

{

       HRESULT hr = S_FALSE;

       // Save our client site instance

       if (pUnkSite)

       {

             _spUnkSite = pUnkSite;

             hr = S_OK;

       }

       // Try to grab BHO

       GrabBHOInstance();

       return hr;

}

 

Here is the implementation for GrabBHOInstance, just getting property from IWebBrowser2::GetProperty()

HRESULT CMyClass::GrabBHOInstance()

{

IServiceProvider* pISP = NULL;

IWebBrowser2* pBrowser = NULL;

BSTR bstrBHOKey = SysAllocString(L"MyBHO_IDisp");

VARIANT vBHO;

HRESULT hr = S_FALSE;

       VariantInit(&vBHO);

            

       // Get the IWebBrowser2 interface

       if (!_spUnkSite)

             goto Cleanup;

       if (FAILED(_spUnkSite->QueryInterface(IID_IServiceProvider, (void **)&pISP) ))

       goto Cleanup;

       if (FAILED( pISP->QueryService(IID_IWebBrowserApp,

   IID_IWebBrowser2, (void **)&pBrowser)))

             goto Cleanup;

       // Get the BHO instance pointer

       if (FAILED( pBrowser->GetProperty(bstrBHOKey, &vBHO) ))

       goto Cleanup;

       // Ensure it's valid and reference count it

       if (vBHO.vt == VT_DISPATCH && vBHO.pdispVal != NULL)

       {

       m_pBHODisp = vBHO.pdispVal;

       m_pBHODisp->AddRef();

       }

       hr = S_OK;

Cleanup:

VariantClear(&vBHO);

SysFreeString(bstrBHOKey);

if (pBrowser != NULL)

{

pBrowser->Release();

pBrowser = NULL;

}

if (pISP != NULL)

{

pISP->Release();

pISP = NULL;

}

       return hr;

}

STDMETHODIMP CMyClass::get_IsBHOInstalled(VARIANT_BOOL* pVal)

{

       // Check if our BHO instance could be get from the SetSite() call

       if (m_pBHODisp)

             *pVal = true;

       else

             *pVal = false;

       return S_OK;

}

 

A simple ActiveX method now can call into our BHO

STDMETHODIMP CMyClass::SayHelloFromBHO(BSTR* pVal)

{

       CComVariant varResult;

       HRESULT hr = S_FALSE;

       DISPPARAMS params = { NULL, NULL, 0, 0 };

       // Ensure we have the BHO instance propertly got

       if (!m_pBHODisp)

             if (FAILED(GrabBHOInstance()))

                    return hr;

       // Forward to BHO instance if valid

       if (m_pBHODisp)

       {

             hr = m_pBHODisp->Invoke(DISP_MYBHOMETHOD, IID_NULL, LOCALE_USER_DEFAULT,

                    DISPATCH_METHOD, &params, &varResult, NULL, NULL);

                   

             // Return the BSTR we got.

             *pVal = SysAllocStringLen(varResult.bstrVal, SysStringLen(varResult.bstrVal));

       }

       return hr;

}

 

Bellow a sample of the Jscript needed to call your ActiveX, that will fire a method from your browser extension.

var myATL = new ActiveXObject("MySampleATL.MyClass");

if (myATL.IsBHOInstalled)

       alert (myATL. SayHelloFromBHO());

else

       alert ("BHO isn't installed now !");

window.external.AddFavorite("https://blogs.msdn.com/nicd", "Nico’s Blog")

That’s here a quick & modest sample of what can be done in order to integrate your BHO within your client web application.

Maybe in later posts I’ll discuss other possible things with some samples if some people are interested.
I’m now going to holidays for some days, so apologizes in advance if my 3rd post takes some times J

Nico

Comments

  • Anonymous
    May 14, 2007
    The comment has been removed

  • Anonymous
    May 14, 2007
    Do you correctly derive from IObjectWithSiteImpl? Do _spUnkSite & pISP look valid? Are you using that ActiveX from a simple html page or within an important html site (with frames, etc..) You can send me offline your ActiveX code and i'll take a look. Nicolas

  • Anonymous
    May 14, 2007
    The comment has been removed

  • Anonymous
    May 15, 2007
    Nevermind, Nico. I figured it out. I read somewhere that when a custom menu script is invoked, a new instance of the MSHTML is used and hence the IWebBrowser comes as NULL. I tweaked the script to pass in the external.menuArguments to the ActiveX object and get the IWebBrowser2 interface from there and it is all fine. Thanks for your interest in this matter.

  • Kiran
  • Anonymous
    May 15, 2007
    Very good news Kian! Regards Nicolas

  • Anonymous
    August 29, 2007
    Hello! I have a question. only this 2 class and the application run, or has other classes? if possible i want to see the source code. Thanks

  • Anonymous
    March 03, 2010
    Hello! I'm having trouble trying to implement the BHO part, it does register, but when I close a tab or window the IE throws an exception: Access violation reading location 0xdddddde5 If I don't call ReferenceMe this doesn't happen. I'm using IE 8. Any ideas? Thanks

  • Anonymous
    July 01, 2010
    The comment has been removed

  • Anonymous
    November 07, 2010
    Does anyone have a working example of this in C# using SpiceIE ?