Building Browser Helper Objects using Managed Code

Over the past few days I’ve been exploring the extensibility of IE7 from a user experience perspective.  One of the options for creating an Add-On for IE is something called the Browser Helper Object (BHO for short).  With a BHO you can trap a variety of events in Internet Explorer (such as browsing for a page, or when the user quits the browser), examine the results and take actions as required.  This powerful feature allows you to build UIs that really integrate with the browsing experience.

How do I write one in .NET?

BHOs sound great, but the question is – how do I write one in .NET?  Although the BHO interface is COM-based, it’s not as hard as it sounds.  Firstly, you must create a class library in .NET and add the following COM references:


[ComImport(), Guid("fc4801a3-2ba9-11cf-a229-00aa003d7352")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IObjectWithSite
{
void SetSite([In ,MarshalAs(UnmanagedType.IUnknown)] object pUnkSite);
void GetSite(ref Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out object ppvSite);
}

This interface provides the GetSite and SetSite for hooking into events within IE.  After you have these defined, create a class that overrides IObjectWithSite:

[ClassInterfaceAttribute(ClassInterfaceType.None)]
[GuidAttribute(MyGuid)]
[ProgIdAttribute("MyNamespace.MyBHO")]
public class MyBHO: IObserver, IObjectWithSite
{

}

In this class you’ll need to define a browser class object and browser events object from the Internet Controls library (VS.NET will create an Interop assembly called Interop.SHDocVw when you import this):

protected SHDocVw.IWebBrowser2 m_pIWebBrowser2;
protected SHDocVw.DWebBrowserEvents2_Event m_pDWebBrowserEvents2;

With these defined, you now override GetSite and SetSite, initializing the Internet Controls and enabling the events:

public void SetSite(object pUnkSite)
{

if (m_pIWebBrowser2!=null)
Release();

if (pUnkSite==null)
return;

m_pIWebBrowser2 = pUnkSite as SHDocVw.IWebBrowser2;  // set the reference

if (!(m_pIWebBrowser2.FullName.ToUpper().EndsWith(“IEXPLORE.EXE”)))  //make sure only runs on IE
{
Release();
return;
}

m_pDWebBrowserEvents2 = m_pIWebBrowser2 as SHDocVw.DWebBrowserEvents2_Event ;

if (m_pDWebBrowserEvents2 != null)
{
m_pDWebBrowserEvents2.BeforeNavigate2 += new SHDocVw.DWebBrowserEvents2_BeforeNavigate2EventHandler(BeforeNavigate);
m_pDWebBrowserEvents2.OnQuit += new SHDocVw.DWebBrowserEvents2_OnQuitEventHandler(OnQuit);
}
else
{

Release();
return;
}
}

 

public void GetSite(ref System.Guid riid, out object ppvSite)
{
ppvSite=null;
if (m_pIWebBrowser2 != null)
{
IntPtr pSite = IntPtr.Zero;
IntPtr pUnk = Marshal.GetIUnknownForObject(m_pIWebBrowser2);
Marshal.QueryInterface(pUnk, ref riid, out pSite);

Marshal.Release(pUnk);
if (!pSite.Equals(IntPtr.Zero))
{
ppvSite = pSite;
}
else
{
Release();
Marshal.ThrowExceptionForHR(E_NOINTERFACE);
}
}
else
{
Release();
Marshal.ThrowExceptionForHR(E_FAIL);
}
}

As you can see in the SetSite method, you can now define the hooks within Internet Explorer:

m_pDWebBrowserEvents2.BeforeNavigate2 += new SHDocVw.DWebBrowserEvents2_BeforeNavigate2EventHandler(BeforeNavigate);
m_pDWebBrowserEvents2.OnQuit += new SHDocVw.DWebBrowserEvents2_OnQuitEventHandler(OnQuit);

These can point to your own methods that can act on these events accordingly (e.g. launch a particular process with a given URL, rewrite URLs etc.)

How to install a BHO using an MSI

One of the challenges is not building a BHO, but getting it to install/uninstall reliably through an MSI.  To run correctly, a BHO requires registration via COM interop, but setting the vsdrpCOM property in the MSI isn’t enough (this results in a successful registration most of the time, but the add-in won’t remove itself at uninstall).

My recommendation is to set the MSI property to vsdrpNoDotRegister and instead create your own registration component.  This can be a simple command prompt executable that you run as a custom action (for both install and uninstall) in the MSI. 

To register the assembly, use the following code within your registration code:

System.Runtime.InteropServices.RegistrationServices r = new System.Runtime.InteropServices.RegistrationServices();
r.RegisterAssembly(System.Reflection.Assembly.LoadFile(bhoDll), System.Runtime.InteropServices.AssemblyRegistrationFlags.SetCodeBase);

To unregister the assembly, you use the UnregisterAssembly method:

System.Runtime.InteropServices.RegistrationServices r = new System.Runtime.InteropServices.RegistrationServices();
r.UnregisterAssembly(System.Reflection.Assembly.LoadFile(bhoDll));

UAC Considerations for Vista

This approach works all very well for XP, but if you are installing your BHO on Vista you’ll need to set the user access control correctly.  One of the problems is that custom actions in MSIs run as an non-elevated user, which means that unless you are running the MSI as elevated your custom action won’t have the permissions required to register the BHO.  I haven’t found a way to elevate a user manually from a piece of managed code, but you can overcome this using one of two ways:

1.  Write a batch file that contains the following:

msiexec /i MyMSI.msi

…and right click on the batch file, and select “Run as Administrator”

2.  Use the ORCA tool (part of the Windows SDK) to set the custom actions to run as SYSTEM.

To do this, download the SDK, install and run ORCA and open your MSI.  Open the custom action from the left hand pane, select your custom action and set the type to 3090.  This will cause the custom action to run as the SYSTEM account and registration will be successful.

Examples

Finally, if you are looking for a great example of a managed BHO (and more details of how one works), check out this post from Michele Leroux Bustamante.  Here, Michele shows how to use a BHO for observing user behavior in IE, with downloadable code that you can build your own from.

10 thoughts on “Building Browser Helper Objects using Managed Code

  1. Kent Sharkey

    There is a slight danger in creating BHOs with .NET — each process can only load a single instance/version of the Framework. Unless you only need the one, or all the BHOs you want to load are built to support the same version of the runtime, one could cause others to fail to load.

    Reply
  2. http://

    Hi,
    i’m developing an addon for IE7.
    Do you know where can i find the list of events i can hook into in IE7?
    i need to get an event which alerts me on tab change…

    thanks,
    Daniel

    Reply
  3. http://

    Will it work in IE Protected Mode in Vista? Means UAC is enabled and IE runs in Protected Mode. In this case, IExplore.exe calls ieuser.exe to open the URL in Vista. And hence Dll does not seems to get Navigate event…

    Please comment as soon as possible.

    Reply
  4. http://

    Hi, i followd the instruction but it doesn’t work, setsite is never called…

    I use c# and .net 2.0 and ie6 sp2

    in my setsite i just put messagebox.show(“hello”) but nothing happens

    can someone help me please?

    Reply
  5. http://

    Hello Simon,

    I saw your that you have experience in creating AddOns for IE.

    We are building a an AddOn for IE which would display meaning in different languages. Our AddOn is working fine in IE6 and funtionality is also working fine in IE7 but IE7 crashes everytime we click on dropdown.

    You can see the exact problem here:

    http://forums.microsoft.com/msdn/ShowPost.aspx?postid=1647690&siteid=1

    I would appreciated if you can help us.

    Thank you in advance :)

    Nemesh

    Reply
  6. http://

    Hi,

    I arrived a little late at this post but I’m trying to build an IE Addon that insert some lines of javascript inside each page for a particular website. As I’m a .NET experienced Developer I want to do it with .NET and BHO. Can you give some ideas on how to do it? I start looking at BHO but I can not find the way to do what I want.

    Hope to hear from you soon. If you don’t mind mail me to fernaramburu@hotmail.com

    thanks a lot.

    Fernando

    Reply
  7. Christopher Painter

    The MSI problems you are seeing are really problems with Visual Studio Deployment Projects. Using a better tool like InstallShield 2008 or Windows Installer XML to generate your MSI packages would allow you to skip DLL self-registration and instead use the COM and/or Registry tables without needing to invoke a custom action. Regasm gives you all the information you need to do this if using InstallShield you simply say `.NET COM Interop=True`.

    Also

    Reply
  8. http://

    This post was very very helpful. It is by far the most useful tutorial I have found so far. It might be helpful to some though: To get a guid, there is a tool in visual studio (Tools > Create GUID) which generates this. I dont know exactly how it works, but I believe the algorithm is based on time, so the id is able to be unique no matter what system it is on. I know I had alot of trouble getting passed this idea because all of the com tutorials, and bho tutorials just assume everyone knows this. I get stuck on little details like this and cant move on until I figure it out :).

    Reply
  9. http://

    I am receiving the below Exception while using SHDocVw in Vista Proteted Mode ON.

    “Retrieving the COM class factory for component with CLSID {9BA05972-F6A8-11CF-A442-00A0C90A8F39} failed due to the following error: 80070002.”

    Any Idea?

    Reply
  10. http://

    I am getting the same problems as stevanus. The component shows up under the Add-Ons in the IE, but this is not getting called. Can somebody help?

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>