Writing a Managed Internet Explorer Extension: Part 1

I’ve recently had the pleasure of writing an Internet Explorer add on. I found this to somewhat difficult for a few reasons and decided to document my findings here.

Managed vs Native

One difficult decision I had to make even before I had to write a single line of code was what do I write it with? I am a C# developer, and would prefer to stay in that world if possible. However, this add-on had the intention of being use commercially, and couldn’t make the decision solely based on preference.

Add-on’s to Internet Explorer are called Browser Helper Objects, often documented as BHOs as well. They are COM types, thus if we were going to do this managed, we will be doing some COM Interop. I’ve done this before, but mostly from a level of tinkering or deciding to go back to native. The .NET Framework had another benefit to me, and that was WPF. My BHO requires an user interface, and doing that natively isn’t as easy or elegant as using native libraries. Ultimately I decided to go with .NET Framework 4.0, and I can only recommend the .NET Framework 4.

Previous versions of the CLR has a serious drawback when exposing the types to COM: They always used the latest version of the CLR on the machine. If you wrote a BHO in the .NET Framework 1.1, and 2.0 was installed, it would load the assembly using the .NET Framework 2.0. This can lead to unexpected behavior. Starting in the .NET Framework 4, COM Visible types are guaranteed to run against the CLR they were compile with.

The Basics of COM and IE

Internet Explorer uses COM as it’s means of extending its functionality. Using .NET, we can create managed types and expose them to COM and Internet Explorer would be non-the-wiser. COM heavily uses Interfaces to provide functionality. Our BHO will be a single class that implements a COM interface. Let’s start by making a single C# Class Library in Visual Studio. Before we can start writing code, we need to let the compiler know we will be generating COM types. This is done by setting the “Register Assembly for COM Interop” in our project settings on the “Build” tab. While you are on the Build tab, change the Platform target to “x86” as we will only be dealing with 32-bit IE if you are running a 64-bit OS. Now that’s out of the way, let’s make our first class. We’ll call our class BHO.

   1: namespace IeAddOnDemo


   2: {


   3:     public class BHO


   4:     {


   5:     }


   6: }

By itself, this class is not useful at all, and nor can COM do anything with it. We need to let COM know this type is useful to it with a few key attributes. The first is ComVisibleAttribute(true). This attribute does exactly what it looks like. The next is GuidAttribute. This is important because all COM types have a unique GUID. This must be unique per-type per application. Just make your own in Visual Studio by clicking “Tools” and “Create GUID”. Finally there is the ClassInterfaceAttribute which will be set to None. Optionally, you can set the ProgIdAttribute if you want. This allows you to specify your own named identifier that will be used when the COM type is registered. Otherwise it’s your class name. Here is what my class looks like now:

   1: [ComVisible(true),


   2: Guid("9AB12757-BDAF-4F9A-8DE8-413C3615590C"),


   3: ClassInterface(ClassInterfaceType.None)]


   4: public class BHO


   5: {


   6: }

So now our type can be registered, but it isn’t useful to IE. Our class needs to implement an interface that IE always expects all BHO’s to implement: IObjectWithSite. This is an already existing COM interface that we will re-define in managed code, but will let the CLR know it’s actually a COM interface through a series of attributes.

   1: [ComImport,


   2: Guid("FC4801A3-2BA9-11CF-A229-00AA003D7352"),


   3: InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]


   4: public interface IObjectWithSite


   5: {


   6:     [PreserveSig]


   7:     int SetSite([In, MarshalAs(UnmanagedType.IUnknown)]object pUnkSite);


   8:     [PreserveSig]


   9:     int GetSite(ref Guid riid, out IntPtr ppvSite);


  10: }

This you can directly copy and paste into your project. Make sure you don’t change the GUID, either. This GUID is already defined by Internet Explorer. The ComImport attribute indicates that this interface is a COM interface. The InterfaceTypeAttribute is important. All COM interfaces we will be working is is InterfaceIsIUnknown. All COM interfaces implement a basic interface. In this case, IObjectWithSite implement IUnknown. We won’t actually be doing anything with this interface though that .NET can’t already do for us with the help of the Marshal class.

The GetSite and SetSite methods will be automatically called by Internet Explorer, we just need to provide the implementation. SetSite is of the most interest to us. pUnkSite is will be another IUnknown interface. Since we won’t be using the IUnknown interface, we’ll just use object instead and be happy with that. We’ll add the IObjectWithSite to our BHO class.

Before that, we need to add a few references to our project. Bring up the Add Reference dialog, and switch over to the COM tab. We’ll be adding these:

  • Microsoft HTML Object Library
  • Microsoft Internet Controls

.NET will automatically generate the .NET wrappers for us rather than having to declare all of them by hand like we did with IObjectWithSite. These libraries contain the useful parts of Internet Explorer that allow us to do cool things, like manipulate the Document Object Model of a page. Now let’s add our interface.

   1: [ComVisible(true),


   2: Guid("9AB12757-BDAF-4F9A-8DE8-413C3615590C"),


   3: ClassInterface(ClassInterfaceType.None)]


   4: public class BHO : IObjectWithSite


   5: {


   6:     private object _pUnkSite;


   7:     public int SetSite(object pUnkSite)


   8:     {


   9:         _pUnkSite = pUnkSite;


  10:         return 0;


  11:     }


  12:  


  13:     public int GetSite(ref Guid riid, out IntPtr ppvSite)


  14:     {


  15:         var pUnk = Marshal.GetIUnknownForObject(_pUnkSite);


  16:         try


  17:         {


  18:             return Marshal.QueryInterface(pUnk, ref riid, out ppvSite);


  19:         }


  20:         finally


  21:         {


  22:             Marshal.Release(pUnk);


  23:         }


  24:     }


  25: }

The GetSite is a fairly vanilla implementation that can be used for all BHOs. Internet Explorer will call GetSite with an interface GUID. We defer this back to our object from SetSite. SetSite gives us an object, but it isn’t just any plain ‘ol boring object. It actually implements a bunch of cool interfaces, like IWebBrowser2 and DWebBrowserEvents2_Event. SetSite is usually called twice by IE. When pUnkSite is not null, it’s an object that is IE itself. When pUnkSite is null, it means IE is shutting down and we need to our cleanup. We can cast our pUnkSite to those two interfaces which are in the COM libraries we referenced earlier. The return value in this case should always be S_OK, or 0. With IWebBrowser2 we can manipulate the DOM, and DWebBrowserEvents2_Event we can listen for events. Let’s add some simple functionality: whenever a page is loaded, let’s display a message box with the title. Here is what my final code looks like:

   1: using System;


   2: using System.Runtime.InteropServices;


   3: using System.Windows;


   4: using mshtml;


   5: using SHDocVw;


   6:  


   7: namespace IeAddOnDemo


   8: {


   9:     [ComVisible(true),


  10:     Guid("9AB12757-BDAF-4F9A-8DE8-413C3615590C"),


  11:     ClassInterface(ClassInterfaceType.None)]


  12:     public class BHO : IObjectWithSite


  13:     {


  14:         private object _pUnkSite;


  15:         private IWebBrowser2 _webBrowser2;


  16:         private DWebBrowserEvents2_Event _webBrowser2Events;


  17:         public int SetSite(object pUnkSite)


  18:         {


  19:             if (pUnkSite != null)


  20:             {


  21:                 _pUnkSite = pUnkSite;


  22:                 _webBrowser2 = (IWebBrowser2)pUnkSite;


  23:                 _webBrowser2Events = (DWebBrowserEvents2_Event)pUnkSite;


  24:                 _webBrowser2Events.DocumentComplete += _webBrowser2Events_DocumentComplete;


  25:             }


  26:             else


  27:             {


  28:                 _webBrowser2Events.DocumentComplete -= _webBrowser2Events_DocumentComplete;


  29:                 _pUnkSite = null;


  30:             }


  31:             return 0;


  32:         }


  33:  


  34:         void _webBrowser2Events_DocumentComplete(object pDisp, ref object URL)


  35:         {


  36:             HTMLDocument messageBoxText = _webBrowser2.Document;


  37:             MessageBox.Show(messageBoxText.title);


  38:         }


  39:  


  40:         public int GetSite(ref Guid riid, out IntPtr ppvSite)


  41:         {


  42:             var pUnk = Marshal.GetIUnknownForObject(_pUnkSite);


  43:             try


  44:             {


  45:                 return Marshal.QueryInterface(pUnk, ref riid, out ppvSite);


  46:             }


  47:             finally


  48:             {


  49:                 Marshal.Release(pUnk);


  50:             }


  51:         }


  52:     }


  53: }

Notice that in SetSite, if pUnkSite is null, I remove the event wireup. This is required, otherwise pUnkSite won’t get released properly and IE is likely to crash when a user tries to close it.

Registering

We have our code now, but how do we get IE to do anything with the assembly? First we need to register it. The .NET Framework comes with a tool called regasm. This will register our .NET Assembly like it were a COM library. Before we can do that, we need to add a strong name to our assembly. If you don’t strong name sign the assembly, the regasm is going to complain.

What you will want to do now is open the Visual Studio Command Prompt found in your start menu along with Visual Studio. You’ll want to run it as an administrator, too and change your working directory to your project’s output. Then call regasm like this:

   1: regasm.exe /register /codebase IeAddOnDemo.dll



If all goes well, you will see “Types registered successfully”. Let’s verify. Open up your registry by running regedit.exe and looking under HKEY_CLASSES_ROOT\CLSID. Remember the GUID you used in the attribute for BHO? It should be under there. In my example, I should see HKEY_CLASSES_ROOT\CLSID\{9AB12757-BDAF-4F9A-8DE8-413C3615590C}. Low and behold, I do.



Note that if you are using a 64-bit operating system, you will see it under HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{your guid here}. That’s OK and expected.



We have the COM class registered, but IE still doesn’t know it’s there. We need to add it to the list of BHO’s. They live under this key:



HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\explorer\Browser Helper Objects



or this for x64 machines:



HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\explorer\Browser Helper Objects



Note that they key “Browser Helper Objects” may not exist if there has never been a BHO installed on the machine. If it’s not there, go ahead and create it.



Finally, create a sub key under the “Browser Helper Objects” using the same GUID that was registered. Make sure to include the curly braces like you saw earlier under CLSID. So now I have a key path:



HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\explorer\Browser Helper Objects\{9AB12757-BDAF-4F9A-8DE8-413C3615590C}



If you want, you can set the default value of the key to a string of your choice to make it more identifiable in the registry. Lastly, you will want to create a DWORD under the registry called NoExplorer with a value of 1. This stops Windows Explorer from loading the Add On and limiting it to just Internet Explorer. I haven’t tested my add on with Windows Explorer so I have no idea if this procedure works for it. Now go ahead and start IE, and if all went according to plan you will see this:



bingalert



Looking under “Tools” and “Manage Add Ons” we see our BHO is listed there.



 



If you want to unregister your add on, simply do the following:



  1. Delete your BHO registration. In my case it’s key “HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Microsoft\Windows\CurrentVersion\explorer\Browser Helper Objects\{9AB12757-BDAF-4F9A-8DE8-413C3615590C}”
  2. Call regasm like we did before, except use /unregister rather than /register.


The next blog post will cover listening to DOM events, such as when a button is clicked.

20 thoughts on “Writing a Managed Internet Explorer Extension: Part 1”

  1. Thanks for this useful article.

    I’d like to add that in Visual Studio 2010 you don’t have to use regasm.exe, VS will register assembly for you (you must run VS as administrator). To unregister simply uncheck “Register Assembly for COM Interop” and rebuild the solution.

  2. @Robert – You are correct. The simple act of compiling in Visual Studio (I think that goes all the way back to 2003) will do the regasm bit for you. I provided the instructions for testing purposes (say on another machine), or if someone wanted to make an Installer (before I get to that).

  3. I am implementing your example in .net 3.5… also i am getting error at..

    _webBrowser2Events.DocumentComplete += _webBrowser2Events_DocumentComplete;

    and also at

    _webBrowser2Events.DocumentComplete -= _webBrowser2Events_DocumentComplete;

    what is the problem… help me… i am bit worried

  4. Hi there. Thanks very much for the detailed tutorials. Only read part 1 so far but they’re proving very useful. One quick correction I’d like to make is in _webBrowser2Events_DocumentComplete, where the line:

    HTMLDocument messageBoxText = _webBrowser2.Document;

    Should read:

    HTMLDocument messageBoxText = (HTMLDocument) _webBrowser2.Document;

    A minor thing, I know, and VS2008 will alert you to it, but I thought it might catch people out.

    Thanks again for the tutorials! I’m building against IE9 and everything seems to apply as written.

  5. I’m developing a BHO in SharpDevelop, I register de DLL ok with RegAsm but the BHO doesn’t execute the DocumentComplete method. I have Attached It to iexplorer but nothing happen, I test a breakpoint in the Constructor but nothing happen….

    please help me and thanks for your post!!

    From Argentina!, cheers and sorry for my poor english :)

  6. how can I make an installer for this kind of extension, so that I just need to carry only 1 exe or msi file with me?
    Thanks!

  7. I implement same code as specified above but I can not get the Messagebox specified in the Screenshot.

    I can see the BHO in “Manage Addon”. So what I’m missing? can you please help me on that?

  8. I run into a error in compiling the code in vs2010, as follows
    Error 1 Attribute ‘ClassInterface’ is not valid on this declaration type. It is only valid on ‘assembly, class’ declarations. c:\users\franklin\documents\visual studio 2010\Projects\ArticleURLCapture\ArticleURLCapture\URLManager.cs 11 7 ArticleURLCapture
    Does anybode knows how to resolve this?

  9. You are much more experienced in building BHO with VS2010 than I am. Right now I try to compile the sample code from the part 1 sample code above.

    I have compile problem at ClassInterface line below after clicking build button

    9: [ComVisible(true),
    10: Guid(“9AB12757-BDAF-4F9A-8DE8-413C3615590C”), 11: ClassInterface(ClassInterfaceType.None)] The error says: ClassInterface is not valid on this declaration type, only valid for ‘assembly, class’ declaration

    What would you suggest to solve this compile problem. I followed everything in the reference above, but got stuck there. Thanks

    very much.

  10. can u please tell me how to implement GUI for this [A small screen for entering some data to process the Data in browser]

  11. very good article, can u please tell me how to impelment a small GUI with this application.
    [I want to create a small form for entering some values and process data in browser]

  12. This article would be great except, for me, I need a Visual Basic version of it. Is there anywhere I can read how to do this in VB?

  13. You could programmatically do the registration with ComRegisterFunction and ComUnregisterFunction attributes instead of opening regedit.

  14. Man, this is really amazing!
    Thank you for this article. I follow the steps and work perfectly.

    Just wanted to know if it is possible to create an installer for this project?

    It is possible? Make an installer that make automatic dll registering and necessary regedit.exe settings?

  15. I have implement this example, I saw the key in the registry but the pop up is not coming when I get started my IE 10.

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>