在座有人有开发IE扩展的经验吗?这将包括代码示例,或链接到好的示例,或关于流程的文档,或任何东西。

我真的很想这么做,但我碰到了一堵巨大的墙,里面有糟糕的文档、糟糕的代码/示例代码/缺乏它们。你能提供的任何帮助/资源都将不胜感激。

具体来说,我想从如何从IE扩展中访问/操作DOM开始。

编辑,甚至更多的细节:

理想情况下,我想种植一个工具栏按钮,当点击时,弹出一个菜单,其中包含到外部网站的链接。我还想根据某些条件访问DOM并在页面上植入JavaScript。

在IE扩展中保存信息的最好方法是什么?在Firefox/Chrome/大多数现代浏览器中,你使用window。localStorage,但显然在IE8/IE7中,这不是一个选项。也许是SQLite DB之类的?可以假设。net 4.0将安装在用户的计算机上吗?

我不想使用Spice IE,因为我想构建一个与IE9兼容的IE。我也为这个问题添加了c++标签,因为如果用c++构建一个更好的话,我可以这样做。


当前回答

这显然是解决了,但对于其他用户,我建议使用SpicIE框架。我在此基础上做了自己的扩展。它只支持Internet Explorer 7/8官方版本,但我在Internet Explorer 6-10(从Windows XP到Windows 8消费者预览版)上进行了测试,结果很好。 不幸的是,在最新的版本中有一些bug,所以我不得不修复它们,并发布了自己的版本: http://archive.msdn.microsoft.com/SpicIE/Thread/View.aspx?ThreadId=5251

其他回答

[更新]我更新这个答案与Internet Explorer 11,在Windows 10 x64与Visual Studio 2017社区。 这个答案的前一个版本(适用于Internet Explorer 8, Windows 7 x64和Visual Studio 2010)在这个答案的底部。

创建一个工作的Internet Explorer 11附加组件

我使用的是Visual Studio 2017 Community, c#, . net Framework 4.6.1,因此其中一些步骤对您来说可能略有不同。

您需要以管理员身份打开Visual Studio来构建解决方案,以便构建后的脚本可以注册BHO(需要注册表访问)。

首先创建一个类库。 我叫我的InternetExplorerExtension。

将这些引用添加到项目中:

交互操作。SHDocVw: COM标签/搜索“Microsoft Internet控件” 微软。mshtml:程序集标签/搜索"Microsoft.mshtml"

注意:不知怎么的,MSHTML没有在我的系统中注册,即使我可以在添加引用窗口中找到。这在构建时导致了一个错误:

找不到类型库“MSHTML”的包装程序集

修复程序可以在http://techninotes.blogspot.com/2016/08/fixing-cannot-find-wrapper-assembly-for.html上找到 或者,你可以运行这个批处理脚本:

"%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community\Common7\Tools\VsDevCmd.bat"
cd "%ProgramFiles(x86)%\Microsoft Visual Studio\2017\Community\Common7\IDE\PublicAssemblies"
regasm Microsoft.mshtml.dll
gacutil /i Microsoft.mshtml.dll

创建以下文件:

IEAddon.cs

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Microsoft.Win32;
using mshtml;
using SHDocVw;

namespace InternetExplorerExtension
{
    [ComVisible(true)]
    [ClassInterface(ClassInterfaceType.None)]
    [Guid("D40C654D-7C51-4EB3-95B2-1E23905C2A2D")]
    [ProgId("MyBHO.WordHighlighter")]
    public class WordHighlighterBHO : IObjectWithSite, IOleCommandTarget
    {
        const string DefaultTextToHighlight = "browser";

        IWebBrowser2 browser;
        private object site;

        #region Highlight Text
        void OnDocumentComplete(object pDisp, ref object URL)
        {
            try
            {
                // @Eric Stob: Thanks for this hint!
                // This was used to prevent this method being executed more than once in IE8... but now it seems to not work anymore.
                //if (pDisp != this.site)
                //    return;

                var document2 = browser.Document as IHTMLDocument2;
                var document3 = browser.Document as IHTMLDocument3;

                var window = document2.parentWindow;
                window.execScript(@"function FncAddedByAddon() { alert('Message added by addon.'); }");

                Queue<IHTMLDOMNode> queue = new Queue<IHTMLDOMNode>();
                foreach (IHTMLDOMNode eachChild in document3.childNodes)
                    queue.Enqueue(eachChild);

                while (queue.Count > 0)
                {
                    // replacing desired text with a highlighted version of it
                    var domNode = queue.Dequeue();

                    var textNode = domNode as IHTMLDOMTextNode;
                    if (textNode != null)
                    {
                        if (textNode.data.Contains(TextToHighlight))
                        {
                            var newText = textNode.data.Replace(TextToHighlight, "<span style='background-color: yellow; cursor: hand;' onclick='javascript:FncAddedByAddon()' title='Click to open script based alert window.'>" + TextToHighlight + "</span>");
                            var newNode = document2.createElement("span");
                            newNode.innerHTML = newText;
                            domNode.replaceNode((IHTMLDOMNode)newNode);
                        }
                    }
                    else
                    {
                        // adding children to collection
                        var x = (IHTMLDOMChildrenCollection)(domNode.childNodes);
                        foreach (IHTMLDOMNode eachChild in x)
                        {
                            if (eachChild is mshtml.IHTMLScriptElement)
                                continue;
                            if (eachChild is mshtml.IHTMLStyleElement)
                                continue;

                            queue.Enqueue(eachChild);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }
        }
        #endregion
        #region Load and Save Data
        static string TextToHighlight = DefaultTextToHighlight;
        public static string RegData = "Software\\MyIEExtension";

        [DllImport("ieframe.dll")]
        public static extern int IEGetWriteableHKCU(ref IntPtr phKey);

        private static void SaveOptions()
        {
            // In IE 7,8,9,(desktop)10 tabs run in Protected Mode
            // which prohibits writes to HKLM, HKCU.
            // Must ask IE for "Writable" registry section pointer
            // which will be something like HKU/S-1-7***/Software/AppDataLow/
            // In "metro" IE 10 mode, tabs run in "Enhanced Protected Mode"
            // where BHOs are not allowed to run, except in edge cases.
            // see http://blogs.msdn.com/b/ieinternals/archive/2012/03/23/understanding-ie10-enhanced-protected-mode-network-security-addons-cookies-metro-desktop.aspx
            IntPtr phKey = new IntPtr();
            var answer = IEGetWriteableHKCU(ref phKey);
            RegistryKey writeable_registry = RegistryKey.FromHandle(
                new Microsoft.Win32.SafeHandles.SafeRegistryHandle(phKey, true)
            );
            RegistryKey registryKey = writeable_registry.OpenSubKey(RegData, true);

            if (registryKey == null)
                registryKey = writeable_registry.CreateSubKey(RegData);
            registryKey.SetValue("Data", TextToHighlight);

            writeable_registry.Close();
        }
        private static void LoadOptions()
        {
            // In IE 7,8,9,(desktop)10 tabs run in Protected Mode
            // which prohibits writes to HKLM, HKCU.
            // Must ask IE for "Writable" registry section pointer
            // which will be something like HKU/S-1-7***/Software/AppDataLow/
            // In "metro" IE 10 mode, tabs run in "Enhanced Protected Mode"
            // where BHOs are not allowed to run, except in edge cases.
            // see http://blogs.msdn.com/b/ieinternals/archive/2012/03/23/understanding-ie10-enhanced-protected-mode-network-security-addons-cookies-metro-desktop.aspx
            IntPtr phKey = new IntPtr();
            var answer = IEGetWriteableHKCU(ref phKey);
            RegistryKey writeable_registry = RegistryKey.FromHandle(
                new Microsoft.Win32.SafeHandles.SafeRegistryHandle(phKey, true)
            );
            RegistryKey registryKey = writeable_registry.OpenSubKey(RegData, true);

            if (registryKey == null)
                registryKey = writeable_registry.CreateSubKey(RegData);
            registryKey.SetValue("Data", TextToHighlight);

            if (registryKey == null)
            {
                TextToHighlight = DefaultTextToHighlight;
            }
            else
            {
                TextToHighlight = (string)registryKey.GetValue("Data");
            }
            writeable_registry.Close();
        }
        #endregion

        [Guid("6D5140C1-7436-11CE-8034-00AA006009FA")]
        [InterfaceType(1)]
        public interface IServiceProvider
        {
            int QueryService(ref Guid guidService, ref Guid riid, out IntPtr ppvObject);
        }

        #region Implementation of IObjectWithSite
        int IObjectWithSite.SetSite(object site)
        {
            this.site = site;

            if (site != null)
            {
                LoadOptions();

                var serviceProv = (IServiceProvider)this.site;
                var guidIWebBrowserApp = Marshal.GenerateGuidForType(typeof(IWebBrowserApp)); // new Guid("0002DF05-0000-0000-C000-000000000046");
                var guidIWebBrowser2 = Marshal.GenerateGuidForType(typeof(IWebBrowser2)); // new Guid("D30C1661-CDAF-11D0-8A3E-00C04FC9E26E");
                IntPtr intPtr;
                serviceProv.QueryService(ref guidIWebBrowserApp, ref guidIWebBrowser2, out intPtr);

                browser = (IWebBrowser2)Marshal.GetObjectForIUnknown(intPtr);

                ((DWebBrowserEvents2_Event)browser).DocumentComplete +=
                    new DWebBrowserEvents2_DocumentCompleteEventHandler(this.OnDocumentComplete);
            }
            else
            {
                ((DWebBrowserEvents2_Event)browser).DocumentComplete -=
                    new DWebBrowserEvents2_DocumentCompleteEventHandler(this.OnDocumentComplete);
                browser = null;
            }
            return 0;
        }
        int IObjectWithSite.GetSite(ref Guid guid, out IntPtr ppvSite)
        {
            IntPtr punk = Marshal.GetIUnknownForObject(browser);
            int hr = Marshal.QueryInterface(punk, ref guid, out ppvSite);
            Marshal.Release(punk);
            return hr;
        }
        #endregion
        #region Implementation of IOleCommandTarget
        int IOleCommandTarget.QueryStatus(IntPtr pguidCmdGroup, uint cCmds, ref OLECMD prgCmds, IntPtr pCmdText)
        {
            return 0;
        }
        int IOleCommandTarget.Exec(IntPtr pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
        {
            try
            {
                // Accessing the document from the command-bar.
                var document = browser.Document as IHTMLDocument2;
                var window = document.parentWindow;
                var result = window.execScript(@"alert('You will now be allowed to configure the text to highlight...');");

                var form = new HighlighterOptionsForm();
                form.InputText = TextToHighlight;
                if (form.ShowDialog() != DialogResult.Cancel)
                {
                    TextToHighlight = form.InputText;
                    SaveOptions();
                }
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
            }

            return 0;
        }
        #endregion

        #region Registering with regasm
        public static string RegBHO = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Browser Helper Objects";
        public static string RegCmd = "Software\\Microsoft\\Internet Explorer\\Extensions";

        [ComRegisterFunction]
        public static void RegisterBHO(Type type)
        {
            string guid = type.GUID.ToString("B");

            // BHO
            {
                RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(RegBHO, true);
                if (registryKey == null)
                    registryKey = Registry.LocalMachine.CreateSubKey(RegBHO);
                RegistryKey key = registryKey.OpenSubKey(guid);
                if (key == null)
                    key = registryKey.CreateSubKey(guid);
                key.SetValue("Alright", 1);
                registryKey.Close();
                key.Close();
            }

            // Command
            {
                RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(RegCmd, true);
                if (registryKey == null)
                    registryKey = Registry.LocalMachine.CreateSubKey(RegCmd);
                RegistryKey key = registryKey.OpenSubKey(guid);
                if (key == null)
                    key = registryKey.CreateSubKey(guid);
                key.SetValue("ButtonText", "Highlighter options");
                key.SetValue("CLSID", "{1FBA04EE-3024-11d2-8F1F-0000F87ABD16}");
                key.SetValue("ClsidExtension", guid);
                key.SetValue("Icon", "");
                key.SetValue("HotIcon", "");
                key.SetValue("Default Visible", "Yes");
                key.SetValue("MenuText", "&Highlighter options");
                key.SetValue("ToolTip", "Highlighter options");
                //key.SetValue("KeyPath", "no");
                registryKey.Close();
                key.Close();
            }
        }

        [ComUnregisterFunction]
        public static void UnregisterBHO(Type type)
        {
            string guid = type.GUID.ToString("B");
            // BHO
            {
                RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(RegBHO, true);
                if (registryKey != null)
                    registryKey.DeleteSubKey(guid, false);
            }
            // Command
            {
                RegistryKey registryKey = Registry.LocalMachine.OpenSubKey(RegCmd, true);
                if (registryKey != null)
                    registryKey.DeleteSubKey(guid, false);
            }
        }
        #endregion
    }
}

Interop.cs

using System;
using System.Runtime.InteropServices;
namespace InternetExplorerExtension
{
    [ComVisible(true)]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    [Guid("FC4801A3-2BA9-11CF-A229-00AA003D7352")]
    public interface IObjectWithSite
    {
        [PreserveSig]
        int SetSite([MarshalAs(UnmanagedType.IUnknown)]object site);
        [PreserveSig]
        int GetSite(ref Guid guid, [MarshalAs(UnmanagedType.IUnknown)]out IntPtr ppvSite);
    }


    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
    public struct OLECMDTEXT
    {
        public uint cmdtextf;
        public uint cwActual;
        public uint cwBuf;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
        public char rgwz;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct OLECMD
    {
        public uint cmdID;
        public uint cmdf;
    }

    [ComImport(), ComVisible(true),
    Guid("B722BCCB-4E68-101B-A2BC-00AA00404770"),
    InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IOleCommandTarget
    {

        [return: MarshalAs(UnmanagedType.I4)]
        [PreserveSig]
        int QueryStatus(
            [In] IntPtr pguidCmdGroup,
            [In, MarshalAs(UnmanagedType.U4)] uint cCmds,
            [In, Out, MarshalAs(UnmanagedType.Struct)] ref OLECMD prgCmds,
            //This parameter must be IntPtr, as it can be null
            [In, Out] IntPtr pCmdText);

        [return: MarshalAs(UnmanagedType.I4)]
        [PreserveSig]
        int Exec(
            //[In] ref Guid pguidCmdGroup,
            //have to be IntPtr, since null values are unacceptable
            //and null is used as default group!
            [In] IntPtr pguidCmdGroup,
            [In, MarshalAs(UnmanagedType.U4)] uint nCmdID,
            [In, MarshalAs(UnmanagedType.U4)] uint nCmdexecopt,
            [In] IntPtr pvaIn,
            [In, Out] IntPtr pvaOut);
    }
}

最后是一个表单,我们会用它来配置选项。在这个表单中放置一个文本框和一个Ok按钮。将按钮的dialgresult设置为Ok。将以下代码放入表单代码:

using System.Windows.Forms;
namespace InternetExplorerExtension
{
    public partial class HighlighterOptionsForm : Form
    {
        public HighlighterOptionsForm()
        {
            InitializeComponent();
        }

        public string InputText
        {
            get { return this.textBox1.Text; }
            set { this.textBox1.Text = value; }
        }
    }
}

在项目属性中,执行以下操作:

Sign the assembly with a strong-key; In the Debug tab, set Start External Program to C:\Program Files (x86)\Internet Explorer\iexplore.exe In the Debug tab, set Command Line Arguments to http://msdn.microsoft.com/en-us/library/ms976373.aspx#bho_getintouch In the Build Events tab, set Post-build events command line to: "%ProgramFiles(x86)%\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\gacutil.exe" /f /i "$(TargetDir)$(TargetFileName)" "%windir%\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe" /unregister "$(TargetDir)$(TargetFileName)" "%windir%\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe" "$(TargetDir)$(TargetFileName)"

注意:即使我的电脑是x64,我使用了非x64的gacutil.exe路径,它工作…特定于x64的是:

C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.6.1 Tools\x64\gacutil.exe

64位IE需要64位编译和64位注册的BHO。虽然我只能使用32位的IE11进行调试,但32位的注册扩展也可以通过运行64位的IE11工作。

这个答案似乎有一些关于这个的额外信息:https://stackoverflow.com/a/23004613/195417

如果需要,你可以使用64位regasm:

%windir%\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe

这个附加组件是如何工作的

我没有改变附加组件的行为…看看下面IE8部分的描述。


## IE8之前的答案

男人。这可是个大工程! 我对如何做到这一点非常好奇,所以我自己做了。

首先……功劳不全是我的。这是我在这些网站上发现的一个汇编:

CodeProject文章,如何制作BHO; 15秒,但不是15秒,它花了大约7个小时; 微软教程,帮助我添加命令按钮。 这就是社交。msdn主题,这帮助我弄清楚程序集必须在GAC中。 这篇MSDN博客文章包含了一个完整的示例 许多其他地点,在发现过程中…

当然,我希望我的答案具有你所要求的特征:

DOM遍历来找到一些东西; 一个显示窗口的按钮(在我的例子中是设置窗口) 持久化配置(我将使用注册表) 最后执行javascript。

我将一步一步地描述它,我是如何在Windows 7 x64中使用Internet Explorer 8工作的……请注意,我无法在其他配置中进行测试。希望你能理解=)

创建一个工作的Internet Explorer 8附加组件

我使用的是Visual Studio 2010, c# 4, . net Framework 4,所以这些步骤对你来说可能略有不同。

创建类库。我叫我的InternetExplorerExtension。

将这些引用添加到项目中:

交互操作。SHDocVw Microsoft.mshtml

注意:这些引用可能位于每台计算机的不同位置。

这是我在csproj中的引用部分包含的内容:

<Reference Include="Interop.SHDocVw, Version=1.1.0.0, Culture=neutral, PublicKeyToken=90ba9c70f846762e, processorArchitecture=MSIL">
  <SpecificVersion>False</SpecificVersion>
  <EmbedInteropTypes>True</EmbedInteropTypes>
  <HintPath>C:\Program Files (x86)\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies\Interop.SHDocVw.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CSharp" />
<Reference Include="Microsoft.mshtml, Version=7.0.3300.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
  <EmbedInteropTypes>True</EmbedInteropTypes>
</Reference>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />

按照IE11更新文件的方法创建文件。

IEAddon.cs

你可以从IE11版本中取消注释以下行:

...
// @Eric Stob: Thanks for this hint!
// This was used to prevent this method being executed more than once in IE8... but now it seems to not work anymore.
if (pDisp != this.site)
    return;
...

Interop.cs

IE11版本相同。

最后是一个表单,我们会用它来配置选项。在这个表单中放置一个文本框和一个Ok按钮。将按钮的dialgresult设置为Ok。该代码是相同的IE11插件。

在项目属性中,执行以下操作:

Sign the assembly with a strong-key; In the Debug tab, set Start External Program to C:\Program Files (x86)\Internet Explorer\iexplore.exe In the Debug tab, set Command Line Arguments to http://msdn.microsoft.com/en-us/library/ms976373.aspx#bho_getintouch In the Build Events tab, set Post-build events command line to: "C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\Bin\NETFX 4.0 Tools\x64\gacutil.exe" /f /i "$(TargetDir)$(TargetFileName)" "C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe" /unregister "$(TargetDir)$(TargetFileName)" "C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe" "$(TargetDir)$(TargetFileName)"

注意:由于我的计算机是x64,在我的计算机上的gacutil可执行文件的路径中有一个特定的x64,可能与您的计算机上的不同。

64位IE需要64位编译和64位注册的BHO。使用64位RegAsm.exe(通常位于C:\Windows\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe)

这个附加组件是如何工作的

It traverses all DOM tree, replacing the text, configured using the button, by itself with a yellow background. If you click on the yellowed texts, it calls a javascript function that was inserted on the page dynamically. The default word is 'browser', so that it matches a lot of them! EDIT: after changing the string to be highlighted, you must click the URL box and press Enter... F5 will not work, I think that it is because F5 is considered as 'navigation', and it would require to listen to navigate event (maybe). I'll try to fix that later.

现在,该走了。我很累。 请随意提问……也许我不能回答,因为我要去旅行了……三天后我就回来了,不过这段时间我会尽量赶回来的。

这显然是解决了,但对于其他用户,我建议使用SpicIE框架。我在此基础上做了自己的扩展。它只支持Internet Explorer 7/8官方版本,但我在Internet Explorer 6-10(从Windows XP到Windows 8消费者预览版)上进行了测试,结果很好。 不幸的是,在最新的版本中有一些bug,所以我不得不修复它们,并发布了自己的版本: http://archive.msdn.microsoft.com/SpicIE/Thread/View.aspx?ThreadId=5251

这个问题是从2013年开始,现在是2020年,但这可能对未来的游客有帮助。

我试着执行米格尔·安吉洛的回答,一开始并没有成功。

还有一些设置需要定义。

在ie浏览器上(我使用IE-11)进入工具-> internet选项->高级:

也可以看看这个SO问题和这个来自github的例子

如果你不打算重新发明轮子,你可以试试IE的Add In Express。我已经使用该产品的VSTO的东西,它很好。此外,他们有一个有用的论坛和快速的支持。

我同意Robert Harvey的观点,c# 4.0改进了COM互操作。这是一段非常需要重写的旧c#代码。

http://www.codeproject.com/KB/cs/Attach_BHO_with_C_.aspx

这是通过避免ATL和使用Spartan COM来简化事情的尝试:

c++和COM让bho运行起来