Most IE extensibility points are, in many ways, separated from the steps and transactions that take place behind the scenes of a page load or the display of the browser frame. Most extensions are used to control a piece of the browser's UI, whether it be a part of a page or a part of the browser window itself. Browser Helper objects (BHOs) fill in the missing piece of this story: they allow developers to access the events called and actions taken by IE silently. Much like a Windows service, these extensions sit in the background and perform tasks without having to interact with a user directly.
In this chapter, I discuss how you can build your own BHO and use this extension to tap into browser events and communicate with other processes. I begin the chapter with a quick introduction of these extensions and how they fit within the IE architecture. Next, I provide a demonstration of how to register, test, and uninstall an extension of this type. This chapter closes with examples of how you can use BHOs to tap into events exposed by the browser tab and process.
BHOs are IE extensions that run in the browser process but have no formal UI. These extensions implement the IObjectWithSite
interface. IE loads a new BHO instance for every tab loaded into the browser, and notifies a loaded extension of placement via the SetSite(...)
method.
Despite not having an official UI requirement, BHOs can still interact with the UI thread of a tab process. Figure 13-1 shows a sample BHO that does just this, launching a message box during an event callback.
Like ActiveX controls, toolbars, search providers, and other IE extensions, users can manage BHOs. BHOs installed into IE can be enabled and disabled through the Manage Add-Ons dialog, as shown in Figure 13-2.
For each add-on, the dialog shows the name, publisher, enabled status, install date, and load time. This dialog does not allow users to uninstall a BHO, but it does allow the user to control whether the BHO is enabled and loaded. Load time, or time it takes for IE to load a BHO and run its SetSite()
callback, was added as of IE 8 to encourage users to disable "slow" BHOs that might be negatively affecting their browser's performance.
BHOs are fairly simple to build, especially when built using the .NET Framework. As described in the preceding section, these objects are basic classes that implement the IObjectWithSite
interface. The only other requirements aside from that are that this same class have a unique GUID, be registered among the machine's COM objects, and be listed under a particular registry key.
A BHO in C# (the language used for the remainder of this chapter) consists of a class with four basic sections: instance variables to hold a browser reference, an implementation of IObjectWithSite
, functions that handle COM registration and unregistration, and invocation/definition of the IObjectWithSite
API. Listing 13-1 demonstrates the first three requirements.
Example 13.1. Basic Architecture for a BHO
[ ComVisible(true), // Make the object visible to COM Guid("48DEAC10-D583-4683-9345-3F8A117DE7A5"), // Create a new, unique GUID for each BHO ClassInterface(ClassInterfaceType.None)] // Don't create an interface (allow only late- // bound access via IDispatch) public class BhoSampleBasic : IObjectWithSite { #region Variables ... #region IObjectWithSite Interface Implementation ... #region COM Registration Functions ... }
In the example, a new BHO—BhoSampleBasic
—is defined as a class that implements the IObjectWithSite
interface. This class has a few important attributes. First, it makes marks this .NET class visible to COM. Next, it defines a GUID to uniquely identify the class. Last, it prevents .NET from exposing a class interface by setting the ClassInterface
attribute to ClassInterface.None
.
Omitting a public COM interface using ClassInterface.None
is important! IE does not know how to talk to this extension natively—it clearly does not know every method and property for every BHO that was ever created or that will be. It does, however, recognize the object's base class: IObjectWithSite
. Setting this value to ClassInterface.None
forces callers (in this case, IE) to bypass the interface of the inherited class find a common base class. Basically, it forces IE and the BHO to communicate with each other using a "language" common to both.
The instance member of this class is a single variable, Browser
. This variable is a WebBrowser
type and is used to store a reference to the browser instance where the BHO is loaded. It is populated when the SetSite
function is called as the BHO is loaded into IE (Listing 13-2).
Example 13.2. Instance Members of the Basic BHO Class
#region Instance Members SHDocVw.WebBrowser Browser; #endregion Variables
This library continues by defining the IObjectWithSite
interface. This interface is implemented by the BHO class to handle the SetSite
and GetSite
calls made by the browser instance loading this extension. Listing 13-3 defines this API.
Example 13.3. The IObjectWithSite Interface
[ComVisible(true), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), Guid("FC4801A3-2BA9-11CF-A229-00AA003D7352")] // Unlike the BHO's GUID, this GUID remains // the same for every BHO using it (since it // is indicating the GUID of IObjectWithSite, // a value constant on all Windows systems) public interface IObjectWithSite { [PreserveSig] int SetSite([MarshalAs(UnmanagedType.IUnknown)] object site); [PreserveSig] int GetSite(ref Guid guid, out IntPtr ppvSite); }
The BHO implements this API within this class. Two functions are created: SetSite
and GetSite
. These functions (described in other chapters) receive and disseminate, respectively, the instance of the browser object that loaded an instance of this class (Listing 13-4).
Example 13.4. IObjectWithSite Implementation Used by a BHO Class
#region IObjectWithSite Interface Implementation public int SetSite(object site) { // If the site is valid, cast as a WebBrowser object and // store it in the Browser variable if (site != null) { // Set the site object to the Browser variable, casting it // to a WebBrowser object type Browser = (SHDocVw.WebBrowser)site; // Tell the user SetSite was called System.Windows.Forms.MessageBox.Show("SetSite called!"); } else Browser = null; return S_OK; } public int GetSite(ref Guid guid, out IntPtr ppvSite) { // Get the IUnknown for the Browser object IntPtr pUnk = Marshal.GetIUnknownForObject(Browser); // Request a pointer for the interface int hResult = Marshal.QueryInterface(pUnk, ref guid, out ppvSite);
// Release the object Marshal.Release(pUnk); // Return the result from the QI call return hResult; } #endregion IObjectWithSite Interface Implementation
The SetSite(...)
function is called to pass the IUnknown
pointer of the IE site to a class implementing the BandObject
class. IE calls this whenever this class is loaded into its process. GetSite(...)
, in contrast, provides the IE site back to the caller of this function.
The sample here launches a MessageBox
whenever this function is called. Since a BHO has no inherent UI, this MessageBox
is used in the following section to demonstrate that an instance of this extension is in fact running.
Once a BHO is implemented, it must be registered with both COM and IE in order for it to be loaded into the browser. Listing 13-5 shows the COM registration methods where this takes place. When a BHO library is registered or unregistered, the .NET registration process calls the Register(...)
or Unregister()
functions, respectively.
Example 13.5. COM Registration Methods for a BHO Class
#region COM Registration Methods [ComRegisterFunction] public static void Register(Type type) { // Open (and create if needed) the main BHO key RegistryKey bhoKey = Registry.LocalMachine.CreateSubKey( @"SoftwareMicrosoftWindowsCurrentVersion" + @"ExplorerBrowser Helper Objects"); // Open (and create if needed) the GUID key RegistryKey guidKey = bhoKey.CreateSubKey( type.GUID.ToString("B")); // Close the open keys bhoKey.Close(); guidKey.Close(); } ...
BHOs must register themselves with IE in order for them to load inside the browser and be placed in each tab instance. Listing 13-6 shows an example registration for a BHO. Unlike with other extension types, BHOs may only be listed within the HKEY_LOCAL_MACHINE
hive. Therefore, on Windows Vista and later, BHOs may only be registered and unregistered by an elevated process running with administrative permissions.
Within the HKEY_LOCAL_MACHINE
hive, BHOs are listed as subkeys named with the GUID of the extension (Listing 13-6).
Example 13.6. Sample Registry Values for a BHO
HKEY_LOCAL_MACHINE Software Microsoft Windows CurrentVersion Explorer Browser Helper Objects {48DEAC10-D583-4683-9345-3F8A117DE7A5}
Because Windows Explorer also supports BHOs, you can prevent your BHO from loading into Windows Explorer by creating a REG_DWORD
named NoExplorer
within your BHO's subkey.
Once registered, IE will load an instance of this BHO whenever a tab is loaded. Figure 13-3 shows an example of this: the BHO class created in the previous section throws a MessageBox
dialog whenever IE loads an instance of that class and calls the SetSite(...)
function within it.
The Unregister()
function is used to remove the registry entries created by the Register(...)
function. Listing 13-7 shows the contents. When called, this removes the key associated with the extension's GUID.
Example 13.7. Unregistration Function for a BHO Class
[ComUnregisterFunction] public static void Unregister(Type type) { // Open up the main BHO key RegistryKey bhoKey = Registry.LocalMachine.OpenSubKey( @"SoftwareMicrosoftWindowsCurrentVersion" + @"ExplorerBrowser Helper Objects", true); // Delete the GUID key if it exists if (bhoKey != null) bhoKey.DeleteSubKey( type.GUID.ToString("B"), false); } #endregion COM Registration Methods
Once the registry key is removed, IE no longer loads the BHO nor lists it in the browser's list of add-ons in the Manage Add-Ons dialog.
Having an extension loaded into the browser process is quite useful. BHOs, however, are not limited to this. Given that this extension is passed a valid WebBrowser
instance through SetSite
, BHOs can take advantage of the wide range of functionality exposed by this object. Arguably, the most important piece of this function is the ability to sink (attach to) events on a WebBrowser
object instance and those of its children. Using event interfaces such as DWebBrowserEvents2
, developers can handle events triggered by the browser, the page, the user, or any other item interacting within the browser process.
The most common and useful events that a BHO can sink are exposed by the DWebBrowserEvents2
event sync interface. These events are raised by the browser object that hosts an instance of a BHO. Such events include BeforeNavigate2
(raised before a navigation), NavigationComplete2
(raised when a navigation is complete), and DocumentComplete
(raised when a document has fully loaded).
Listing 13-8 shows an example of how a BHO could sink these events. The example in the last section is extended to call event handlers as events are raised. The SetSite()
function, after grabbing the browser object instance passed to it, adds two event handlers.
Example 13.8. Web Browser Events Sunk During a SetSite Call
[SecurityPermission(SecurityAction.LinkDemand, Flags = SecurityPermissionFlag.UnmanagedCode), ComVisible(true), Guid("FE19878E-55B9-48AA-B293-2BDEEB2232DB"), ClassInterface(ClassInterfaceType.None)] public class BhoSampleEvents : NativeMethods.IObjectWithSite { ... public int SetSite(object site) {
// If the site is valid, cast as a WebBrowser object and // store it in the Browser variable if (site != null) { // Set the site object, cast as a WebBrowser object, // to the Browser variable Browser = (SHDocVw.WebBrowser)site; // Add a handler for when navigation starts Browser.BeforeNavigate2 += Browser_BeforeNavigate2; // Add a handler for when the document has completely loaded Browser.DocumentComplete += Browser_DocumentComplete; } else { // Detatch the event handlers if they were attached Browser.BeforeNavigate2 -= Browser_BeforeNavigate2; Browser.DocumentComplete -= Browser_DocumentComplete; // Set the browser object to null Browser = null; } return S_OK; }
First, it ties the function Browser_BeforeNavigate2
as a callback to the BeforeNavigate2
event. Next, it ties the function Browser_DocumentComplete
as a callback to the DocumentComplete
event.
Listing 13-9 shows the callback function called when the BeforeNavigate2
event is raised. This function demonstrates its usage by displaying a MessageBox
to the user before navigation within a specific tab takes place. This MessageBox
shows the target url
for the navigation.
Example 13.9. BHO Callback for the BeforeNavigate2 Event
void Browser_BeforeNavigate2(object pDisp, ref object URL, ref object Flags, ref object TargetFrameName, ref object PostData, ref object Headers, ref bool Cancel) { // If the site or url is null, do not continue if (pDisp == null || URL == null) return; // Convert the url object reference to a string string url = URL.ToString(); // Show the url in a message box MessageBox.Show("Begin navigation to: " + url); }
The example in Listing 13-10 performs a similar task in handling the DocumentComplete
event using the callback Browser_DocumentComplete
. Like the previous code snippet, this example also throws up a MessageBox
to demonstrate its usage. This message box takes advantage of the browser's state: since the document target is complete, this document is now loaded into the browser object and accessible by the extension. Consequently, it displays the number of links contained within the loaded document by using functionality provided by the document object instance (using the IHTMLDocument2
interface).
Example 13.10. BHO Callback for the DocumentComplete Event
void Browser_DocumentComplete(object pDisp, ref object URL) { // If the site or url is null, do not continue if (pDisp == null || URL == null) return; // Grab the document object off of the WebBrowser control IHTMLDocument2 document = (IHTMLDocument2)Browser.Document; if (document == null) return; // Report the total number of links on the current page MessageBox.Show("Total links on this page: " + document.links.length.ToString()); }
Figure 13-4 shows this callback in action. When MSN (www.msn.com
) is loaded into the browser, the DocumentComplete
event runs this callback in the browser's UI thread. The callback function (described in the previous listing) shows a message box displaying the number of links found in this page.
Developers may notice that the DocumentComplete
event sunk by this example might trigger multiple times per page load. This specific event is raised once for each frame loaded on a page, followed last by this same event raised for the top-level window. Developers looking to distinguish between DocumentComplete
events (or any other related events) raised for a frame and a top-level window can do so by comparing the pDisp
pointer passed to the event handler to the pDisp
pointer of the top-level window. One way to keep track of the pDisp
pointer for such a comparison is to save a reference to the same pDisp
pointer passed to SetSite
(in that case, the variable is site
). During DocumentComplete
, if the pDisp
pointer does not reference the same object as the version passed during SetSite
, one can assume that the event was raised for something other than the top-level window.
Those developing plug-ins should also take note of the fact that callbacks are run in the IE UI thread (as noted previously). This means that IE takes away user control (as well as the ability to update or change its UI) while a callback function is running. Controls attaching to these events should spend as little time as possible in these functions or, at the very least, spawn another thread to handle any reaction to these events. Slow or clumsy extensions can ruin a user's browser experience—a BHO that spends, for instance, 5 seconds processing data on every DocumentComplete
event will introduce a 5-second hang in the browser UI. Users might not appreciate that.
In this chapter, I talked about how you can build your own IE extension that runs in-process and out of sight, allowing you to access the events and functionality of IE without the requirement or overhead of a UI. I started this chapter by introducing the concept of BHOs, their goals, and how they fit into the overall IE architecture. Following this was an example of how to implement a BHO using C# and the .NET Framework. I then talked about how these extensions are registered, loaded, managed, and removed from the browser. I ended the chapter by expanding on this example by accessing the owning browser object and tying into events exposed from it.
BHOs offer developers a simple way to tap into the browser and enhance the functionality of pages. You can build a basic extension in a few simple steps, and tie into browser events in a simple way too. This extension is one of the simplest forms of binary IE extensions; while they cannot be constructed using simple XML or registry entries, their structure and implementation is straightforward and fairly lightweight. The functionality of BHOs is clearly not limited to the examples shown in this chapter; since these extensions have access to the browser tab and the browser object, these extensions have the same access and power as other binary IE extensions.
18.222.117.157