Writing a Managed Internet Explorer Extension: Part 2.5

When we last discussed wiring events in part 2, we discussed how events work and how to wire them, and more importantly how to unwire them. I also mentioned that we could use attachEvent and detachEvent rather than the events on interfaces. This is useful if you don’t know what type of element you are attaching an event to.

attachEvent and detachEvent

attachEvent is part of the IHTMLElement2 interface, and fortunately all elements and tags implement this interface, so long as you are targeting Internet Explorer 5.0+. attachEvent takes two parameters, a string indicating which event to attach to, and the actual handler itself. It’s signature looks like this:

attachEvent(
BSTR event, //in
IDispatch *pDisp, //in
VARIANT_BOOL *pfResult //out, retval
);

The IDispatch is the event handler. Unfortunately this means it isn’t as simple as passing a delegate to it and “it just works”. We need to implement a class that will marshal the handler to COM correctly.

Also note, that like Part 2, we need to call detachEvent to stop from memory leaking. Rather than implement the IDispatch interface ourselves, we can use the IReflect interface to give COM marshalling all of the help that it needs. When IDispatch (in this case) invokes the event, it will use the name “[DISPID=0]”. We can handle that in the InvokeMember implementation of IReflect, otherwise we just pass it to our own type. The nice approach to this is it uses a common delegate that we are probably already fimiliar with, EventHandler. In this case, I’ve called the class EventProxy.

public class EventProxy : IReflect
{
private readonly string _eventName;
private readonly IHTMLElement2 _target;
private readonly Action<CEventObj> _eventHandler;
private readonly Type _type;

public EventProxy(string eventName, IHTMLElement2 target, Action<CEventObj> eventHandler)
{
_eventName = eventName;
_target = target;
_eventHandler = eventHandler;
_type = typeof(EventProxy);
}

public IHTMLElement2 Target
{
get { return _target; }
}

public string EventName
{
get { return _eventName; }
}

public void OnHtmlEvent(object o)
{
InvokeClrEvent((CEventObj)o);
}

private void InvokeClrEvent(CEventObj o)
{
if (_eventHandler != null)
{
_eventHandler(o);
}
}

public MethodInfo GetMethod(string name, BindingFlags bindingAttr, Binder binder, Type[] types, ParameterModifier[] modifiers)
{
return _type.GetMethod(name, bindingAttr, binder, types, modifiers);
}

public MethodInfo GetMethod(string name, BindingFlags bindingAttr)
{
return _type.GetMethod(name, bindingAttr);
}

public MethodInfo[] GetMethods(BindingFlags bindingAttr)
{
return _type.GetMethods(bindingAttr);
}

public FieldInfo GetField(string name, BindingFlags bindingAttr)
{
return _type.GetField(name, bindingAttr);
}

public FieldInfo[] GetFields(BindingFlags bindingAttr)
{
return _type.GetFields(bindingAttr);
}

public PropertyInfo GetProperty(string name, BindingFlags bindingAttr)
{
return _type.GetProperty(name, bindingAttr);
}

public PropertyInfo GetProperty(string name, BindingFlags bindingAttr, Binder binder, Type returnType, Type[] types, ParameterModifier[] modifiers)
{
return _type.GetProperty(name, bindingAttr, binder, returnType, types, modifiers);
}

public PropertyInfo[] GetProperties(BindingFlags bindingAttr)
{
return _type.GetProperties(bindingAttr);
}

public MemberInfo[] GetMember(string name, BindingFlags bindingAttr)
{
return _type.GetMember(name, bindingAttr);
}

public MemberInfo[] GetMembers(BindingFlags bindingAttr)
{
return _type.GetMembers(bindingAttr);
}

public object InvokeMember(string name, BindingFlags invokeAttr, Binder binder, object target, object[] args, ParameterModifier[] modifiers, CultureInfo culture, string[] namedParameters)
{
if (name == "[DISPID=0]")
{
OnHtmlEvent(args == null ? null : args.Length == 0 ? null : args[0]);
return null;
}
return _type.InvokeMember(name, invokeAttr, binder, target, args, modifiers, culture, namedParameters);
}

public Type UnderlyingSystemType
{
get { return _type.UnderlyingSystemType; }
}
}

Instances of EventProxy can be handed as the IDispatch to attachEvent. We would use it like so:

private void _webBrowser2Events_DocumentComplete(object pdisp, ref object url)
{
var document = (HTMLDocument)_webBrowser2.Document;
var htmlElements = document.getElementsByTagName("input").OfType<IHTMLElement2>();
foreach (var htmlElement in htmlElements)
{
Action<CEventObj> handler = s => MessageBox.Show("Clicked: " + s.srcElement.id);
var proxy = new EventProxy("onclick", htmlElement, handler);
htmlElement.attachEvent("onclick", proxy);
}
}

Like in part 2, we need to keep a running dictionary of all of the events that we attach, then call detachEvent on BeforeNavigate2. I’ll leave that to you, but at the end of the series I will post the entire working solution in a VS 2010 project.

DocumentComplete Fired Multiple Times

If you use the code from above or from part two, you may notice that clicking an input element causes a dialog to show several times. That is because DocumentComplete is being called more than once. DocumentComplete is fired whenever any document completes, not just the whole thing. So content from an <iframe> will cause the DocumentComplete to get fired again. Sometimes this behavior is desirable, but in this case it is not. How do we ensure it’s only called once for the main document?

The DocumentComplete gives use two things: the URN of the document that was loaded, and a pointer to a dispatch object. Simply put, if the pdisp is the same reference as your IWebBrowser2 instance you setup from SetSite, then it’s the “root” document. This ensures DocumentComplete is only fired once, when the main document is complete:

   1: if (!ReferenceEquals(pdisp, _webBrowser2))


   2: {


   3:     return;


   4: }







As I mentioned in part 2, part 3 will be about manipulating the DOM. I just wanted to cover this first.

Writing a Managed Internet Explorer Extension: Part 2

Continuing my miniseries from Writing a Managed Internet Explorer Extension: Part 1, we discussed how to setup a simple Internet Explorer Browser Helper Object in C# and got a basic, but somewhat useless, example working. We want to interact with our Document Object Model a bit more, including listening for events, like when a button was clicked. I’ll assume that you are all caught up on the basics with my previous post, and we will continue to use the sample solution.

Elements in the HTMLDocument can be accessed by getElementById, getElementsByName, or getElementsByTagName, etc. We’ll use getElementsByTagName, and then filter that based on their “type” attribute of “button” or “submit”.

objexplorerAn issue that regularly comes up with using the generated .NET MSHTML library is its endless web of delegates, events, and interfaces. Looking at the object explorer, you can see that there are several delegates per type. This makes it tricky to say “I want to handle the ‘onclick’ event for all elements.” You couldn’t do that because there is no common interface they all implement with a single onclick element. However, if you are brave you can let dynamic types in .NET Framework 4.0 solve that for you. Otherwise you will have a complex web of casting ahead of you.

Another issue that you may run into is conflicting member names. Yes, you would think this isn’t possible, but the CLR allows it, I just don’t believe C# and VB.NET Compiles allow it. For example, on the interface HTMLInputElement, there is a property called “onclick” and an event called “onclick”. This interface will not compile under C# 4:

   1: public interface HelloWorld


   2: {


   3:     event Action HelloWorld;


   4:     string HelloWorld { get; } 


   5: }

However, an interesting fact about the CLR is it allows methods and properties to be overloaded by the return type. Crazy, huh? Here’ is some bare bones MSIL you can compile on your own using ilasm to see it in action:

   1: .assembly extern mscorlib


   2: {


   3:   .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )


   4:   .ver 4:0:0:0


   5: }


   6:  


   7: .module MislExample.dll


   8: .imagebase 0x00400000


   9: .file alignment 0x00000200


  10: .stackreserve 0x00100000


  11: .subsystem 0x0003


  12: .corflags 0x0000000b


  13:  


  14: .class interface public abstract auto ansi MislExample.HelloWorld


  15: {


  16:   .method public hidebysig newslot specialname abstract virtual 


  17:           instance void  add_HelloWorld


  18:             (class [mscorlib]System.Action 'value') cil managed


  19:   {


  20:   }


  21:  


  22:   .method public hidebysig newslot specialname abstract virtual 


  23:           instance void  remove_HelloWorld


  24:             (class [mscorlib]System.Action 'value') cil managed


  25:   {


  26:   }


  27:  


  28:   .method public hidebysig newslot specialname abstract virtual 


  29:           instance string  get_HelloWorld() cil managed


  30:   {


  31:   }


  32:  


  33:   .event [mscorlib]System.Action HelloWorld


  34:   {


  35:     .addon instance void MislExample.HelloWorld::


  36:             add_HelloWorld(class [mscorlib]System.Action)


  37:     .removeon instance void MislExample.HelloWorld::


  38:             remove_HelloWorld(class [mscorlib]System.Action)


  39:   }


  40:   .property instance string HelloWorld()


  41:   {


  42:     .get instance string MislExample.HelloWorld::get_HelloWorld()


  43:   }


  44: }

That MSIL isn’t fully complete as it lacks any sort of manifest, but it will compile and .NET Reflector will be able to see it. You might have trouble referencing it from a C# or VB.NET project.

You can work around this issue by being explicit in this case: cast it to the interface to gain access to the event or do something clever with LINQ:

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


   2: {


   3:     HTMLDocument document = _webBrowser2.Document;


   4:     var inputElements = from element in document.getElementsByTagName("input").Cast<HTMLInputElement>()


   5:                     select new { Class = element, Interface = (HTMLInputTextElementEvents2_Event)element };


   6:     foreach (var inputElement in inputElements)


   7:     {


   8:         inputElement.Interface.onclick += inputElement_Click;


   9:     }


  10: }


  11:  


  12: static bool inputElement_Click(IHTMLEventObj htmlEventObj)


  13: {


  14:     htmlEventObj.cancelBubble = true;


  15:     MessageBox.Show("You clicked an input element!");


  16:     return false;


  17: }

This is pretty straight forward: whenever the document is complete, loop through all of the input elements and attach on onclick handler to it. Despite the name of the interface, this will work with all HTMLInputElement objects.

Great! We have events wired up. Unfortunately, we’re not done. This appears to work at first try. However, go ahead and load the add on and use IE for a while. It’s going to start consuming more and more memory. We have written a beast with an unquenchable thirst for memory! We can see that in Son of Strike, too.

MT Count TotalSize Class Name
03c87ecc 3502 112064 mshtml.HTMLInputTextElementEvents2_onclickEventHandler
06c2aac0 570 9120 mshtml.HTMLInputElementClass

This is a bad figure, because it is never going down, even if we Garbage Collect. With just a few minutes of use of Internet Explorer, there is a huge number of event handles. The reason being because we never unwire the event handler, thus we are leaking events. We need to unwire them. Many people have bemoaned this problem in .NET: event subscriptions increment the reference count. Many people have written Framework wrappers for events to use “Weak Events”, or events that don’t increment the reference count. Both strong and weak reference have their advantages.

I’ve found the best way to do this is to keep a running Dictionary of all the events you subscribed to, and unwire them in BeforeNavigate2 by looping through the dictionary, then removing the element from the dictionary, allowing it to be garbage collected.

Here is my final code for unwiring events:

   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:     private IWebBrowser2 _webBrowser2;


   8:     private DWebBrowserEvents2_Event _webBrowser2Events;


   9:     private readonly Dictionary


  10:         <


  11:             HTMLInputTextElementEvents2_onclickEventHandler,


  12:             HTMLInputTextElementEvents2_Event


  13:         > _wiredEvents


  14:         = new Dictionary


  15:         <


  16:             HTMLInputTextElementEvents2_onclickEventHandler,


  17:             HTMLInputTextElementEvents2_Event


  18:         >();


  19:  


  20:     public int SetSite(object pUnkSite)


  21:     {


  22:         if (pUnkSite != null)


  23:         {


  24:             _pUnkSite = pUnkSite;


  25:             _webBrowser2 = (IWebBrowser2)pUnkSite;


  26:             _webBrowser2Events = (DWebBrowserEvents2_Event)pUnkSite;


  27:             _webBrowser2Events.DocumentComplete += _webBrowser2Events_DocumentComplete;


  28:             _webBrowser2Events.BeforeNavigate2 += _webBrowser2Events_BeforeNavigate2;


  29:         }


  30:         else


  31:         {


  32:             _webBrowser2Events.DocumentComplete -= _webBrowser2Events_DocumentComplete;


  33:             _webBrowser2Events.BeforeNavigate2 -= _webBrowser2Events_BeforeNavigate2;


  34:             _pUnkSite = null;


  35:         }


  36:         return 0;


  37:     }


  38:  


  39:     void _webBrowser2Events_BeforeNavigate2(object pDisp, ref object URL, ref object Flags,


  40:         ref object TargetFrameName, ref object PostData, ref object Headers, ref bool Cancel)


  41:     {


  42:         foreach (var wiredEvent in _wiredEvents)


  43:         {


  44:             wiredEvent.Value.onclick -= wiredEvent.Key;


  45:         }


  46:         _wiredEvents.Clear();


  47:     }


  48:  


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


  50:     {


  51:         HTMLDocument document = _webBrowser2.Document;


  52:         var inputElements = from element in document.getElementsByTagName("input").Cast<HTMLInputElement>()


  53:                             select new { Class = element, Interface = (HTMLInputTextElementEvents2_Event)element };


  54:         foreach (var inputElement in inputElements)


  55:         {


  56:             HTMLInputTextElementEvents2_onclickEventHandler interfaceOnOnclick = inputElement_Click;


  57:             inputElement.Interface.onclick += interfaceOnOnclick;


  58:             _wiredEvents.Add(interfaceOnOnclick, inputElement.Interface);


  59:         }


  60:     }


  61:  


  62:     static bool inputElement_Click(IHTMLEventObj htmlEventObj)


  63:     {


  64:         htmlEventObj.cancelBubble = true;


  65:         MessageBox.Show("You clicked an input!");


  66:         return false;


  67:     }


  68:  


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


  70:     {


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


  72:         try


  73:         {


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


  75:         }


  76:         finally


  77:         {


  78:             Marshal.Release(pUnk);


  79:         }


  80:     }


  81: }





After performing the same level of stress as before, there were only 209 instances of HTMLInputTextElementEvents2_onclickEventHandler. That is still a bit high, but it’s because the Garbage Collector done it’s cleanup. The Garbage Collector makes it a bit subjective to counting how many objects are in memory. If we really cared we could check and see which of those have a reference count greater than zero to get the full result, but I think that’s above the call of duty, right?



There are alternative ways to wire events. If the strong typing and plethora of interfaces is getting to you, it’s possible to use attachEvent and detachEvent albeit it requires converting these events into objects that COM can understand.



Part 3 we will look into manipulating the DOM.

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.