my raw header!
Welcome to MSDN Blogs Sign in | Join | Help

Nico's WebLog

Internet Development stuffs IIS, ISAPI, .NET, Debugging, Networking, ...

News

  • These postings are provided "AS IS" with no warranties, and confers no rights. The content of this site are personal opinions and do not represent the Microsoft corporation view in anyway. In addition, thoughts and opinions often change. Because a weblog is intended to provide a semi-permanent point-in-time snapshot, you should not consider out of date posts to reflect current thoughts and opinions.
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("http://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

 

Posted: Wednesday, April 18, 2007 12:12 AM by nicd
Filed under: ,

Comments

kirants said:

I have followed the steps you mention, however, in my ActiveX class' SetSite, the following line fails with E_NOINTERFACE

pISP->QueryService(IID_IWebBrowserApp,   IID_IWebBrowser2, (void **)&pBrowser)

Any ideas ?

One thing I want to mention is that, the BHO class and the Activex class are implemented in the same DLL. Would that matter ?

Also, I am using IE7 on XP

Thanks,

Kiran

# May 14, 2007 6:23 PM

nicd said:

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

# May 14, 2007 6:35 PM

kirants said:

Hi Nico,

Thanks a lot for following up.

This is something interesting I found.

See below:

1. I implemented an activex control class and a BHO class in the same DLL.

2. I implemented IObjectWithSite SetSite and the Service provider query to get the IWebBrwoser2 interface in both BHO and Activex control class. The same code is used in both places like below:

CComQIPtr<IServiceProvider> isp = pUnkSite;

CComQIPtr<IWebBrowser2> iwb2;

isp->QueryService(IID_IWebBrowserApp,IID_IWebBrowser2, (void**)&iwb2);

3. I register this dll.

4. I wrote a sample HTML with the following code:

<HTML>

<HEAD>

</HEAD>

<SCRIPT LANGUAGE="JavaScript">

var oShell = new ActiveXObject("TestJavaActivex.JavaActivexClass");

</SCRIPT>

<BODY>

</BODY>

</HTML>

5. I launch IE. The BHO is loaded and iwb2 is valid for the BHO class' SetSite

6. I browse and open the HTML file mentioned above. The activex control class object is instantiated and the SetSite for the control shows iwb2 as valid.

All is fine. Now, I want to customize the right mouse menu for IE and so I add the following registry entries:

[HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\MenuExt\CustomMenu]

@="C:\\tmp\\MenuScript.html"

"Contexts"=dword:00000001

where menuscript.html is like below:

<SCRIPT LANGUAGE="JavaScript">

var oShell = new ActiveXObject("TestJavaActivex.JavaActivexClass");

</SCRIPT>

Note: This is no different from the HTML code earlier.

7. I launch IE again. The BHO is loaded and iwb2 is valid for the BHO class' SetSite

8. I right click and choose 'CustomMenu' item. The activex control class object is instantiated and the SetSite for the control is called, however, now iwb2 is NULL.

I can send you the code, but it is pretty much bare bones ATL project. If it helps in me sending the code your way, please let me know how I can send it offline.

Thanks,

Kiran

# May 14, 2007 7:41 PM

kirants said:

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

# May 15, 2007 7:44 PM

nicd said:

Very good news Kian!

Regards

Nicolas

# May 16, 2007 4:28 AM

kovacs said:

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

# August 30, 2007 4:25 AM
Anonymous comments are disabled
Page view tracker