9

这是挑战。我来自框架的WebBrowserSite类。我的派生类的一个实例,ImprovedWebBrowserSite通过返回WebBrowser.CreateWebBrowserSiteBase,我在我的派生版本的WebBrowser类中覆盖它——特别是为了提供一个自定义站点对象。框架的WebBrowser实现进一步将其传递给底层的非托管 WebBrowser ActiveX 控件。

到目前为止,我已经设法IDocHostUIHandler在我的ImprovedWebBrowserSite实现中覆盖(像这样)。我现在正在寻找更多的核心 COM 接口,例如IOleClientSite,我想传递到WebBrowserSite. 它们都通过 暴露给 COM ComImport,但声明为privateinternal由框架的WebBrowserSite/实现声明UnsafeNativeMethods。因此,我无法在派生类中显式地重新实现它们。我必须定义自己的版本,就像我对IDocHostUIHandler.

所以,问题是,如何WebBrowserSite从派生类中调用定义在 中的私有或内部 COM 接口的方法?例如,我想调用IOleClientSite.GetContainer. 我可以使用反射(像这样),但这将是最后的手段,仅次于WebBrowser从头重新实现。

我的想法是,因为框架的私有UnsafeNativeMethods.IOleClientSite和我自己ImprovedWebBrowserSite.IOleClientSite的都是COM接口,用ComImport属性声明,相同的 GUID 和相同的方法签名。.NET 4.0+ 中有COM 类型等效,因此必须有一种方法可以在没有反射的情况下做到这一点。

[更新]现在我有了一个解决方案,我相信它为自定义WinForms 版本WebBrowser控件打开了一些新的有趣的可能性。

这个版本的问题是在我最初尝试以更抽象的形式表述问题被评论员称为误导之后创建的。该评论后来被删除,但我决定保留两个版本。

为什么我不想使用反射来解决这个问题?有几个原因:

  • 依赖于内部或私有方法的实际符号名称,由 的实现者给出WebBrowserSite,与 COM 接口不同,它是关于二进制 v-table 协定的。

  • 庞大的反射代码。例如,考虑调用 base 的私有TranslateAcceleratorvia Type.InvokeMember,我有大约 20 种这样的方法可以调用。

  • 虽然不太重要,但效率:通过反射的后期绑定调用总是比通过 v-table 直接调用 COM 接口方法的效率低。

4

2 回答 2

9

最后,在@EricBrown 的帮助下,我相信我已经使用解决了这个问题。Marshal.CreateAggregatedObject

下面是使自定义WebBrowserSiteOLE 接口成为可能的代码,以IOleClientSite调用WebBrowserSite. 它可以扩展到其他接口,例如IDocHostUIHandler.

using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace CustomWebBrowser
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();
        }

        private void MainForm_Load(object sender, EventArgs e)
        {
            var wb = new ImprovedWebBrowser();
            wb.Dock = DockStyle.Fill;
            this.Controls.Add(wb);
            wb.Visible = true;
            wb.DocumentText = "<b>Hello from ImprovedWebBrowser!</b>";
        }
    }

    // ImprovedWebBrowser with custom pass-through IOleClientSite 
    public class ImprovedWebBrowser: WebBrowser
    {
        // provide custom WebBrowserSite,
        // where we override IOleClientSite and call the base implementation
        protected override WebBrowserSiteBase CreateWebBrowserSiteBase()
        {
            return new ImprovedWebBrowserSite(this);
        }

        // IOleClientSite
        [ComImport(), Guid("00000118-0000-0000-C000-000000000046")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        public interface IOleClientSite
        {
            void SaveObject();

            [return: MarshalAs(UnmanagedType.Interface)]
            object GetMoniker(
                [In, MarshalAs(UnmanagedType.U4)] int dwAssign,
                [In, MarshalAs(UnmanagedType.U4)] int dwWhichMoniker);

            [PreserveSig]
            int GetContainer([Out] out IntPtr ppContainer);

            void ShowObject();

            void OnShowWindow([In, MarshalAs(UnmanagedType.I4)] int fShow);

            void RequestNewObjectLayout();
        }

        // ImprovedWebBrowserSite
        protected class ImprovedWebBrowserSite :
            WebBrowserSite,
            IOleClientSite,
            ICustomQueryInterface,
            IDisposable
        {
            IOleClientSite _baseIOleClientSite;
            IntPtr _unkOuter;
            IntPtr _unkInnerAggregated;
            Inner _inner;

            #region Inner
            // Inner as aggregated object
            class Inner :
                ICustomQueryInterface,
                IDisposable
            {
                object _outer;
                Type[] _interfaces;

                public Inner(object outer)
                {
                    _outer = outer;
                    // the base's private COM interfaces are here
                    _interfaces = _outer.GetType().BaseType.GetInterfaces(); 
                }

                public CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv)
                {
                    if (_outer != null)
                    {
                        var ifaceGuid = iid;
                        var iface = _interfaces.FirstOrDefault((t) => t.GUID == ifaceGuid);
                        if (iface != null)
                        {
                            var unk = Marshal.GetComInterfaceForObject(_outer, iface, CustomQueryInterfaceMode.Ignore);
                            if (unk != IntPtr.Zero)
                            {
                                ppv = unk;
                                return CustomQueryInterfaceResult.Handled;
                            }
                        }
                    }
                    ppv = IntPtr.Zero;
                    return CustomQueryInterfaceResult.Failed;
                }

                ~Inner()
                {
                    // need to work out the reference counting for GC to work correctly
                    Debug.Print("Inner object finalized.");
                }

                public void Dispose()
                {
                    _outer = null;
                    _interfaces = null;
                }
            }
            #endregion

            // constructor
            public ImprovedWebBrowserSite(WebBrowser host):
                base(host)
            {
                // get the CCW object for this
                _unkOuter = Marshal.GetIUnknownForObject(this);
                Marshal.AddRef(_unkOuter);
                try
                {
                    // aggregate the CCW object with the helper Inner object
                    _inner = new Inner(this);
                    _unkInnerAggregated = Marshal.CreateAggregatedObject(_unkOuter, _inner);

                    // turn private WebBrowserSiteBase.IOleClientSite into our own IOleClientSite
                    _baseIOleClientSite = (IOleClientSite)Marshal.GetTypedObjectForIUnknown(_unkInnerAggregated, typeof(IOleClientSite));
                }
                finally
                {
                    Marshal.Release(_unkOuter);
                }
            }

            ~ImprovedWebBrowserSite()
            {
                // need to work out the reference counting for GC to work correctly
                Debug.Print("ImprovedClass finalized.");
            }

            public CustomQueryInterfaceResult GetInterface(ref Guid iid, out IntPtr ppv)
            {
                if (iid == typeof(IOleClientSite).GUID)
                {
                    // CustomQueryInterfaceMode.Ignore is to avoid infinite loop during QI.
                    ppv = Marshal.GetComInterfaceForObject(this, typeof(IOleClientSite), CustomQueryInterfaceMode.Ignore);
                    return CustomQueryInterfaceResult.Handled;
                }
                ppv = IntPtr.Zero;
                return CustomQueryInterfaceResult.NotHandled;
            }

            void IDisposable.Dispose()
            {
                base.Dispose();

                // we may have recicular references to itself
                _baseIOleClientSite = null;

                if (_inner != null)
                {
                    _inner.Dispose();
                    _inner = null;
                }

                if (_unkInnerAggregated != IntPtr.Zero)
                {
                    Marshal.Release(_unkInnerAggregated);
                    _unkInnerAggregated = IntPtr.Zero;
                }

                if (_unkOuter != IntPtr.Zero)
                {
                    Marshal.Release(_unkOuter);
                    _unkOuter = IntPtr.Zero;
                }
            }

            #region IOleClientSite
            // IOleClientSite
            public void SaveObject()
            {
                Debug.Print("IOleClientSite.SaveObject");
                _baseIOleClientSite.SaveObject();
            }

            public object GetMoniker(int dwAssign, int dwWhichMoniker)
            {
                Debug.Print("IOleClientSite.GetMoniker");
                return _baseIOleClientSite.GetMoniker(dwAssign, dwWhichMoniker);
            }

            public int GetContainer(out IntPtr ppContainer)
            {
                Debug.Print("IOleClientSite.GetContainer");
                return _baseIOleClientSite.GetContainer(out ppContainer);
            }

            public void ShowObject()
            {
                Debug.Print("IOleClientSite.ShowObject");
                _baseIOleClientSite.ShowObject();
            }

            public void OnShowWindow(int fShow)
            {
                Debug.Print("IOleClientSite.OnShowWindow");
                _baseIOleClientSite.OnShowWindow(fShow);
            }

            public void RequestNewObjectLayout()
            {
                Debug.Print("IOleClientSite.RequestNewObjectLayout");
                _baseIOleClientSite.RequestNewObjectLayout();
            }
            #endregion
        }
    }
}
于 2013-11-02T07:00:00.267 回答
4

只是一个想法,但也许您可以使用这里的一些源代码。你会重新实现,但它可能会给你你想要的。

于 2013-11-01T12:47:59.523 回答