【问题标题】:WebBrowserSite: how to call a private COM interface method in a derived class?WebBrowserSite:如何在派生类中调用私有 COM 接口方法?
【发布时间】:2013-11-12 02:58:42
【问题描述】:

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

到目前为止,我已经设法在我的ImprovedWebBrowserSite 实现中覆盖IDocHostUIHandler(如this)。我现在正在寻找更多的核心 COM 接口,例如 IOleClientSite,我想将其传递给 WebBrowserSite。它们都以ComImport 暴露给COM,但由框架的WebBrowserSite/UnsafeNativeMethods 实现声明为privateinternal。因此,我不能在派生类中explicitly re-implement 他们。我必须定义自己的版本,就像我对 IDocHostUIHandler 所做的那样。

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

我的想法是,因为框架的私有UnsafeNativeMethods.IOleClientSite 和我自己的ImprovedWebBrowserSite.IOleClientSite 都是COM 接口,用ComImport 属性声明,相同的GUID 和相同的方法签名。 .NET 4.0+ 中有COM Type Equivalence,所以必须有一种方法可以不用反射。

[更新]现在我有了一个solution,我相信它为自定义WebBrowser 控件的WinForms version 开辟了一些新的有趣的可能性。

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

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

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

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

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

【问题讨论】:

  • 为什么要覆盖它们而不是简单地复制粘贴它们的声明?
  • @VladL,看看.NET 4.x sourcesWebBrowserSite 在 ActiveX 托管方面做了很多事情,我不能绕过调用它的 COM 接口实现。我当然可以复制粘贴接口声明,但我也必须调用原始实现。
  • 我不认为你可以在非公共类型之间有类型等价。如果您尝试将 TypeIdentifier 属性添加到非公共类型,则会收到此类错误:"Could not load type 'IOleClientSite' from assembly 'blabla...'. The type is marked as eligible for type equivalence, but it is not marked as public."
  • @SimonMourier,似乎可以通过使用Marshal.CreateAggregatedObject 创建的聚合代理对象来实现。
  • 反射有什么问题?为私有方法创建一个委托,调用速度与任何委托调用一样快。

标签: c# .net com webbrowser-control com-interop


【解决方案1】:

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

以下代码可以自定义WebBrowserSite OLE 接口,以IOleClientSite 为例,调用WebBrowserSite 的私有COM 可见实现。它可以扩展到其他接口,例如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
        }
    }
}

【讨论】:

  • 它在 .Net 4.5 中运行良好,但在 .Net4.0 中出现 FatalExecutionEngineError 它说“运行时遇到致命错误。错误地址位于线程 0x2e18 上的 0x6ef47786。错误代码是 0xc0000005。此错误可能是 CLR 中的错​​误或用户代码的不安全或不可验证部分中的错误。”此错误发生在以下行:_baseIDocHostUiHandler = (NativeMethods.IDocHostUIHandler)Marshal.GetTypedObjectForIUnknown(_unkInnerAggregated, typeof(NativeMethods.IDocHostUIHandler));有什么想法吗?
  • @VallabhaV,对 .NET 4.0 没有寄予厚望,我的空闲时间很短,抱歉。
  • 实施 IDisposable 不会神奇地让消费者使用它。 _unkOuter 和 _unkInnerAggregated 仅在构造函数中使用,因此它们不应在实例的整个生命周期内保持活动状态。相反,它们都应该在使用后在构造函数中释放,CreateAggregatedObject 将已经 AddRef 对它们进行了 AddRef 调用,并且应该从构造函数中删除 AddRef 调用。然后去掉 IDisposable 实现,反正也不会用。
  • @poizan42,很高兴知道这段代码引起了您的兴趣,并感谢您对如何改进它的想法。
【解决方案2】:

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

【讨论】:

  • 谢谢,我实际上已经研究过框架源代码——对于理解WebBrowser 的工作原理非常有帮助。尽管在这种情况下我能做的最好的事情就是借用UnsafeNativeMethods的定义。
猜你喜欢
  • 2013-12-26
  • 2014-01-27
  • 2017-11-29
  • 2015-01-04
  • 2010-09-16
  • 1970-01-01
  • 1970-01-01
  • 2019-07-15
  • 1970-01-01
相关资源
最近更新 更多