Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Issues faced while extending IE with Band Objects using .NET and Windows Forms

0.00/5 (No votes)
13 Aug 2007 1  
Issues faced while extending IE with Band Objects using .NET and Windows Forms

Download MyToolbar.zip - 170.8 KB

Introduction

Here, i would share some of the issues faced while developing the IE toolbar by extending a band object. I would recommend the following readings, if you are new to band objects before moving on further:

1. Creating Custom Explorer Bars, Tool Bands, and Desk Bands: It gives an idea of what exactly the bandobjects are.

2. Extending Explorer with Band Objects using .NET and Windows Forms By Pavel Zolnikov: It's a nice article, i would say. if you are new to IE extension development this article can help you a lot.

3. Band Objects - .NET 2.0 Redux By cambo1982: It is all about migrating your custom IE toolbar code developed in visual studio 2003 to 2005.

About the code

The sample toolbar(does have very basic UI) incorporates almost all the solutions discussed above. To Run the sample, build the code in the release mode to get msi and Run the msi to install the toolbar.

1. BandObjectLib

I have used Pavel Zolnikov's Bandobject library in the code and did following minor additions to it as well.
i) Added DBIMF enum and used in DESKBANDINFO struct (defined in ComInterop.cs)
ii) Modified GetBandInfo() in BandObject.cs
iii) Modified TranslateAcceleratorIO() in BandObject.cs

2. SearchBar

SearchBar is the toolbar's dll that contains
i). Toolbar class that extends BandObject defined in BandobjectLib (Toolbar.cs)
ii). A BHO Class (InitToolbarBHO.cs)
iii). An installer class that defines custom actions (InstallerClass.cs)
iv). Logger class (Logger.cs)
v). External config file (App.config)

3. ToolbarInstaller

It is a setup project that generates msi installer.

Issues and their solutions:

#1. Toolbar is not selected at IE startup by default like Google and Yahoo toolbars

It is required to select the toolbar from IE Tools->Toolbars menu before you can view it and once you selected the toolbar and it is visible, if you switch to another tab it needs to select the toolbar again.

The solution is to implement a BHO that would display the custom toolbar as soon as the IE browser starts. A Browser Helper Object (BHO) is a DLL module designed as a plugin for Microsoft's Internet Explorer web browser to provide added functionality(This definition is from wikipedia). A Browser Helper Object is loaded when the main window of the browser is about to be displayed and is unloaded when that window is destroyed. If you open more copies of the browser window, more instances of the BHO will be created.

A BHO is a COM object, so we need to add attributes to the class definition. One attribute is a GUID that should be unique. Once you generate this GUID, you can use that value forever for this one class. You can use Create Guid tool(guidgen.exe) from visual studio Tools menu to create a new GUID. BHO must implement IObjectWithSite as well.

Here an example of implementing the BHO.

I. Adding com attributes to the BHO class

   [ComVisible(true)] 
   [Guid("1D970ED5-3EDA-438d-BFFD-715931E2775B")]
   [ClassInterface(ClassInterfaceType.None)]
   public class ShowToolbarBHO : IObjectWithSite
   {
     
   } 

II. Implementing IObjectWithSite Members

IObjectWithSite interface has two methods SetSite and GetSite. The SetSite method is invoked when the BHO is instantiated and when it is destroyed. I am still unaware of when GetSite method gets called.

      #region IObjectWithSite Members
       private InternetExplorer explorer;
       /// <summary>

       /// Called, when the BHO is instantiated and when it is destroyed.

       /// </summary>

       /// <param name="site"></param>

       public void SetSite(Object site)
       {
           if (site != null)
           {
                explorer = (InternetExplorer)site;
               ShowBrowserBar(true);
           }
       }
       public void GetSite(ref Guid guid, out Object ppvSite)
       {
           IntPtr punk = Marshal.GetIUnknownForObject(explorer);
           ppvSite = new object();
           IntPtr ppvSiteIntPtr =  Marshal.GetIUnknownForObject(ppvSite);
           int hr = Marshal.QueryInterface(punk, ref guid, out ppvSiteIntPtr);
           Marshal.Release(punk);
       }
      #endregion 

III. Selecting and Displaying the toolbar at IE startup

The following method calls explorer's ShowBrowserBar method and passes the guid of your toolbar to it.

      #region Helper Methods
      private void ShowBrowserBar(bool bShow)
      {
         //GUID_OF_YOUR_BANDOBJECT: 51B38FD0-2699-4a48-8E06-46B002F58F4A

         object pvaClsid = (object)(new Guid("51B38FD0-2699-4a48-8E06-46B002F58F4A").ToString("B"));
         object pvarShow = (object)bShow;
         object pvarSize = null;       
         if (bShow) /* hide Browser bar before showing to prevent erroneous behavior of IE*/
         {
            object pvarShowFalse = (object)false;
            explorer.ShowBrowserBar(ref pvaClsid, ref pvarShowFalse, ref pvarSize);
         }
         explorer.ShowBrowserBar(ref pvaClsid, ref pvarShow, ref pvarSize);
      }
      #endregion
   }

IV. Auto-Registering the BHO

To register this COM object, you can use the .NET regasm tool. This tool will interogate the attribute you applied to the class and register the DLL as a COM component in the System Registry. But this doesn't bind the DLL as a BHO. You first need to define a couple of special methods used only during COM registration and unregistration.

When you register the DLL using the command regasm /codebase, the regasm tool searches for a method with the ComRegisterFunction attribute and, if found, will execute it. Here is where you need to add custom code to set up the system registry.

When you unregister the DLL using the command regasm /unregister, the regasm tool searches for a method with the ComUnregisterFunction attribute and, if found, will execute it. Here is where you need to add custom code to delete the registry keys you created in the ComRegisterFunction method.

       #region Com Register/UnRegister Methods
        private const string BHOKeyName = 
"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Browser Helper Objects";

       /// <summary>

       /// Called, when IE browser starts.

       /// </summary>

       /// <param name="t"></param>

       [ComRegisterFunction]
       public static void RegisterBHO(Type t)
       {
           RegistryKey key = Registry.LocalMachine.OpenSubKey(BHOKeyName, true);
           if (key == null)
           {
               key = Registry.LocalMachine.CreateSubKey(BHOKeyName);
           }
           string guidString = t.GUID.ToString("B");
           RegistryKey bhoKey = key.OpenSubKey(guidString, true);

           if (bhoKey == null)
           {
               bhoKey = key.CreateSubKey(guidString);
           }
           // NoExplorer:dword = 1 prevents the BHO to be loaded by Explorer

           string _name = "NoExplorer";
           object _value = (object)1;
           bhoKey.SetValue(_name, _value);
           key.Close();
           bhoKey.Close();
       }

       /// <param name="t"></param>

       [ComUnregisterFunction]
       public static void UnregisterBHO(Type t)
       {
           RegistryKey key = Registry.LocalMachine.OpenSubKey(BHOKeyName, true);
           string guidString = t.GUID.ToString("B");
           if (key != null)
           {
               key.DeleteSubKey(guidString, false);
           }
       }
      #endregion

#2. Debugging toolbar

Debugging a bandobject is straight forward. Go to Project properties --> Debug and choose "Start External Program" option. Browse for internet explorer path which is C:\Program Files\Internet Explorer\iexplore.exe in my case.

But to debug the BHO code, there is a trick. Since it's managed code and obviously IE is not managed code, it's rather difficult to jump in right at the first instantiation of the first BHO.
Set your IE home page set to about:blank . That way you can start up the browser as fast as possible and go where you need to. So, start up the first IE window. Then, from VS.NET use the Attach to Process item in the Debug menu to attach to iexplore.exe. Set breakpoints in your BHO. To break within the constructor, just open a second IE window.

Some of the tools that have been very useful while debugging IE extensions:
1. fuslogvw (Visual Studio tool): Assembly Binding Viewer

#3. Installing a BandObject/BHO using an MSI:

The real challenge is not building a BandObject/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).

In my opinion it would be safe to set the MSI property to vsdrpNoDotRegister and instead create your own registration component. So to register the assembly(BHO is part of the toolbar dll in my case, which is MyToolbar.dll), use the following code within your registration code:

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

And, 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(MyToolbar.dll));


i have written a installer class that is part of MyToolbar.dll and used this dll as a custom action (for both install and uninstall) in the MSI. Below is the entire installer class.

using System;
using System.ComponentModel;
using System.Configuration.Install;
using System.Runtime.InteropServices;

namespace MyToolbar
{
    [RunInstaller(true)]
    public partial class MyToolbarInstaller : Installer
    {
        public MyToolbarInstaller()
        {
            InitializeComponent();
        }

        public override void Install(System.Collections.IDictionary stateSaver)
        {
            base.Install(stateSaver);
            RegistrationServices regsrv = new RegistrationServices();
            if (!regsrv.RegisterAssembly(this.GetType().Assembly,
            AssemblyRegistrationFlags.SetCodeBase))
            {
                throw new InstallException("Failed To Register for COM");
            }
        } 

        public override void Uninstall(System.Collections.IDictionary savedState)
        {   base.Uninstall(savedState);
            RegistrationServices regsrv = new RegistrationServices();
            if (!regsrv.UnregisterAssembly(this.GetType().Assembly))
            {
                throw new InstallException("Failed To Unregister for COM");
            }
        }
    }
} 

User Account Control considerations for IE7/Vista

This approach works all very well for XP, but if you are installing your BHO/BandObject 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/BandObject. 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:

I. Write a batch file that contains the following:

msiexec /i MyToolbarMSI.msi
...and right click on the batch file, and select "Run as Administrator"

II. 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.

#4. Backspace, Home and other keys are not responding

If there is a text box in your toolbar and it's not responding to the Backspace or Home key. Then add the following code to your Bandobject's TranslateAcceleratorIO()'s else block, which handles all the keys other than Tab and F6.

                  //This sends messages for the backspace, home and other keys.

                    TranslateMessage(ref msg);
                    DispatchMessage(ref msg);
                    return 0;//S_OK


Here is the complete method.

       [DllImport("user32.dll")]
        public static extern int TranslateMessage(ref MSG lpMsg);

        [DllImport("user32", EntryPoint = "DispatchMessage")]
        static extern bool DispatchMessage(ref MSG msg);
        /// <summary>

        /// Called by explorer to process keyboard events.

        /// </summary>

        /// <param name="msg"></param>

        /// <returns>S_OK if message was processed, S_FALSE otherwise.</returns>

        public virtual Int32 TranslateAcceleratorIO(ref MSG msg)
        { 
            if (msg.message == 0x100)//WM_KEYDOWN 

            {
                //keys used by explorer to navigate from control to control

                if (msg.wParam == (uint)Keys.Tab || msg.wParam == (uint)Keys.F6)
                {
                    if (SelectNextControl(
                            ActiveControl,
                            ModifierKeys == Keys.Shift ? false : true,
                            true,
                            true,
                            false)
                        )
                        return 0;//S_OK

                }
                else
                {
                    //This sends messages for the backspace, home and other keys.

                    TranslateMessage(ref msg);
                    DispatchMessage(ref msg);
                    return 0;//S_OK

                }
            }
            return 1;//S_FALSE

        }

#5. GAC not allowing bandobject library to uninstall from there

I tried to uninstall BandObjectLib.dll from GAC(%systemroot%\assembly) the usual way but could not succeed even after closing all instances of browser and had to remove it manually. It always shown me a message saying 'Assembly BandObjectLib could not be uninstalled because it is required by other applications'.

You may also try to delete the file in GAC directory directly by following the steps below, if you can not uninstall the usual way.

1. Run cmd in run dialog.
2. cd
%systemroot%\assembly\gac_msil\BandObjectsLib\1.0.0.0__a0ebf05e75e2c6d2
(BandObjectsLib.dll is the target dll)
3. del BandObjectsLib.dll
4. cd..
5. rd 1.0.0.0__a0ebf05e75e2c6d2
6. cd..
7. rd BandObjectsLib

#6. Toolbar is not visible

Errors in assembly loading, security permissions, or object initialization can be the cause of not displaying toolbar and these are not recorded in the Fusion log. As with ActiveX controls, a .NET object that is not initialized typically fails silently, leaving a small, grooved box where the control should be. To see such errors, you need to active the IEHost debug log. So, if the toolbar is not visible may be there is some problem with bandobject's initialization and if so, please refer the link below to setup the logs and debug the cause.
HOW to Use the IEHost Log to Debug .NET Object Hosting in Internet Explorer

#7. Using App.config file with toolbar

Initially the toolbar dll was not able to locate it's App.config file. After spending a significant amount of time i realized that because of the fact that toolband runs in IE process's appdomain, when the toolband is initialized it searches for the iexplore.exe.config in the IE executable's directory(i. e. directory containing iexplore.exe) which actually does not exist there.

So i explicitly used the path of the config file (which is MyToolbar.dll.config in my case) file, where ever i needed it.

One of the place where i used it was to configure the logs(I am using log4net for logging). Following is the code snippet i used to get the absolute path of my config file:

            Assembly executingAssembly = Assembly.GetExecutingAssembly();
            string filePath = Path.GetDirectoryName(executingAssembly.Location);
            string asmName = Path.GetFileName(executingAssembly.Location);
            string configFileName = Path.Combine(filePath, asmName + ".config");            
            //configures logs

            XmlConfigurator.Configure(new FileInfo(configFileName));

Finally the log file was generated in the internet explorer's(which is C:\Program Files\Internet Explorer on my PC) directory.

Note: I used App.config file to configure logging only. if you want to read some cusom settings of your toolbar then you might need to load the App.config and read/write it explicitly, though i did not require to do so.

#8. Building the setup project

unfortunately, msbuild.exe does not support building .vdproj(setup project). So you need to have visual studio installed on the integration/build machine that is a major restriction in .net setup projects. One alternative i found is WIX(Windows installer XML). Following are the links that provide good details to start working with WIX.

Download Windows installer XML

WIX Tutorial

Using the WiX Toolset to Integrate Setup into Your Development Process

Because of short of time i couldn't integrate WIX to my build process, but it seems very interesting and solves the purpose of integrating setup development into your daily development process. And i am sure you will find it very useful.

#9 Can't move Toolbar

Please uncheck 'Lock the Toolbars' option in the browser window, before trying to move your toolbar. If the toolbar does not move, then the solution is to do some modifications to Pavel Zolnikov's Bandobject library.
i) Add DBIMF enum and used it in DESKBANDINFO struct (defined in ComInterop.cs)
ii) Modify GetBandInfo() in BandObject.cs

Here is the code snippet.

[Flags]
    public enum DBIMF : uint
    { 
        NORMAL = 0x0001,
        FIXED = 0x0002, 
        FIXEDBMP = 0x0004,
        VARIABLEHEIGHT = 0x0008,
        UNDELETEABLE = 0x0010,
        DEBOSSED = 0x0020,
        BKCOLOR = 0x0040,
        USECHEVRON = 0x0080,
        BREAK = 0x0100,
        ADDTOFRONT = 0x0200,
        TOPALIGN = 0x0400,
        NOGRIPPER = 0x0800,
        ALWAYSGRIPPER = 0x1000,
        NOMARGINS = 0x2000
    }
[StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)]
    public struct DESKBANDINFO
    {
        public DBIM         dwMask;
        public Point        ptMaxSize;
        public Point        ptIntegral;
        public Point        ptActual;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst=255)]
        public String        wszTitle;
        public Int32        crBkgnd;
    };

    public virtual void GetBandInfo(
            UInt32 dwBandID,
            UInt32 dwViewMode,
            ref DESKBANDINFO dbi )
        {

            if ( ( dbi.dwMask & DBIM.MINSIZE ) != 0 )
            {
                dbi.ptMinSize.X = this.MinSize.Width;
                dbi.ptMinSize.Y = this.MinSize.Height;
            }
            if ( ( dbi.dwMask & DBIM.MAXSIZE ) != 0 )
            {
                dbi.ptMaxSize.X = this.MaxSize.Width;
                dbi.ptMaxSize.Y = this.MaxSize.Height;
            }
            if ( ( dbi.dwMask & DBIM.INTEGRAL ) != 0 )
            {
                dbi.ptIntegral.X = this.IntegralSize.Width;
                dbi.ptIntegral.Y = this.IntegralSize.Height;
            } 
            if ( ( dbi.dwMask & DBIM.ACTUAL ) != 0 )
            {
                dbi.ptActual.X = this.Size.Width;
                dbi.ptActual.Y = this.Size.Height;
            }
            if ( ( dbi.dwMask & DBIM.TITLE ) != 0 )
            {
                dbi.wszTitle = this.Title; 
            }
            if ( ( dbi.dwMask & DBIM.MODEFLAGS ) != 0 )
            {
                dbi.dwModeFlags = DBIMF.VARIABLEHEIGHT;
            }
            dbi.dwModeFlags = DBIMF.BREAK | DBIMF.ALWAYSGRIPPER;
        } 

Detailed discussion on the issue is available at the MSDN forums: http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=469442&SiteID=1

History

Aug 1, 2007 - Initial Release

Aug 14, 2007 - Uploaded source

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here