locked
Recommended BHO sample in C# RRS feed

  • Question

  • Hi, is there an 'approved' BHO sample in C# that people generally use as a starting point? 
    Most of the ones I see are pretty old (not necessarily a bad thing) but there are a few out there with slight differences related to COM issues, and I want to ensure I'm starting with all that just right.

    eg some differences I've seen:
    1) this one says you have to call Release() twice within GetSite to ensure the COM reference counts are correct. I haven't seen that anywhere else, but looking at the API docs for Marshal.GetIUnknownForObject() and Marshal.QueryInterface() it does seem they both require a subsequent Release() call. Does anyone else do this?
    2) this one has lots of calls to Release() all over the place too.
    3) this msdn example in c++ suggests calling
    DisableThreadLibraryCalls() to avoid the overhead of new thread notifications... is this something we should generally do in C# BHOs too?  Also this article mentions the NoExplorer registry entry that most BHO examples miss.

    Many thanks,

    - Rory

    Wednesday, September 3, 2008 12:16 PM

All replies

  • This should get you going:

    using System;

    using System.Runtime.InteropServices;

    using Microsoft.Win32;

    using SHDocVw;

     

    namespace cablehead

    {

        [ComVisible(true),

        ClassInterface(ClassInterfaceType.None),

        // Create a GUID Under Tools\Cteate GUID before compiling

        Guid("Your Guid Here")]

        public class BHO : IObjectWithSite

        {

            public static readonly Guid IID_IWebBrowserApp = new Guid("{0002DF05-0000-0000-C000-000000000046}");

            public static readonly Guid IID_IUnknown = new Guid("{00000000-0000-0000-C000-000000000046}");

            WebBrowserClass explorer;

            Object site;

     

            void IObjectWithSite.SetSite(Object newSite)

            {

                if (site != null)

                  Marshal.FinalReleaseComObject(site);

     

     

                site = newSite;

                if (site != null)

                {

                    IServiceProvider sp = site as IServiceProvider;

                    Guid guid = IID_IWebBrowserApp;

                    Guid riid = IID_IUnknown;

     

                    object wba;

                    sp.QueryService(

                        ref guid,

                        ref riid,

                        out wba);

     

                    explorer = (WebBrowserClass)Marshal.CreateWrapperOfType(

                        wba as IWebBrowser,

                        typeof(WebBrowserClass));

     

                    explorer.DocumentComplete += new DWebBrowserEvents2_DocumentCompleteEventHandler(explorer_DocumentComplete);

     

                }

                else

                {

                    if (explorer != null)

                    {

                        Marshal.FinalReleaseComObject(explorer);

                        explorer = null;

                    }

     

                }

            }

     

            void explorer_DocumentComplete(object pDisp, ref object URL)

            {

                throw new Exception("The method or operation is not implemented.");

            }

            void IObjectWithSite.GetSite(ref Guid riid, out Object outSite)

            {

                outSite = site;

            }

     

     

            [ComRegisterFunction]

            public static void RegisterBHO(Type t)

            {

                RegistryKey rk = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects", true);

                rk.CreateSubKey("{" + t.GUID.ToString() + "}");

     

            }

     

            [ComUnregisterFunction]

            public static void UnregisterBHO(Type t)

            {

                RegistryKey rk = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects", true);

                rk.DeleteSubKey("{" + t.GUID.ToString() + "}");

     

            }

     

        }

     

        #region interfaces

        [ComImport]

        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]

        [Guid("6d5140c1-7436-11ce-8034-00aa006009fa")]

        public interface IServiceProvider

        {

            void QueryService(

                ref Guid guid,

                ref Guid riid,

                [MarshalAs(UnmanagedType.Interface)] out Object Obj);

        }

        [ComImport]

        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]

        [Guid("FC4801A3-2BA9-11CF-A229-00AA003D7352")]

        public interface IObjectWithSite

        {

            void SetSite([In, MarshalAs(UnmanagedType.IUnknown)] Object pUnkSite);

            void GetSite(ref Guid riid, [MarshalAs(UnmanagedType.IUnknown)] out Object ppvSite);

        }

        #endregion

     

    }

    Wednesday, September 3, 2008 3:14 PM
  • To register your BHO:

     

    In project Propertires\Build Events

     

    Post build event command line:

     

    Make sure you have Debug or Release correct on the next line.

     

    cd $(ProjectDir)bin\Release

    "D:\Micro\Visual Studio 8\SDK\v2.0\Bin\gacutil" /if BHO.dll
    "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\regasm" BHO.dll
    "D:\Micro\Visual Studio 8\SDK\v2.0\Bin\gacutil" /if Interop.SHDocVw.dll

     

    You will have to change the paths to where your gacutil and regasm are installed.

     

    This registers the BHO and puts it and your interop dll in the GAC.

     

    Fire up IE...good to go.

     

     

    Wednesday, September 3, 2008 3:26 PM
  • Wow, thanks that looks great!
    I have a few questions, maybe some are just style things, but I'm not really experienced enough in COM to know if any are important:

    - I see you use Marshal.CreateWrapperOfType() and IServiceProvider.QueryService() to get a reference to a WebBrowserClass.  Is there any advantage of this over just getting a reference to a WebBrowser using:
        explorer = site as WebBrowser;

    - When SetSite() is null you don't try to remove the DocumentComplete event wired up earlier. Is this not required?

    - The SDK says IObjectWithSite.SetSite and GetSite return HRESULT, so shouldn't they be declared as returning int, and have return value set appropriately? Or doesn't it matter in the case of BHOs?

    - Your interface for GetSite() assumes it'll be asked for an IUnknown, do we not need to worry about being asked for a different interface?

    - You use Marshal.ReleaseComObject() instead of Marshal.FinalReleaseComObject()... any reason?

    Wednesday, September 3, 2008 4:43 PM
  • << Moved another comment about DisconnectedContext to a new post here >>


    Wednesday, September 3, 2008 4:58 PM
  • Most of that code codes from Mr Paval...who was the first...as far as I know to pull off BandObjects of all types in C#.

    Deskbands, horz, vertical toolbars etc,

     

    http://www.codeproject.com/KB/shell/dotnetbandobjects.aspx

     

    Never had any problems with it.

     

    On the tabs close problem...What is in your Quit Event?

     

    Wednesday, September 3, 2008 5:01 PM
  • I haven't got a Quit event, I'm just using your sample verbatim (with a GUID).
    Wednesday, September 3, 2008 6:17 PM
  • Odd...I use that code in every BHO...no problems.

    Add a quit event to see if it fires:

                    explorer.DWebBrowserEvents_Event_Quit += new SHDocVw.DWebBrowserEvents_QuitEventHandler(explorer_DWebBrowserEvents_Event_Quit);

     

    Wednesday, September 3, 2008 6:26 PM
  • I moved the problem with closing tabs (DisconnectedContext) to this thread, but the solution seems to be using FinalReleaseComObject() instead of just ReleaseComObject(). So if anyone is using the sample above I suggest changing the two uses of ReleaseComObject() to FinalReleaseComObject() instead.

    Wednesday, September 3, 2008 6:28 PM
  • Changed the code to FinalRelease in the code sample above.

    It didn't seem to break any of my BHO's....

     

    Wednesday, September 3, 2008 6:45 PM
  •  cablehead wrote:

    Most of that code codes from Mr Paval...who was the first...as far as I know to pull off BandObjects of all types in C#.

    Deskbands, horz, vertical toolbars etc,

     

    http://www.codeproject.com/KB/shell/dotnetbandobjects.aspx

     

    Never had any problems with it.

     

     

    I too have started from there (and and sincere thanks a lot for that, otherwise it would have been a real and endless nightmare), still that code is far from perfect. Of course, one is supposed to just take it as a starting point, and here I am ***NOT*** complaining with the original author(s).

     

    What I am saying is that it doesn't even get the COM mappings right (the DBIM/DBINF stuff for sure; still impossible to fix, as the MDNS docs just refer to header files that do not belong to any SDK I am aware of -- but that must be me, so if you know, please just tell). Not to talk about the SetSite/GetSite good-as-it-doesnt-apparently-crash, as well as the GetBandInfo function that has got it wrong too (or maybe just superseeded).

     

    So, great that there was that example, and great that, by digging and digging and digging, one can even find some clues as to how to make it work for real, like the transparency for a toolbar, and similar basic stuff.

     

    I am no COM expert, I wouldn't know where to look for the undocumented header files, neither I would know how to fix the SetSite/GetSite thing, otherwise I guess I'd already have published a polished C# sample.

     

    My thanks to Rory K who has raised this issue: maybe we could join efforts and release a "BandObjects 2008"?

     

    -LV

    Thursday, September 4, 2008 12:17 AM
  • Hi,

    Hoping someone can help me here. I am new to Unmanaged access from .Net  and am confused!

     

    I am trying to have a BandObject automatically show on the Windows XP taskbar not IE. I can only get it run by right clicking and selecting it from the tool bars which works. But I would like it to automatically get it to show, ideally without the Toolbar resizer....

     

    I am using the BHO above but can't really figure out how to hook it up to the Windows Task Bar. I thought perhaps I need to call it directly with something like:

    IntPtr explorerHandle = IntPtr.Zero;

    BrowserHelperObject bho = new BrowserHelperObject();

    bho.GetSite(ref guid, out explorerHandle);

    WebBrowserClass explorerObj = (WebBrowserClass)Marshal.PtrToStructure(explorerHandle, typeof(WebBrowserClass));

    bho.SetSite(explorerObj);

     

    However I get a null type exception on bho.GetSite(ref guid, out explorerHandle); and not even sure if this is correct.

     

    Your help is greatly appreciated!!!

     

    Dwight

    Friday, September 19, 2008 5:09 PM